| File: BuildService.cs | Web Access |
| Project: src\sdk\src\Microsoft.DotNet.ProjectTools\Microsoft.DotNet.ProjectTools.csproj (Microsoft.DotNet.ProjectTools) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Concurrent; using System.Collections.Immutable; using System.Runtime.CompilerServices; using System.Xml; namespace Microsoft.DotNet.FileBasedPrograms; public sealed class BuildService : IBuildService { public static BuildService Instance { get; } = new(); private BuildService() { } public ValueTask<IProjectInstance> CreateProjectInstanceFromProjectRootElementAsync( IProjectRootElement projectRoot, IProjectCollection projectCollection, IDictionary<string, string>? additionalGlobalProperties) { return new(ProjectInstance.FromProjectRootElement((ProjectRootElement)projectRoot, (ProjectCollection)projectCollection, additionalGlobalProperties)); } public IProjectRootElement CreateProjectRootElement(XmlReader xmlReader, IProjectCollection projectCollection, string entryPointFilePath) { return ProjectRootElement.Create(xmlReader, (ProjectCollection)projectCollection, entryPointFilePath); } } public static class BuildServiceExtensions { private static readonly ConditionalWeakTable<Microsoft.Build.Evaluation.ProjectCollection, IProjectCollection> s_projectCollections = new(); extension(Microsoft.Build.Evaluation.ProjectCollection projectCollection) { public IProjectCollection Wrap() { return s_projectCollections.GetValue(projectCollection, static inner => new ProjectCollection(inner)); } } extension(Microsoft.Build.Execution.ProjectInstance projectInstance) { public IProjectInstance Wrap() { return new ProjectInstance(projectInstance); } } extension(IProjectRootElement projectRootElement) { public Microsoft.Build.Construction.ProjectRootElement Unwrap() { return ((ProjectRootElement)projectRootElement).Inner; } } extension(IProjectInstance projectInstance) { public Microsoft.Build.Execution.ProjectInstance Unwrap() { return ((ProjectInstance)projectInstance).Inner; } } } sealed file class ProjectCollection(Microsoft.Build.Evaluation.ProjectCollection inner) : IProjectCollection { /// <summary> /// Keeps a strong reference to the latest virtual <see cref="IProjectRootElement"/> created for each entry point in this <see cref="IProjectCollection"/>, /// preventing it from being garbage collected when MSBuild's <c>ProjectRootElementCache</c> demotes it to a weak reference. /// Without this, nested <c><MSBuild></c> tasks that re-evaluate virtual projects with different properties /// would fail to find the <see cref="IProjectRootElement"/> in the cache and try to load it from disk, /// resulting in MSB4025 because the virtual project file does not exist on disk. /// See <see href="https://github.com/dotnet/sdk/issues/52714"/>. /// </summary> private readonly ConcurrentDictionary<string, Microsoft.Build.Construction.ProjectRootElement> _projectRootElements = new(StringComparer.OrdinalIgnoreCase); public Microsoft.Build.Evaluation.ProjectCollection Inner => inner; public void StoreProjectRootElement(string entryPointFilePath, Microsoft.Build.Construction.ProjectRootElement projectRootElement) { _projectRootElements[entryPointFilePath] = projectRootElement; } } sealed file class ProjectInstance(Microsoft.Build.Execution.ProjectInstance inner) : IProjectInstance { public static ProjectInstance FromProjectRootElement( ProjectRootElement projectRoot, ProjectCollection projectCollection, IDictionary<string, string>? additionalGlobalProperties) { var mergedGlobalProperties = new Dictionary<string, string>(projectCollection.Inner.GlobalProperties, StringComparer.OrdinalIgnoreCase); if (additionalGlobalProperties != null) { foreach (var pair in additionalGlobalProperties) { mergedGlobalProperties[pair.Key] = pair.Value; } } return new(Microsoft.Build.Execution.ProjectInstance.FromProjectRootElement(projectRoot.Inner, new() { ProjectCollection = projectCollection.Inner, GlobalProperties = mergedGlobalProperties, })); } public Microsoft.Build.Execution.ProjectInstance Inner => inner; public ValueTask<ImmutableArray<ImmutableArray<string>>> GetItemMetadataValuesAsync(string itemType, ImmutableArray<string> metadataNames) => new(inner.GetItems(itemType).Select(i => metadataNames.Select(n => i.GetMetadataValue(n)).ToImmutableArray()).ToImmutableArray()); public ValueTask<string> GetPropertyValueAsync(string propertyName) => new(inner.GetPropertyValue(propertyName)); public ValueTask<string> ExpandStringAsync(string value) => new(inner.ExpandString(value)); } sealed file class ProjectRootElement(Microsoft.Build.Construction.ProjectRootElement inner) : IProjectRootElement { public static ProjectRootElement Create(XmlReader xmlReader, ProjectCollection projectCollection, string entryPointFilePath) { var inner = Microsoft.Build.Construction.ProjectRootElement.Create(xmlReader, projectCollection.Inner); projectCollection.StoreProjectRootElement(entryPointFilePath, inner); return new(inner); } public Microsoft.Build.Construction.ProjectRootElement Inner => inner; public string? FullPath { get => inner.FullPath; set => inner.FullPath = value; } public string GetRawXml() => inner.RawXml; }