// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Definition; using Microsoft.Build.Evaluation; using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using Microsoft.Build.FileSystem; using Microsoft.Build.Framework; using Microsoft.Build.Instance; using Microsoft.Build.Instance.ImmutableProjectCollections; using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Constants = Microsoft.Build.Framework.Constants; using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord; using ObjectModel = System.Collections.ObjectModel; using ProjectItemInstanceFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.ProjectItemInstanceFactory; using SdkResult = Microsoft.Build.BackEnd.SdkResolution.SdkResult; #nullable disable namespace Microsoft.Build.Execution { using Utilities = Microsoft.Build.Internal.Utilities; /// /// Enum for controlling project instance creation /// [Flags] [SuppressMessage("Microsoft.Usage", "CA2217:DoNotMarkEnumsWithFlags", Justification = "ImmutableWithFastItemLookup is a variation on Immutable")] public enum ProjectInstanceSettings { /// /// no options /// None = 0x0, /// /// create immutable version of project instance /// Immutable = 0x1, /// /// create project instance with some look up table that improves performance /// ImmutableWithFastItemLookup = Immutable | 0x2 } /// /// What the user gets when they clone off a ProjectInstance. /// They can hold onto this, change/query items and properties, /// and call it several times to build it. /// /// /// Neither this class nor any of its constituents are allowed to have /// references to any of the Construction or Evaluation objects. /// This class is immutable except for adding instance items and setting instance properties. /// It only exposes items and properties: targets, host services, and the task registry are not exposed as they are only the concern of build. /// Constructors are internal in order to direct users to Project class instead; these are only createable via Project objects. /// [DebuggerDisplay(@"{FullPath} #Targets={TargetsCount} DefaultTargets={(DefaultTargets == null) ? System.String.Empty : System.String.Join("";"", DefaultTargets.ToArray())} ToolsVersion={Toolset.ToolsVersion} InitialTargets={(InitialTargets == null) ? System.String.Empty : System.String.Join("";"", InitialTargets.ToArray())} #GlobalProperties={GlobalProperties.Count} #Properties={Properties.Count} #ItemTypes={ItemTypes.Count} #Items={Items.Count}")] public class ProjectInstance : IPropertyProvider, IItemProvider, IEvaluatorData, ITranslatable { /// /// Targets in the project after overrides have been resolved. /// This is an unordered collection keyed by target name. /// Only the wrapper around this collection is exposed. /// private RetrievableEntryHashSet _actualTargets; /// /// Targets in the project after overrides have been resolved. /// This is an immutable, unordered collection keyed by target name. /// It is just a wrapper around actualTargets. /// private IDictionary _targets; private List _defaultTargets; private List _initialTargets; private IList _importPaths; private IList _importPathsIncludingDuplicates; /// /// The global properties evaluation occurred with. /// Needed by the build as they traverse between projects. /// private PropertyDictionary _globalProperties; /// /// List of names of the properties that, while global, are still treated as overridable /// private ISet _globalPropertiesToTreatAsLocal; /// /// Whether the tools version used originated from an explicit specification, /// for example from an MSBuild task or /tv switch. /// private bool _explicitToolsVersionSpecified; /// /// Properties in the project. This is a dictionary of name, value pairs. /// private PropertyDictionary _properties; /// /// Properties originating from environment variables, gotten from the project collection /// private PropertyDictionary _environmentVariableProperties; /// /// Properties originating from SDK resolution-reported environment variables /// private PropertyDictionary _sdkResolvedEnvironmentVariableProperties; /// /// Items in the project. This is a dictionary of ordered lists of a single type of items keyed by item type. /// private IItemDictionary _items; /// /// Items organized by evaluatedInclude value /// private IMultiDictionary _itemsByEvaluatedInclude; /// /// The project's root directory, for evaluation of relative paths and /// setting the current directory during build. /// Is never null. /// If the project has not been loaded from disk and has not been given a path, returns the current directory from /// the time the project was loaded - this is the same behavior as Whidbey/Orcas. /// If the project has not been loaded from disk but has been given a path, this path may not exist. /// private string _directory; /// /// The project file location, for logging. /// If the project has not been loaded from disk and has not been given a path, returns null. /// If the project has not been loaded from disk but has been given a path, this path may not exist. /// private ElementLocation _projectFileLocation; /// /// The item definitions from the parent Project. /// private IRetrievableEntryHashSet _itemDefinitions; /// /// The HostServices to use during a build. /// private HostServices _hostServices; /// /// Whether when we read a ToolsVersion that is not equivalent to the current one on the Project tag, we /// treat it as the current one. /// private bool _usingDifferentToolsVersionFromProjectFile; /// /// The toolsversion that was originally on the project's Project root element /// private string _originalProjectToolsVersion; /// /// Whether the instance is immutable. /// The object is always mutable during evaluation. /// private bool _isImmutable; private IDictionary> _beforeTargets; private IDictionary> _afterTargets; private Toolset _toolset; private string _subToolsetVersion; private TaskRegistry _taskRegistry; private bool _translateEntireState; private int _evaluationId = BuildEventContext.InvalidEvaluationId; /// /// How far evaluation proceeded when this instance was produced. Defaults to /// . A partial value means later-pass state /// (items, targets, and so on) was not produced and accessing it will throw. /// private ProjectEvaluationStage _evaluationStage = ProjectEvaluationStage.Full; /// /// The property and item filter used when creating this instance, or null if this is not a filtered copy /// of another ProjectInstance. /// private RequestedProjectState _requestedProjectStateFilter; /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Uses the default project collection. /// /// The name of the project file. /// A new project instance public ProjectInstance(string projectFile) : this(projectFile, null, (string)null) { } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Uses the default project collection. /// /// The name of the project file. /// The global properties to use. /// The tools version. /// A new project instance public ProjectInstance(string projectFile, IDictionary globalProperties, string toolsVersion) : this(projectFile, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection) { } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Global properties may be null. /// Tools version may be null. /// /// The name of the project file. /// The global properties to use. /// The tools version. /// Project collection /// A new project instance public ProjectInstance(string projectFile, IDictionary globalProperties, string toolsVersion, ProjectCollection projectCollection) : this(projectFile, globalProperties, toolsVersion, null /* no sub-toolset version */, projectCollection) { } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Global properties may be null. /// Tools version may be null. /// /// The name of the project file. /// The global properties to use. /// The tools version. /// The sub-toolset version, used in tandem with the ToolsVersion to determine the set of toolset properties. /// Project collection /// A new project instance public ProjectInstance(string projectFile, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection) : this(projectFile, globalProperties, toolsVersion, subToolsetVersion, projectCollection, projectLoadSettings: null, evaluationContext: null, directoryCacheFactory: null, interactive: false) { } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// /// The path to the project file. /// The global properties to use. /// The tools version. May be . /// The sub-toolset version, used in tandem with to determine the set of toolset properties. May be . /// Project collection /// Context to evaluate inside, potentially sharing caches with other evaluations. /// Indicates if loading the project is allowed to interact with the user. internal ProjectInstance(string projectFile, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, EvaluationContext context, bool interactive = false) : this(projectFile, globalProperties, toolsVersion, subToolsetVersion, projectCollection, projectLoadSettings: null, evaluationContext: context, directoryCacheFactory: null, interactive: interactive) { } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Global properties may be null. /// Tools version may be null. /// Evaluation context may be null. /// /// The name of the project file. /// The global properties to use. /// The tools version. /// The sub-toolset version, used in tandem with the ToolsVersion to determine the set of toolset properties. /// Project collection /// Project load settings /// The context to use for evaluation. /// The directory cache factory to use for file I/O. /// Indicates if loading the project is allowed to interact with the user. /// The stage after which to stop evaluation. /// A new project instance private ProjectInstance(string projectFile, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { ArgumentException.ThrowIfNullOrEmpty(projectFile); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion)); // We do not control the current directory at this point, but assume that if we were // passed a relative path, the caller assumes we will prepend the current directory. projectFile = FileUtilities.NormalizePath(projectFile); BuildParameters buildParameters = new BuildParameters(projectCollection) { Interactive = interactive }; BuildEventContext buildEventContext = new BuildEventContext(buildParameters.NodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile, globalProperties, toolsVersion, buildParameters.ProjectRootElementCache, true /*Explicitly Loaded*/); Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version provided */, buildParameters, projectCollection.LoggingService, buildEventContext, projectLoadSettings: projectLoadSettings, evaluationContext: evaluationContext, directoryCacheFactory: directoryCacheFactory, evaluationStage: evaluationStage); } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Uses the default project collection. /// /// The project root element /// A new project instance public ProjectInstance(ProjectRootElement xml) : this(xml, null, null, ProjectCollection.GlobalProjectCollection) { } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Global properties may be null. /// Tools version may be null. /// /// The project root element /// The global properties to use. /// The tools version. /// Project collection /// A new project instance public ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, ProjectCollection projectCollection) : this(xml, globalProperties, toolsVersion, null, projectCollection) { } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Global properties may be null. /// Tools version may be null. /// Sub-toolset version may be null, but if specified will override all other methods of determining the sub-toolset. /// /// The project root element /// The global properties to use. /// The tools version. /// The sub-toolset version, used in tandem with the ToolsVersion to determine the set of toolset properties. /// Project collection /// A new project instance public ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection) : this(xml, globalProperties, toolsVersion, subToolsetVersion, projectCollection, projectLoadSettings: null, evaluationContext: null, directoryCacheFactory: null, interactive: false) { } /// /// Creates a ProjectInstance from an external created . /// Properties and items are cloned immediately and only the instance data is stored. /// public ProjectInstance(Project project, ProjectInstanceSettings settings) { Assumed.NotNull(project); var projectPath = project.FullPath; _directory = Path.GetDirectoryName(projectPath); _projectFileLocation = ElementLocation.Create(projectPath); _hostServices = project.ProjectCollection.HostServices; EvaluationId = project.EvaluationCounter; var immutable = (settings & ProjectInstanceSettings.Immutable) == ProjectInstanceSettings.Immutable; this.CreatePropertiesSnapshot(project.Properties, immutable); this.CreateItemDefinitionsSnapshot(project.ItemDefinitions); var keepEvaluationCache = (settings & ProjectInstanceSettings.ImmutableWithFastItemLookup) == ProjectInstanceSettings.ImmutableWithFastItemLookup; var projectItemToInstanceMap = this.CreateItemsSnapshot(project.Items, project.ItemTypes.Count, keepEvaluationCache); this.CreateEvaluatedIncludeSnapshotIfRequested(keepEvaluationCache, project.Items, projectItemToInstanceMap); _globalProperties = new PropertyDictionary(project.GlobalPropertiesCount); foreach (var property in project.GlobalPropertiesEnumerable) { _globalProperties.Set(ProjectPropertyInstance.Create(property.Key, property.Value)); } this.CreateEnvironmentVariablePropertiesSnapshot(project.ProjectCollection.EnvironmentProperties); this.CreateTargetsSnapshot(project.Targets, null, null, null, null); this.CreateImportsSnapshot(project.Imports, project.ImportsIncludingDuplicates); this.Toolset = project.ProjectCollection.GetToolset(project.ToolsVersion); this.SubToolsetVersion = project.SubToolsetVersion; this.TaskRegistry = new TaskRegistry(Toolset, project.ProjectCollection.ProjectRootElementCache); this.ProjectRootElementCache = project.ProjectCollection.ProjectRootElementCache; this.EvaluatedItemElements = new List(); _usingDifferentToolsVersionFromProjectFile = false; _originalProjectToolsVersion = project.ToolsVersion; _explicitToolsVersionSpecified = project.SubToolsetVersion != null; _isImmutable = immutable; } /// /// Creates a ProjectInstance from an immutable . /// The resulting object wraps the /// object. Unlike the ProjectInstance(Project project, ProjectInstanceSettings settings) /// constructor, the properties and items are not cloned. /// /// The immutable . /// Whether the fast item lookup cache is required. private ProjectInstance(Project linkedProject, bool fastItemLookupNeeded) { Assumed.NotNull(linkedProject); var projectPath = linkedProject.FullPath; _directory = Path.GetDirectoryName(projectPath); _projectFileLocation = ElementLocation.Create(projectPath); _hostServices = linkedProject.ProjectCollection.HostServices; _isImmutable = true; EvaluationId = linkedProject.EvaluationCounter; // ProjectProperties _properties = GetImmutablePropertyDictionaryFromImmutableProject(linkedProject); // ProjectItemDefinitions _itemDefinitions = GetImmutableItemDefinitionsHashSetFromImmutableProject(linkedProject); // ProjectItems _items = GetImmutableItemsDictionaryFromImmutableProject(linkedProject, this); // ItemsByEvaluatedInclude if (fastItemLookupNeeded) { _itemsByEvaluatedInclude = new ImmutableLinkedMultiDictionaryConverter( linkedProject.GetItemsByEvaluatedInclude, item => ConvertCachedProjectItemToInstance(linkedProject, this, item)); } // GlobalProperties var globalPropertiesRetrievableHashSet = new ImmutableGlobalPropertiesCollectionConverter(linkedProject.GlobalProperties, _properties); _globalProperties = new PropertyDictionary(globalPropertiesRetrievableHashSet); // EnvironmentVariableProperties _environmentVariableProperties = linkedProject.ProjectCollection.SharedReadOnlyEnvironmentProperties; // Targets _targets = linkedProject.Targets; InitializeTargetsData(null, null, null, null); // Imports var lazyImportsList = new LazyStringValuedList(linkedProject.Link, GetImportFullPaths); _importPaths = lazyImportsList; ImportPaths = lazyImportsList; lazyImportsList = new LazyStringValuedList(linkedProject.Link, GetImportFullPathsIncludingDuplicates); _importPathsIncludingDuplicates = lazyImportsList; ImportPathsIncludingDuplicates = lazyImportsList; Toolset = string.IsNullOrEmpty(linkedProject.ToolsVersion) ? null : linkedProject.ProjectCollection.GetToolset(linkedProject.ToolsVersion); SubToolsetVersion = linkedProject.SubToolsetVersion; TaskRegistry = Toolset is null ? new TaskRegistry(linkedProject.ProjectCollection.ProjectRootElementCache) : new TaskRegistry(Toolset, linkedProject.ProjectCollection.ProjectRootElementCache); ProjectRootElementCache = linkedProject.ProjectCollection.ProjectRootElementCache; EvaluatedItemElements = new List(); _usingDifferentToolsVersionFromProjectFile = false; _originalProjectToolsVersion = linkedProject.ToolsVersion; _explicitToolsVersionSpecified = linkedProject.SubToolsetVersion != null; _isImmutable = true; static List GetImportFullPaths(ObjectModelRemoting.ProjectLink projectLink) { // Imports collection contains ResolvedImports, which are structures which are much bigger than the string list. // We convert to the string list and let Imports itself to BE GCed. var imports = projectLink.Imports; var paths = new List(imports.Count); foreach (var import in imports) { if (import.ImportedProject != null) { paths.Add(import.ImportedProject.FullPath); } } return paths; } static List GetImportFullPathsIncludingDuplicates(ObjectModelRemoting.ProjectLink projectLink) { var imports = projectLink.ImportsIncludingDuplicates; var paths = new List(imports.Count); foreach (var import in imports) { if (import.ImportedProject != null) { paths.Add(import.ImportedProject.FullPath); } } return paths; } } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Global properties may be null. /// Tools version may be null. /// Sub-toolset version may be null, but if specified will override all other methods of determining the sub-toolset. /// /// The project root element /// The global properties to use. /// The tools version. /// The sub-toolset version, used in tandem with the ToolsVersion to determine the set of toolset properties. /// Project collection /// Project load settings /// The context to use for evaluation. /// The directory cache factory to use for file I/O. /// Indicates if loading the project is allowed to interact with the user. /// The stage after which to stop evaluation. /// A new project instance private ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { BuildEventContext buildEventContext = new BuildEventContext(0, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); BuildParameters buildParameters = new BuildParameters(projectCollection) { Interactive = interactive }; Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version specified */, buildParameters, projectCollection.LoggingService, buildEventContext, projectLoadSettings: projectLoadSettings, evaluationContext: evaluationContext, directoryCacheFactory: directoryCacheFactory, evaluationStage: evaluationStage); } /// /// Creates a ProjectInstance directly. Used to generate solution metaprojects. /// /// The full path to give to this project. /// The traversal project from which global properties and tools version will be inherited. /// An containing global properties. internal ProjectInstance(string projectFile, ProjectInstance projectToInheritFrom, IDictionary globalProperties) { _projectFileLocation = ElementLocation.Create(projectFile); _globalProperties = new PropertyDictionary(globalProperties.Count); this.Toolset = projectToInheritFrom.Toolset; this.SubToolsetVersion = projectToInheritFrom.SubToolsetVersion; _explicitToolsVersionSpecified = projectToInheritFrom._explicitToolsVersionSpecified; _properties = new PropertyDictionary(projectToInheritFrom._properties); // This brings along the reserved properties, which are important. _items = new ItemDictionary(); // We don't want any of the items. That would include things like ProjectReferences, which would just pollute our own. _actualTargets = new RetrievableEntryHashSet(StringComparer.OrdinalIgnoreCase); _targets = new ObjectModel.ReadOnlyDictionary(_actualTargets); _environmentVariableProperties = projectToInheritFrom._environmentVariableProperties; _sdkResolvedEnvironmentVariableProperties = projectToInheritFrom._sdkResolvedEnvironmentVariableProperties; _itemDefinitions = new RetrievableEntryHashSet(projectToInheritFrom._itemDefinitions, MSBuildNameIgnoreCaseComparer.Default); _hostServices = projectToInheritFrom._hostServices; this.ProjectRootElementCache = projectToInheritFrom.ProjectRootElementCache; _explicitToolsVersionSpecified = projectToInheritFrom._explicitToolsVersionSpecified; this.InitialTargets = new List(); this.DefaultTargets = new List(); this.DefaultTargets.Add("Build"); this.TaskRegistry = projectToInheritFrom.TaskRegistry; _isImmutable = projectToInheritFrom._isImmutable; _importPaths = projectToInheritFrom._importPaths; ImportPaths = new ObjectModel.ReadOnlyCollection(_importPaths); _importPathsIncludingDuplicates = projectToInheritFrom._importPathsIncludingDuplicates; ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection(_importPathsIncludingDuplicates); this.EvaluatedItemElements = new List(); IEvaluatorData thisAsIEvaluatorData = this; thisAsIEvaluatorData.AfterTargets = new Dictionary>(); thisAsIEvaluatorData.BeforeTargets = new Dictionary>(); foreach (KeyValuePair property in globalProperties) { _globalProperties[property.Key] = ProjectPropertyInstance.Create(property.Key, property.Value, false /* may not be reserved */, _isImmutable); } } /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Global properties may be null. /// Tools version may be null. /// Used by SolutionProjectGenerator so that it can explicitly pass the vsVersionFromSolution in for use in /// determining the sub-toolset version. /// /// The project root element /// The global properties to use. /// The tools version. /// The version of the solution, used to help determine which sub-toolset to use. /// Project collection /// An instance to use when resolving SDKs. /// The current build submission ID. /// A new project instance internal ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, int visualStudioVersionFromSolution, ProjectCollection projectCollection, ISdkResolverService sdkResolverService, int submissionId) { BuildEventContext buildEventContext = new BuildEventContext(0, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); Initialize(xml, globalProperties, toolsVersion, null, visualStudioVersionFromSolution, new BuildParameters(projectCollection), projectCollection.LoggingService, buildEventContext, sdkResolverService, submissionId); } /// /// Initializes a new instance of the class directly. /// No intermediate Project object is created. /// This is ideal if the project is simply going to be built, and not displayed or edited. /// Global properties may be null. /// Tools version may be null. /// Used by SolutionProjectGenerator so that it can explicitly pass the vsVersionFromSolution in for use in /// determining the sub-toolset version. /// internal ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, ILoggingService loggingService, int visualStudioVersionFromSolution, ProjectCollection projectCollection, ISdkResolverService sdkResolverService, int submissionId) { BuildEventContext buildEventContext = new BuildEventContext(submissionId, 0, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); Initialize(xml, globalProperties, toolsVersion, null, visualStudioVersionFromSolution, new BuildParameters(projectCollection), loggingService, buildEventContext, sdkResolverService, submissionId); } /// /// Creates a mutable ProjectInstance directly, using the specified logging service. /// Assumes the project path is already normalized. /// Used by the RequestBuilder. /// internal ProjectInstance(string projectFile, IDictionary globalProperties, string toolsVersion, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext buildEventContext, ISdkResolverService sdkResolverService, int submissionId, ProjectLoadSettings? projectLoadSettings) { ArgumentException.ThrowIfNullOrEmpty(projectFile); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion)); ArgumentNullException.ThrowIfNull(buildParameters); ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile, globalProperties, toolsVersion, buildParameters.ProjectRootElementCache, false /*Not explicitly loaded*/); Initialize(xml, globalProperties, toolsVersion, null, 0 /* no solution version specified */, buildParameters, loggingService, buildEventContext, sdkResolverService, submissionId, projectLoadSettings); } /// /// Creates a mutable ProjectInstance directly, using the specified logging service. /// Assumes the project path is already normalized. /// Used by this class when generating legacy solution wrappers. /// internal ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext buildEventContext, ISdkResolverService sdkResolverService, int submissionId) { ArgumentNullException.ThrowIfNull(xml); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion)); ArgumentNullException.ThrowIfNull(buildParameters); Initialize(xml, globalProperties, toolsVersion, null, 0 /* no solution version specified */, buildParameters, loggingService, buildEventContext, sdkResolverService, submissionId); } /// /// Constructor called by Project's constructor to create a fresh instance. /// Properties and items are cloned immediately and only the instance data is stored. /// internal ProjectInstance(Evaluation.Project.Data data, string directory, string fullPath, HostServices hostServices, PropertyDictionary environmentVariableProperties, ProjectInstanceSettings settings) { Assumed.NotNull(data); Assumed.NotNullOrEmpty(directory); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(fullPath, nameof(fullPath)); _directory = directory; _projectFileLocation = ElementLocation.Create(fullPath); _hostServices = hostServices; EvaluationId = data.EvaluationId; var immutable = (settings & ProjectInstanceSettings.Immutable) == ProjectInstanceSettings.Immutable; this.CreatePropertiesSnapshot(new ReadOnlyCollection(data.Properties), immutable); this.CreateItemDefinitionsSnapshot(data.ItemDefinitions); var keepEvaluationCache = (settings & ProjectInstanceSettings.ImmutableWithFastItemLookup) == ProjectInstanceSettings.ImmutableWithFastItemLookup; var projectItemToInstanceMap = this.CreateItemsSnapshot(new ReadOnlyCollection(data.Items), data.ItemTypes.Count, keepEvaluationCache); this.CreateEvaluatedIncludeSnapshotIfRequested(keepEvaluationCache, new ReadOnlyCollection(data.Items), projectItemToInstanceMap); this.CreateGlobalPropertiesSnapshot(data.GlobalPropertiesDictionary); this.CreateEnvironmentVariablePropertiesSnapshot(environmentVariableProperties); this.CreateSdkResolvedEnvironmentVariablePropertiesSnapshot(data.SdkResolvedEnvironmentVariablePropertiesDictionary); this.CreateTargetsSnapshot(data.Targets, data.DefaultTargets, data.InitialTargets, data.BeforeTargets, data.AfterTargets); this.CreateImportsSnapshot(data.ImportClosure, data.ImportClosureWithDuplicates); // Toolset and task registry are logically immutable after creation, and shareable by project instances // with same evaluation (global/local properties) - which is guaranteed here (the passed in data is recreated on evaluation if needed) this.Toolset = data.Toolset; this.SubToolsetVersion = data.SubToolsetVersion; this.TaskRegistry = data.TaskRegistry; this.ProjectRootElementCache = data.Project.ProjectCollection.ProjectRootElementCache; this.EvaluatedItemElements = new List(data.EvaluatedItemElements); _usingDifferentToolsVersionFromProjectFile = data.UsingDifferentToolsVersionFromProjectFile; _originalProjectToolsVersion = data.OriginalProjectToolsVersion; _explicitToolsVersionSpecified = data.ExplicitToolsVersion != null; _isImmutable = immutable; } private void CreateSdkResolvedEnvironmentVariablePropertiesSnapshot(PropertyDictionary sdkResolvedEnvironmentVariablePropertiesDictionary) { _sdkResolvedEnvironmentVariableProperties = new PropertyDictionary(sdkResolvedEnvironmentVariablePropertiesDictionary.Count); foreach (ProjectPropertyInstance environmentProperty in sdkResolvedEnvironmentVariablePropertiesDictionary) { _sdkResolvedEnvironmentVariableProperties.Set(environmentProperty.DeepClone()); } } /// /// Constructor for deserialization. /// private ProjectInstance(ITranslator translator) { ((ITranslatable)this).Translate(translator); } /// /// Deep clone of this object. /// Useful for compiling a single file; or for keeping resolved assembly references between builds. /// private ProjectInstance(ProjectInstance that, bool isImmutable, RequestedProjectState filter = null) { Assumed.True(filter == null || isImmutable, "The result of a filtered ProjectInstance clone must be immutable."); _directory = that._directory; _projectFileLocation = that._projectFileLocation; _hostServices = that._hostServices; _isImmutable = isImmutable; _evaluationId = that.EvaluationId; _translateEntireState = that._translateEntireState; _requestedProjectStateFilter = filter?.DeepClone(); if (filter == null) { _properties = new PropertyDictionary(that._properties.Count); foreach (ProjectPropertyInstance property in that.Properties) { _properties.Set(property.DeepClone(_isImmutable)); } _items = new ItemDictionary(that._items.Count); foreach (ProjectItemInstance item in that.Items) { _items.Add(item.DeepClone(this)); } _globalProperties = new PropertyDictionary(that._globalProperties.Count); foreach (ProjectPropertyInstance globalProperty in that.GlobalPropertiesDictionary) { _globalProperties.Set(globalProperty.DeepClone(_isImmutable)); } _environmentVariableProperties = new PropertyDictionary(that._environmentVariableProperties.Count); foreach (ProjectPropertyInstance environmentProperty in that._environmentVariableProperties) { _environmentVariableProperties.Set(environmentProperty.DeepClone(_isImmutable)); } if (that._sdkResolvedEnvironmentVariableProperties is PropertyDictionary thatEnvProps) { _sdkResolvedEnvironmentVariableProperties = new(thatEnvProps.Count); foreach (ProjectPropertyInstance sdkResolvedEnvironmentVariable in thatEnvProps) { _sdkResolvedEnvironmentVariableProperties.Set(sdkResolvedEnvironmentVariable.DeepClone(_isImmutable)); } } this.DefaultTargets = new List(that.DefaultTargets); this.InitialTargets = new List(that.InitialTargets); ((IEvaluatorData)this).BeforeTargets = CreateCloneDictionary( ((IEvaluatorData)that).BeforeTargets, StringComparer.OrdinalIgnoreCase); ((IEvaluatorData)this).AfterTargets = CreateCloneDictionary( ((IEvaluatorData)that).AfterTargets, StringComparer.OrdinalIgnoreCase); // These are immutable (or logically immutable after creation) so we don't need to clone them: this.TaskRegistry = that.TaskRegistry; this.Toolset = that.Toolset; this.SubToolsetVersion = that.SubToolsetVersion; _targets = that._targets; _itemDefinitions = that._itemDefinitions; _explicitToolsVersionSpecified = that._explicitToolsVersionSpecified; _importPaths = that._importPaths; ImportPaths = new ObjectModel.ReadOnlyCollection(_importPaths); _importPathsIncludingDuplicates = that._importPathsIncludingDuplicates; ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection(_importPathsIncludingDuplicates); this.EvaluatedItemElements = that.EvaluatedItemElements; this.ProjectRootElementCache = that.ProjectRootElementCache; } else { if (filter.PropertyFilters != null) { // If PropertyFilters is defined, filter all types of property to contain // only those explicitly specified. // Reserve space assuming all specified properties exist. _properties = new PropertyDictionary(filter.PropertyFilters.Count); _globalProperties = new PropertyDictionary(filter.PropertyFilters.Count); _environmentVariableProperties = new PropertyDictionary(filter.PropertyFilters.Count); _sdkResolvedEnvironmentVariableProperties = new PropertyDictionary(filter.PropertyFilters.Count); // Filter each type of property. foreach (var desiredProperty in filter.PropertyFilters) { var regularProperty = that.GetProperty(desiredProperty); if (regularProperty != null) { _properties.Set(regularProperty.DeepClone(isImmutable: true)); } var globalProperty = that.GetProperty(desiredProperty); if (globalProperty != null) { _globalProperties.Set(globalProperty.DeepClone(isImmutable: true)); } var environmentProperty = that._environmentVariableProperties?.GetProperty(desiredProperty); if (environmentProperty != null) { _environmentVariableProperties.Set(environmentProperty.DeepClone(isImmutable: true)); } var sdkResolvedEnvironmentProperty = that._sdkResolvedEnvironmentVariableProperties?.GetProperty(desiredProperty); if (sdkResolvedEnvironmentProperty != null) { _sdkResolvedEnvironmentVariableProperties.Set(sdkResolvedEnvironmentProperty.DeepClone(isImmutable: true)); } } } if (filter.ItemFilters != null) { // If ItemFilters is defined, filter items down to the list // specified, optionally also filtering metadata. // Temporarily allow editing items to remove metadata that // wasn't explicitly asked for. _isImmutable = false; _items = new ItemDictionary(that.Items.Count); foreach (var itemFilter in filter.ItemFilters) { foreach (var actualItem in that.GetItems(itemFilter.Key)) { var filteredItem = actualItem.DeepClone(this); if (itemFilter.Value == null) { // No specified list of metadata names, so include all metadata. // The returned list of items is still filtered by item name. } else { // Include only the explicitly-asked-for metadata by removing // any extant metadata. // UNDONE: This could be achieved at lower GC cost by applying // the metadata filter at DeepClone time above. foreach (var metadataName in filteredItem.EnumerableMetadataNames) { if (!itemFilter.Value.Contains(metadataName, StringComparer.OrdinalIgnoreCase)) { filteredItem.RemoveMetadata(metadataName); } } } _items.Add(filteredItem); } } // Restore immutability after editing newly cloned items. _isImmutable = isImmutable; // A filtered result is not useful for building anyway; ensure that // it has minimal IPC wire cost. _translateEntireState = false; } } } /// /// Create a file based ProjectInstance. /// /// The file to evaluate the ProjectInstance from. /// The to use. /// public static ProjectInstance FromFile(string file, ProjectOptions options) { return new ProjectInstance( file, options.GlobalProperties, options.ToolsVersion, options.SubToolsetVersion, options.ProjectCollection ?? ProjectCollection.GlobalProjectCollection, options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, options.Interactive, options.EvaluationStage); } /// /// Create a based ProjectInstance. /// /// The to evaluate the ProjectInstance from. /// The to use. public static ProjectInstance FromProjectRootElement(ProjectRootElement rootElement, ProjectOptions options) { return new ProjectInstance( rootElement, options.GlobalProperties, options.ToolsVersion, options.SubToolsetVersion, options.ProjectCollection ?? ProjectCollection.GlobalProjectCollection, options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, options.Interactive, options.EvaluationStage); } /// /// Create a ProjectInstance from an immutable project source. /// /// The immutable on which the ProjectInstance is based. /// The to use. public static ProjectInstance FromImmutableProjectSource(Project project, ProjectInstanceSettings settings) { bool fastItemLookupNeeded = settings.HasFlag(ProjectInstanceSettings.ImmutableWithFastItemLookup); return new ProjectInstance(project, fastItemLookupNeeded); } private static IRetrievableEntryHashSet GetImmutableItemDefinitionsHashSetFromImmutableProject(Project linkedProject) { IDictionary linkedProjectItemDefinitions = linkedProject.ItemDefinitions; VerifyCollectionImplementsRequiredDictionaryInterfaces( linkedProjectItemDefinitions, out IDictionary elementsDictionary, out IDictionary<(string, int, int), ProjectItemDefinition> constrainedElementsDictionary); var hashSet = new ImmutableElementCollectionConverter( elementsDictionary, constrainedElementsDictionary, ConvertCachedItemDefinitionToInstance); return hashSet; } private static ImmutableItemDictionary GetImmutableItemsDictionaryFromImmutableProject( Project linkedProject, ProjectInstance owningProjectInstance) { var itemsByType = linkedProject.Items as IDictionary>; if (itemsByType == null) { throw new ArgumentException(nameof(linkedProject)); } Func convertCachedItemToInstance = projectItem => ConvertCachedProjectItemToInstance(linkedProject, owningProjectInstance, projectItem); var itemDictionary = new ImmutableItemDictionary( linkedProject.Items, itemsByType, convertCachedItemToInstance, projectItemInstance => projectItemInstance.ItemType); return itemDictionary; } private static ProjectItemInstance ConvertCachedProjectItemToInstance( Project linkedProject, ProjectInstance owningProjectInstance, ProjectItem projectItem) { ProjectItemInstance result = null; if (projectItem is IImmutableInstanceProvider instanceProvider) { result = instanceProvider.ImmutableInstance; if (result == null) { var newInstance = InstantiateProjectItemInstanceFromImmutableProjectSource( linkedProject, owningProjectInstance, projectItem); result = instanceProvider.GetOrSetImmutableInstance(newInstance); } } return result; } private static ProjectItemDefinitionInstance ConvertCachedItemDefinitionToInstance(ProjectItemDefinition projectItemDefinition) { ProjectItemDefinitionInstance result = null; if (projectItemDefinition is IImmutableInstanceProvider instanceProvider) { result = instanceProvider.ImmutableInstance; if (result == null) { ImmutableDictionary metadata = null; if (projectItemDefinition.Metadata is IDictionary linkedMetadataDict) { IEnumerable> projectMetadataInstances = linkedMetadataDict.Select(directMetadatum => new KeyValuePair(directMetadatum.Key, directMetadatum.Value.EvaluatedValueEscaped)); metadata = ImmutableDictionaryExtensions.EmptyMetadata .SetItems(projectMetadataInstances, ProjectMetadataInstance.VerifyThrowReservedName); } result = instanceProvider.GetOrSetImmutableInstance( new ProjectItemDefinitionInstance(projectItemDefinition.ItemType, metadata)); } } return result; } private static PropertyDictionary GetImmutablePropertyDictionaryFromImmutableProject(Project linkedProject) { ICollection linkedProjectProperties = linkedProject.Properties; VerifyCollectionImplementsRequiredDictionaryInterfaces( linkedProjectProperties, out IDictionary elementsDictionary, out IDictionary<(string, int, int), ProjectProperty> constrainedElementsDictionary); var hashSet = new ImmutableProjectPropertyCollectionConverter( linkedProject, elementsDictionary, constrainedElementsDictionary, ConvertCachedPropertyToInstance); return new PropertyDictionary(hashSet); } private static ProjectPropertyInstance ConvertCachedPropertyToInstance(ProjectProperty property) { ProjectPropertyInstance result = null; if (property is IImmutableInstanceProvider instanceProvider) { result = instanceProvider.ImmutableInstance; if (result == null) { result = instanceProvider.GetOrSetImmutableInstance(InstantiateProjectPropertyInstance(property, isImmutable: true)); } } return result; } private static void VerifyCollectionImplementsRequiredDictionaryInterfaces( object elementsCollection, out IDictionary elementsDictionary, out IDictionary<(string, int, int), TCached> constrainedElementsDictionary) { // The elementsCollection we receive here is implemented in CPS as a special collection // that is both IDictionary and also IDictionary<(string, int, int), TCached>. // This allows it to represent the fundamental operations of an IRetrievableEntryHashSet. // The IDictionary<(string, int, int), TCached> interface is used to handle the // IRetrievableEntryHashSet's Get(string key, int index, int length) method. Here we take // elementsCollection and put it into an ImmutableElementCollectionConverter, which // represents the elementsCollection as an IRetrievableEntryHashSet. // That IRetrievableEntryHashSet is then used either directly or as a backing source for // another collection wrapper (e.g. PropertyDictionary). if (elementsCollection is not IDictionary elementsDict || elementsCollection is not IDictionary<(string, int, int), TCached> constrainedElementsDict) { throw new ArgumentException(nameof(elementsCollection)); } elementsDictionary = elementsDict; constrainedElementsDictionary = constrainedElementsDict; } /// /// Global properties this project was evaluated with, if any. /// Read only collection. /// Traverses project references. /// /// /// This is the publicly exposed getter, that translates into a read-only dead IDictionary<string, string>. /// public IDictionary GlobalProperties { [DebuggerStepThrough] get { if (_globalProperties == null /* cached */ || _globalProperties.Count == 0) { return ReadOnlyEmptyDictionary.Instance; } return _globalProperties.ToReadOnlyDictionary(); } } /// /// The tools version this project was evaluated with, if any. /// Not necessarily the same as the tools version on the Project tag, if any; /// it may have been externally specified, for example with a /tv switch. /// public string ToolsVersion { get { return Toolset.ToolsVersion; } } /// /// Enumerator over item types of the items in this project /// public ICollection ItemTypes { [DebuggerStepThrough] get { // KeyCollection, which is already read-only return _items.ItemTypes; } } bool IEvaluatorData.CanEvaluateElementsWithFalseConditions => false; /// /// Enumerator over properties in this project /// public ICollection Properties { [DebuggerStepThrough] get { return (_properties == null) ? (ICollection)ReadOnlyEmptyCollection.Instance : new ReadOnlyCollection(_properties); } } /// /// Enumerator over items in this project. /// [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This is a reasonable choice. API review approved")] public ICollection Items { [DebuggerStepThrough] get { VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(Items)); return (_items == null) ? (ICollection)ReadOnlyEmptyCollection.Instance : new ReadOnlyCollection(_items); } } /// /// Gets a object containing evaluated items. /// public List EvaluatedItemElements { get; private set; } /// /// Serialize the entire project instance state. /// /// When false, only a part of the project instance state is serialized (properties and items). /// In this case out of proc nodes re-evaluate the project instance from disk to obtain the un-serialized state. /// This partial state recombination may lead to build issues when the project instance state differs from what is on disk. /// public bool TranslateEntireState { get => _translateEntireState; set => _translateEntireState = value; } /// /// The ID of the evaluation that produced this ProjectInstance. /// /// See . /// public int EvaluationId { get { return _evaluationId; } set { _evaluationId = value; } } /// /// How far evaluation proceeded when this instance was produced. /// When this is not , the instance is the result of a /// partial evaluation and members exposing state from later passes (for example items or targets) /// throw . /// public ProjectEvaluationStage EvaluationStage { get { return _evaluationStage; } } /// /// Throws if this instance was produced by a partial /// evaluation that stopped before , meaning the requested /// member's state was never computed. /// private void VerifyThrowEvaluationStageReached(ProjectEvaluationStage requiredStage, string memberName) { if (_evaluationStage < requiredStage) { ErrorUtilities.ThrowInvalidOperation("OM_PartialEvaluationMemberUnavailable", memberName, _evaluationStage, requiredStage); } } /// /// The project's root directory, for evaluation of relative paths and /// setting the current directory during build. /// Is never null: projects not loaded from disk use the current directory from /// the time the build started. /// public string Directory { [DebuggerStepThrough] get { return _directory; } } /// /// The full path to the project, for logging. /// If the project was never given a path, returns empty string. /// public string FullPath { [DebuggerStepThrough] get => _projectFileLocation?.File ?? string.Empty; } /// /// Read-only dictionary of item definitions in this project. /// Keyed by item type /// public IDictionary ItemDefinitions { get { VerifyThrowEvaluationStageReached(ProjectEvaluationStage.ItemDefinitions, nameof(ItemDefinitions)); return _itemDefinitions; } } /// /// The full file paths of all the files that during evaluation contributed to this project instance. /// This does not include projects that were never imported because a condition on an Import element was false. /// The outer ProjectRootElement that maps to this project instance itself is not included. /// public IReadOnlyList ImportPaths { get; private set; } /// /// This list will contain duplicate imports if an import is imported multiple times. However, only the first import was used in evaluation. /// public IReadOnlyList ImportPathsIncludingDuplicates { get; private set; } /// /// DefaultTargets specified in the project, or /// the logically first target if no DefaultTargets is /// specified in the project. /// The build builds these if no targets are explicitly specified /// to build. /// public List DefaultTargets { get { VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Full, nameof(DefaultTargets)); return _defaultTargets; } private set { _defaultTargets = value; } } /// /// InitialTargets specified in the project, plus those /// in all imports, gathered depth-first. /// The build runs these before anything else. /// public List InitialTargets { get { return _initialTargets; } private set { _initialTargets = value; } } /// /// Targets in the project. The build process can find one by looking for its name /// in the dictionary. /// This collection is read-only. /// public IDictionary Targets { [DebuggerStepThrough] get { VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Full, nameof(Targets)); return _targets; } } /// /// Whether the instance is immutable. /// This is set permanently when the instance is created. /// public bool IsImmutable { get { return _isImmutable; } } /// /// The property and item filter used when creating this instance, or null if this is not a filtered copy /// of another ProjectInstance. /// internal RequestedProjectState RequestedProjectStateFilter => _requestedProjectStateFilter; /// /// Task classes and locations known to this project. /// This is the project-specific task registry, which is consulted before /// the toolset's task registry. /// Only set during evaluation, so does not check for immutability. /// TaskRegistry IEvaluatorData.TaskRegistry { [DebuggerStepThrough] get { return TaskRegistry; } set { TaskRegistry = value; } } /// /// Gets the Toolset /// Toolset IEvaluatorData.Toolset { [DebuggerStepThrough] get { return Toolset; } } /// /// The sub-toolset version we should use during the build, used to determine which set of sub-toolset /// properties we should merge into this toolset. /// string IEvaluatorData.SubToolsetVersion { [DebuggerStepThrough] get { return SubToolsetVersion; } } /// /// The externally specified tools version, if any. /// For example, the tools version from a /tv switch. /// Not necessarily the same as the tools version from the project tag or of the toolset used. /// May be null. /// Flows through to called projects. /// string IEvaluatorData.ExplicitToolsVersion { [DebuggerStepThrough] get { return ExplicitToolsVersion; } } /// /// Gets the global properties /// PropertyDictionary IEvaluatorData.GlobalPropertiesDictionary { [DebuggerStepThrough] get { return _globalProperties; } } PropertyDictionary IEvaluatorData.EnvironmentVariablePropertiesDictionary { get => _environmentVariableProperties; } PropertyDictionary IEvaluatorData.SdkResolvedEnvironmentVariablePropertiesDictionary { get => _sdkResolvedEnvironmentVariableProperties; } /// /// Adds an Environment Variable that was resolved by an to the set of properties tracked by this . /// /// The name of the environment variable. /// The value of the environment variable. /// /// SDK-resolved environment variables override ambient environment variables, but do not override regular properties defined in XML. /// public void AddSdkResolvedEnvironmentVariable(string name, string value) { ArgumentException.ThrowIfNullOrEmpty(name); ArgumentNullException.ThrowIfNull(value); // If another SDK already set it, we do not overwrite it. if (_sdkResolvedEnvironmentVariableProperties?.Contains(name) == true) { LogIfValueDiffers(_sdkResolvedEnvironmentVariableProperties, name, value, "SdkEnvironmentVariableAlreadySetBySdk"); return; } _sdkResolvedEnvironmentVariableProperties ??= new(); ProjectPropertyInstance.SdkResolvedEnvironmentVariablePropertyInstance property = new(name, value); _sdkResolvedEnvironmentVariableProperties.Set(property); // SDK-resolved environment variables override ambient environment variables. bool overridingAmbient = _environmentVariableProperties.Contains(name); if (overridingAmbient) { // Log before removing so LogIfValueDiffers can compare the old and new values. LogIfValueDiffers(_environmentVariableProperties, name, value, "SdkEnvironmentVariableOverridingAmbient"); _environmentVariableProperties.Remove(name); } // Set the property, overriding ambient environment variables but not regular properties defined in XML (aka if the Property explicitly exists already). // We have to logically 'set' a property here to do the ambient override (marking this set as coming from an env var) but we're not actually 'overridding' // a pre-existing Property. if (overridingAmbient || GetProperty(name) is null) { ((IEvaluatorData)this) .SetProperty(name, value, isGlobalProperty: false, mayBeReserved: false, loggingContext: _loggingContext, isEnvironmentVariable: true, isCommandLineProperty: false); } } /// /// Helper method to log a message if the attempted value differs from the existing value. /// private void LogIfValueDiffers(PropertyDictionary propertyDictionary, string name, string attemptedValue, string messageResourceName) { ProjectPropertyInstance existingProperty = propertyDictionary.GetProperty(name); if (existingProperty != null && !string.Equals(existingProperty.EvaluatedValue, attemptedValue, StringComparison.Ordinal)) { _loggingContext.LogComment(MessageImportance.Low, messageResourceName, name, attemptedValue, existingProperty.EvaluatedValue); } } /// /// List of names of the properties that, while global, are still treated as overridable /// ISet IEvaluatorData.GlobalPropertiesToTreatAsLocal { get { if (_globalPropertiesToTreatAsLocal == null) { _globalPropertiesToTreatAsLocal = new HashSet(MSBuildNameIgnoreCaseComparer.Default); } return _globalPropertiesToTreatAsLocal; } } /// /// Gets the properties /// PropertyDictionary IEvaluatorData.Properties { [DebuggerStepThrough] get { return _properties; } } /// /// Gets the item definitions /// IEnumerable IEvaluatorData.ItemDefinitionsEnumerable { [DebuggerStepThrough] get { return _itemDefinitions.Values; } } /// /// Gets the items /// IItemDictionary IEvaluatorData.Items { [DebuggerStepThrough] get { return _items; } } /// /// Sets the initial targets /// Only set during evaluation, so does not check for immutability. /// List IEvaluatorData.InitialTargets { [DebuggerStepThrough] get { return InitialTargets; } set { InitialTargets = value; } } /// /// Gets or sets the default targets /// Only set during evaluation, so does not check for immutability. /// List IEvaluatorData.DefaultTargets { [DebuggerStepThrough] get { return DefaultTargets; } set { DefaultTargets = value; } } /// /// Gets or sets the before targets /// Only set during evaluation, so does not check for immutability. /// IDictionary> IEvaluatorData.BeforeTargets { get { return _beforeTargets; } set { _beforeTargets = value; } } /// /// Gets or sets the after targets /// Only set during evaluation, so does not check for immutability. /// IDictionary> IEvaluatorData.AfterTargets { get { return _afterTargets; } set { _afterTargets = value; } } /// /// List of possible values for properties inferred from certain conditions, /// keyed by the property name. /// /// /// Because ShouldEvaluateForDesignTime returns false, this should not be called. /// Dictionary> IEvaluatorData.ConditionedProperties => Assumed.Unreachable>>(); /// /// Whether evaluation should collect items ignoring condition, /// as well as items respecting condition; and collect /// conditioned properties, as well as regular properties /// bool IEvaluatorData.ShouldEvaluateForDesignTime { get { return false; } } /// /// Location of the originating file itself, not any specific content within it. /// Never returns null, even if the file has not got a path yet. /// public ElementLocation ProjectFileLocation { get { return _projectFileLocation; } } /// /// Gets the global properties this project was evaluated with, if any. /// Traverses project references. /// internal PropertyDictionary GlobalPropertiesDictionary { [DebuggerStepThrough] get { return _globalProperties; } } /// /// The tools version we should use during the build, used to determine which toolset we should access. /// internal Toolset Toolset { get { return _toolset; } private set { _toolset = value; } } /// /// If we are treating a missing toolset as the current ToolsVersion /// internal bool UsingDifferentToolsVersionFromProjectFile { get { return _usingDifferentToolsVersionFromProjectFile; } } /// /// The toolsversion that was originally specified on the project's root element /// internal string OriginalProjectToolsVersion { get { return _originalProjectToolsVersion; } } /// /// The externally specified tools version, if any. /// For example, the tools version from a /tv switch. /// Not necessarily the same as the tools version from the project tag or of the toolset used. /// May be null. /// Flows through to called projects. /// internal string ExplicitToolsVersion { get { return _explicitToolsVersionSpecified ? Toolset.ToolsVersion : null; } } /// /// Whether the tools version used originated from an explicit specification, /// for example from an MSBuild task or /tv switch. /// internal bool ExplicitToolsVersionSpecified { get { return _explicitToolsVersionSpecified; } } /// /// The sub-toolset version we should use during the build, used to determine which set of sub-toolset /// properties we should merge into this toolset. /// internal string SubToolsetVersion { get { return _subToolsetVersion; } private set { _subToolsetVersion = value; } } /// /// Actual collection of properties in this project, /// for the build to start with. /// internal PropertyDictionary PropertiesToBuildWith { [DebuggerStepThrough] get { return _properties; } } internal ICollection TestEnvironmentalProperties => new ReadOnlyCollection(_environmentVariableProperties); /// /// Actual collection of items in this project, /// for the build to start with. /// internal IItemDictionary ItemsToBuildWith { [DebuggerStepThrough] get { return _items; } } /// /// Task classes and locations known to this project. /// This is the project-specific task registry, which is consulted before /// the toolset's task registry. /// /// /// UsingTask tags have already been evaluated and entered into this task registry. /// internal TaskRegistry TaskRegistry { get { return _taskRegistry; } private set { _taskRegistry = value; } } /// /// Number of targets in the project. /// internal int TargetsCount { get { return _targets.Count; } } /// /// The project root element cache from the project collection /// that began the build. This is a thread-safe object. /// It's held here so it can get passed to the build. /// internal ProjectRootElementCacheBase ProjectRootElementCache { get; private set; } /// /// Returns the evaluated, escaped value of the provided item's include. /// [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IItem is an internal interface; this is less confusing to outside customers. ")] public static string GetEvaluatedItemIncludeEscaped(ProjectItemInstance item) { ArgumentNullException.ThrowIfNull(item); return ((IItem)item).EvaluatedIncludeEscaped; } /// /// Returns the evaluated, escaped value of the provided item definition's include. /// public static string GetEvaluatedItemIncludeEscaped(ProjectItemDefinitionInstance item) { ArgumentNullException.ThrowIfNull(item); return ((IItem)item).EvaluatedIncludeEscaped; } /// /// Gets the escaped value of the provided metadatum. /// public static string GetMetadataValueEscaped(ProjectMetadataInstance metadatum) { ArgumentNullException.ThrowIfNull(metadatum); return metadatum.EvaluatedValueEscaped; } /// /// Gets the escaped value of the metadatum with the provided name on the provided item. /// [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IItem is an internal interface; this is less confusing to outside customers. ")] public static string GetMetadataValueEscaped(ProjectItemInstance item, string name) { ArgumentNullException.ThrowIfNull(item); return ((IItem)item).GetMetadataValueEscaped(name); } /// /// Gets the escaped value of the metadatum with the provided name on the provided item definition. /// public static string GetMetadataValueEscaped(ProjectItemDefinitionInstance item, string name) { ArgumentNullException.ThrowIfNull(item); return ((IItem)item).GetMetadataValueEscaped(name); } /// /// Get the escaped value of the provided property /// [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IProperty is an internal interface; this is less confusing to outside customers. ")] public static string GetPropertyValueEscaped(ProjectPropertyInstance property) { ArgumentNullException.ThrowIfNull(property); return ((IProperty)property).EvaluatedValueEscaped; } /// /// Gets items of the specified type. /// For internal use. /// /// /// Already a readonly collection /// ICollection IItemProvider.GetItems(string itemType) { return _items[itemType]; } /// /// Initializes the object for evaluation. /// Only called during evaluation, so does not check for immutability. /// void IEvaluatorData. InitializeForEvaluation(IToolsetProvider toolsetProvider, EvaluationContext evaluationContext, LoggingContext loggingContext) { // All been done in the constructor. We don't allow re-evaluation of project instances. } /// /// Indicates to the data block that evaluation has completed, /// so for example it can mark datastructures read-only. /// void IEvaluatorData.FinishEvaluation() { // Ideally we would unify targets collections here (they are almost all the same) as Project.FinishEvaluation() does. // However it's trickier as the target collections here are in a few cases mutated: they would have to be copy on write. } /// /// Adds a new item /// Only called during evaluation, so does not check for immutability. /// void IEvaluatorData.AddItem(ProjectItemInstance item) { _items.Add(item); } /// /// Adds a new item to the collection of all items ignoring condition /// /// /// Because ShouldEvaluateForDesignTime returns false, this should not be called. /// void IEvaluatorData.AddItemIgnoringCondition(ProjectItemInstance item) => Assumed.Unreachable(); /// /// Adds a new item definition /// Only called during evaluation, so does not check for immutability. /// IItemDefinition IEvaluatorData.AddItemDefinition(string itemType) { ProjectItemDefinitionInstance itemDefinitionInstance = new ProjectItemDefinitionInstance(itemType); _itemDefinitions.Add(itemDefinitionInstance); return itemDefinitionInstance; } /// /// Properties encountered during evaluation. These are read during the first evaluation pass. /// Unlike those returned by the Properties property, these are ordered, and include any properties that /// were subsequently overridden by others with the same name. It does not include any /// properties whose conditions did not evaluate to true. /// /// /// Because ShouldEvaluateForDesignTime returns false, this should not be called. /// void IEvaluatorData.AddToAllEvaluatedPropertiesList(ProjectPropertyInstance property) => Assumed.Unreachable(); /// /// Item definition metadata encountered during evaluation. These are read during the second evaluation pass. /// Unlike those returned by the ItemDefinitions property, these are ordered, and include any metadata that /// were subsequently overridden by others with the same name and item type. It does not include any /// elements whose conditions did not evaluate to true. /// /// /// Because ShouldEvaluateForDesignTime returns false, this should not be called. /// void IEvaluatorData.AddToAllEvaluatedItemDefinitionMetadataList(ProjectMetadataInstance itemDefinitionMetadatum) => Assumed.Unreachable(); /// /// Items encountered during evaluation. These are read during the third evaluation pass. /// Unlike those returned by the Items property, these are ordered. /// It does not include any elements whose conditions did not evaluate to true. /// It does not include any items added since the last evaluation. /// /// /// Because ShouldEvaluateForDesignTime returns false, this should not be called. /// void IEvaluatorData.AddToAllEvaluatedItemsList(ProjectItemInstance item) => Assumed.Unreachable(); /// /// Retrieves an existing item definition, if any. /// IItemDefinition IEvaluatorData.GetItemDefinition(string itemType) { ProjectItemDefinitionInstance itemDefinitionInstance; _itemDefinitions.TryGetValue(itemType, out itemDefinitionInstance); return itemDefinitionInstance; } /// /// Sets a property which does not come from the Xml. /// This is where global, environment, and toolset properties are added to the project instance by the evaluator, and we mark them /// immutable if we are immutable. /// Only called during evaluation, so does not check for immutability. /// ProjectPropertyInstance IEvaluatorData.SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, LoggingContext loggingContext, bool isEnvironmentVariable, bool isCommandLineProperty) { // Mutability not verified as this is being populated during evaluation ProjectPropertyInstance property = ProjectPropertyInstance.Create(name, evaluatedValueEscaped, mayBeReserved, _isImmutable, isEnvironmentVariable, loggingContext); _properties.Set(property); return property; } /// /// Sets a property which comes from the Xml. /// Predecessor is discarded as it is a design time only artefact. /// Only called during evaluation, so does not check for immutability. /// ProjectPropertyInstance IEvaluatorData.SetProperty(ProjectPropertyElement propertyElement, string evaluatedValueEscaped, LoggingContext loggingContext) { // Mutability not verified as this is being populated during evaluation ProjectPropertyInstance property = ProjectPropertyInstance.Create(propertyElement.Name, evaluatedValueEscaped, false /* may not be reserved */, _isImmutable); _properties.Set(property); return property; } /// /// Retrieves an existing target, if any. /// ProjectTargetInstance IEvaluatorData.GetTarget(string targetName) { ProjectTargetInstance targetInstance; _targets.TryGetValue(targetName, out targetInstance); return targetInstance; } /// /// Adds a new target. /// Only called during evaluation, so does not check for immutability. /// void IEvaluatorData.AddTarget(ProjectTargetInstance target) { _actualTargets[target.Name] = target; } /// /// Record an import opened during evaluation. /// void IEvaluatorData.RecordImport( ProjectImportElement importElement, ProjectRootElement import, int versionEvaluated, SdkResult sdkResult) { _importPaths.Add(import.FullPath); if (sdkResult?.EnvironmentVariablesToAdd is IDictionary sdkEnvironmentVariablesToAdd && sdkEnvironmentVariablesToAdd.Count > 0) { foreach (var environmentVariable in sdkEnvironmentVariablesToAdd) { _sdkResolvedEnvironmentVariableProperties.Set(ProjectPropertyInstance.Create(environmentVariable.Key, environmentVariable.Value, importElement.Location, isImmutable: true)); } } ((IEvaluatorData)this).RecordImportWithDuplicates(importElement, import, versionEvaluated); } /// /// Record an import opened during evaluation. Include duplicates /// void IEvaluatorData.RecordImportWithDuplicates(ProjectImportElement importElement, ProjectRootElement import, int versionEvaluated) { _importPathsIncludingDuplicates.Add(import.FullPath); } /// /// Get any property in the item that has the specified name, /// otherwise returns null /// [DebuggerStepThrough] public ProjectPropertyInstance GetProperty(string name) { return _properties[name]; } /// /// Get any property in the item that has the specified name, /// otherwise returns null. /// Name is the segment of the provided string with the provided start and end indexes. /// [DebuggerStepThrough] ProjectPropertyInstance IPropertyProvider.GetProperty(string name, int startIndex, int endIndex) { return _properties.GetProperty(name, startIndex, endIndex); } /// /// Get the value of a property in this project, or /// an empty string if it does not exist. /// /// /// A property with a value of empty string and no property /// at all are not distinguished between by this method. /// This is because the build does not distinguish between the two. /// The reason this method exists when users can simply do GetProperty(..).EvaluatedValue, /// is that the caller would have to check for null every time. For properties, empty and undefined are /// not distinguished, so it much more useful to also have a method that returns empty string in /// either case. /// This function returns the unescaped value. /// public string GetPropertyValue(string name) { if (!_properties.TryGetPropertyUnescapedValue(name, out string unescapedValue)) { unescapedValue = String.Empty; } return unescapedValue; } internal string GetEngineRequiredPropertyValue(string name) { if (!_properties.TryGetPropertyUnescapedValue(name, out string unescapedValue)) { unescapedValue = String.Empty; } else { _loggingContext?.ProcessPropertyRead( new PropertyReadInfo(name, ElementLocation.EmptyLocation, false, PropertyReadContext.Other)); } return unescapedValue; } /// /// Add a property with the specified name and value. /// Overwrites any property with the same name already in the collection. /// /// /// We don't take a ProjectPropertyInstance to make sure we don't have one that's already /// in use by another ProjectPropertyInstance. /// public ProjectPropertyInstance SetProperty(string name, string evaluatedValue) { VerifyThrowNotImmutable(); ProjectPropertyInstance property = ProjectPropertyInstance.Create(name, evaluatedValue, false /* may not be reserved */, _isImmutable); _properties.Set(property); _loggingContext?.ProcessPropertyWrite(new PropertyWriteInfo(name, false, ElementLocation.EmptyLocation)); return property; } /// /// Adds an item with no metadata to the project /// /// /// We don't take a ProjectItemInstance to make sure we don't have one that's already /// in use by another ProjectInstance. /// /// /// For purposes of declaring the project that defined this item (for use with e.g. the /// DeclaringProject* metadata), the entrypoint project is used for synthesized items /// like those added by this API. /// public ProjectItemInstance AddItem(string itemType, string evaluatedInclude) { VerifyThrowNotImmutable(); ProjectItemInstance item = new ProjectItemInstance(this, itemType, evaluatedInclude, this.FullPath); _items.Add(item); return item; } /// /// Adds an item with metadata to the project. /// Metadata may be null. /// /// /// We don't take a ProjectItemInstance to make sure we don't have one that's already /// in use by another ProjectInstance. /// /// /// For purposes of declaring the project that defined this item (for use with e.g. the /// DeclaringProject* metadata), the entrypoint project is used for synthesized items /// like those added by this API. /// public ProjectItemInstance AddItem(string itemType, string evaluatedInclude, IEnumerable> metadata) { VerifyThrowNotImmutable(); ProjectItemInstance item = new ProjectItemInstance(this, itemType, evaluatedInclude, metadata, this.FullPath); _items.Add(item); return item; } /// /// Get a list of all the items in the project of the specified /// type, or an empty list if there are none. /// This is a read-only list. /// public ICollection GetItems(string itemType) { VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(GetItems)); // GetItems already returns a readonly collection return ((IItemProvider)this).GetItems(itemType); } /// /// get items by item type and evaluated include value /// public IEnumerable GetItemsByItemTypeAndEvaluatedInclude(string itemType, string evaluatedInclude) { // Avoid using LINQ - this is called a lot in VS if (_itemsByEvaluatedInclude == null) { foreach (var item in GetItems(itemType)) { if (string.Equals(item.EvaluatedInclude, evaluatedInclude, StringComparison.OrdinalIgnoreCase)) { yield return item; } } } else { foreach (var item in GetItemsByEvaluatedInclude(evaluatedInclude)) { if (string.Equals(item.ItemType, itemType, StringComparison.OrdinalIgnoreCase)) { yield return item; } } } } /// /// Removes an item from the project, if present. /// Returns true if it was present, false otherwise. /// public bool RemoveItem(ProjectItemInstance item) { VerifyThrowNotImmutable(); return _items.Remove(item); } /// /// Removes any property with the specified name. /// Returns true if the property had a value (possibly empty string), otherwise false. /// public bool RemoveProperty(string name) { VerifyThrowNotImmutable(); return _properties.Remove(name); } /// /// Create an independent, deep clone of this object and everything in it. /// Useful for compiling a single file; or for keeping build results between builds. /// Clone has the same mutability as the original. /// public ProjectInstance DeepCopy() { return DeepCopy(_isImmutable); } /// /// Create an independent clone of this object, keeping ONLY the explicitly /// requested project state. /// /// /// Useful for reducing the wire cost of IPC for out-of-proc nodes used during /// design-time builds that only need to populate a known set of data. /// /// Project state that should be returned. /// public ProjectInstance FilteredCopy(RequestedProjectState filter) { return new ProjectInstance(this, true, filter); } /// /// Create an independent, deep clone of this object and everything in it, with /// specified mutability. /// Useful for compiling a single file; or for keeping build results between builds. /// public ProjectInstance DeepCopy(bool isImmutable) { if (isImmutable && _isImmutable) { // No need to clone return this; } return new ProjectInstance(this, isImmutable); } /// /// Build default target/s with loggers of the project collection. /// Returns true on success, false on failure. /// Only valid if mutable. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build() { return Build(null); } /// /// Build default target/s with specified loggers. /// Returns true on success, false on failure. /// Loggers may be null. /// Only valid if mutable. /// /// /// If any of the loggers supplied are already attached to the logging service we /// were passed, throws InvalidOperationException. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build(IEnumerable loggers) { return Build((string[])null, loggers, null); } /// /// Build default target/s with specified loggers. /// Returns true on success, false on failure. /// Loggers may be null. /// Only valid if mutable. /// /// /// If any of the loggers supplied are already attached to the logging service we /// were passed, throws InvalidOperationException. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build(IEnumerable loggers, IEnumerable remoteLoggers) { return Build((string[])null, loggers, remoteLoggers); } /// /// Build a target with specified loggers. /// Returns true on success, false on failure. /// Target may be null. /// Loggers may be null. /// Only valid if mutable. /// /// /// If any of the loggers supplied are already attached to the logging service we /// were passed, throws InvalidOperationException. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build(string target, IEnumerable loggers) { return Build(target, loggers, null); } /// /// Build a target with specified loggers. /// Returns true on success, false on failure. /// Target may be null. /// Loggers may be null. /// Remote loggers may be null. /// Only valid if mutable. /// /// /// If any of the loggers supplied are already attached to the logging service we /// were passed, throws InvalidOperationException. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build(string target, IEnumerable loggers, IEnumerable remoteLoggers) { string[] targets = (target == null) ? [] : [target]; return Build(targets, loggers, remoteLoggers); } /// /// Build a list of targets with specified loggers. /// Returns true on success, false on failure. /// Targets may be null. /// Loggers may be null. /// Only valid if mutable. /// /// /// If any of the loggers supplied are already attached to the logging service we /// were passed, throws InvalidOperationException. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build(string[] targets, IEnumerable loggers) { return Build(targets, loggers, null); } /// /// Build a list of targets with specified loggers. /// Returns true on success, false on failure. /// Targets may be null. /// Loggers may be null. /// Remote loggers may be null. /// Only valid if mutable. /// /// /// If any of the loggers supplied are already attached to the logging service we /// were passed, throws InvalidOperationException. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build(string[] targets, IEnumerable loggers, IEnumerable remoteLoggers) { IDictionary targetOutputs; return Build(targets, loggers, remoteLoggers, out targetOutputs); } /// /// Build a list of targets with specified loggers. /// Returns true on success, false on failure. /// Targets may be null. /// Loggers may be null. /// Only valid if mutable. /// /// /// If any of the loggers supplied are already attached to the logging service we /// were passed, throws InvalidOperationException. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build(string[] targets, IEnumerable loggers, out IDictionary targetOutputs) { return Build(targets, loggers, null, null, out targetOutputs); } /// /// Build a list of targets with specified loggers. /// Returns true on success, false on failure. /// Targets may be null. /// Loggers may be null. /// Remote loggers may be null. /// Only valid if mutable. /// /// /// If any of the loggers supplied are already attached to the logging service we /// were passed, throws InvalidOperationException. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] public bool Build(string[] targets, IEnumerable loggers, IEnumerable remoteLoggers, out IDictionary targetOutputs) { return Build(targets, loggers, remoteLoggers, null, out targetOutputs); } /// /// Evaluates the provided string by expanding items and properties, /// using the current items and properties available. /// This is useful for some hosts, or for the debugger immediate window. /// Does not expand bare metadata expressions. /// /// /// Not for internal use. /// public string ExpandString(string unexpandedValue) { Expander expander = new Expander(this, this, FileSystems.Default, _loggingContext); string result = expander.ExpandIntoStringAndUnescape(unexpandedValue, ExpanderOptions.ExpandPropertiesAndItems, ProjectFileLocation); return result; } /// /// Evaluates the provided string as a condition by expanding items and properties, /// using the current items and properties available, then doing a logical evaluation. /// This is useful for the immediate window. /// Does not expand bare metadata expressions. /// /// /// Not for internal use. /// public bool EvaluateCondition(string condition) { Expander expander = new Expander(this, this, FileSystems.Default, _loggingContext); bool result = ConditionEvaluator.EvaluateCondition( condition, ParserOptions.AllowPropertiesAndItemLists, expander, ExpanderOptions.ExpandPropertiesAndItems, Directory, ProjectFileLocation, FileSystems.Default, null /* no logging context */); return result; } /// /// Creates a ProjectRootElement from the contents of this ProjectInstance. /// /// A ProjectRootElement which represents this instance. public ProjectRootElement ToProjectRootElement() { ProjectRootElement rootElement = ProjectRootElement.Create(); rootElement.InitialTargets = String.Join(";", InitialTargets); rootElement.DefaultTargets = String.Join(";", DefaultTargets); rootElement.ToolsVersion = ToolsVersion; // Add all of the item definitions. ProjectItemDefinitionGroupElement itemDefinitionGroupElement = rootElement.AddItemDefinitionGroup(); foreach (ProjectItemDefinitionInstance itemDefinitionInstance in _itemDefinitions.Values) { itemDefinitionInstance.ToProjectItemDefinitionElement(itemDefinitionGroupElement); } // Add all of the items. foreach (string itemType in _items.ItemTypes) { ProjectItemGroupElement itemGroupElement = rootElement.AddItemGroup(); foreach (ProjectItemInstance item in _items.GetItems(itemType)) { item.ToProjectItemElement(itemGroupElement); } } // Add all of the properties. ProjectPropertyGroupElement propertyGroupElement = rootElement.AddPropertyGroup(); foreach (ProjectPropertyInstance property in _properties) { if (!ReservedPropertyNames.IsReservedProperty(property.Name)) { // Only emit the property if it does not exist in the global or environment properties dictionaries or differs from them. if (!_globalProperties.Contains(property.Name) || !String.Equals(_globalProperties[property.Name].EvaluatedValue, property.EvaluatedValue, StringComparison.OrdinalIgnoreCase)) { if ((!_environmentVariableProperties.Contains(property.Name) || !String.Equals(_environmentVariableProperties[property.Name].EvaluatedValue, property.EvaluatedValue, StringComparison.OrdinalIgnoreCase)) && _sdkResolvedEnvironmentVariableProperties is not null && (!_sdkResolvedEnvironmentVariableProperties.Contains(property.Name) || !String.Equals(_sdkResolvedEnvironmentVariableProperties[property.Name].EvaluatedValue, property.EvaluatedValue, StringComparison.OrdinalIgnoreCase))) { property.ToProjectPropertyElement(propertyGroupElement); } } } } // Add all of the targets. foreach (ProjectTargetInstance target in Targets.Values) { target.ToProjectTargetElement(rootElement); } return rootElement; } /// /// Replaces the project state (, and ) with that /// from the provided. /// /// with the state to use. public void UpdateStateFrom(ProjectInstance projectState) { _globalProperties = new PropertyDictionary(projectState._globalProperties); _properties = new PropertyDictionary(projectState._properties); _items = new ItemDictionary(projectState._items); } internal bool IsLoaded => ProjectRootElementCache != null && TaskRegistry.IsLoaded; /// /// When project instances get serialized between nodes, they need to be initialized with node specific information. /// The node specific information cannot come from the constructor, because that information is not available to INodePacketTranslators /// internal void LateInitialize(ProjectRootElementCacheBase projectRootElementCache, HostServices hostServices) { Assumed.Null(ProjectRootElementCache, $"{nameof(ProjectRootElementCache)} is already set. Cannot set again"); Assumed.Null(_hostServices, $"{nameof(HostServices)} is already set. Cannot set again"); Assumed.NotNull(TaskRegistry, $"{nameof(TaskRegistry)} Cannot be null after {nameof(ProjectInstance)} object creation."); ProjectRootElementCache = projectRootElementCache; _taskRegistry.RootElementCache = projectRootElementCache; _hostServices = hostServices; } #region INodePacketTranslatable Members /// /// Translate the project instance to or from a stream. /// Only translates global properties, properties, items, and mutability. /// void ITranslatable.Translate(ITranslator translator) { if (translator.Mode == TranslationDirection.WriteToStream) { // When serializing into stream apply Traits.Instance.EscapeHatches.ProjectInstanceTranslation if defined. MaybeForceTranslateEntireStateMode(); } translator.Translate(ref _translateEntireState); if (_translateEntireState) { TranslateAllState(translator); } else { TranslateMinimalState(translator); } } private void MaybeForceTranslateEntireStateMode() { var forcedProjectInstanceTranslationMode = Traits.Instance.EscapeHatches.ProjectInstanceTranslation; if (forcedProjectInstanceTranslationMode != null) { switch (forcedProjectInstanceTranslationMode) { case EscapeHatches.ProjectInstanceTranslationMode.Full: _translateEntireState = true; break; case EscapeHatches.ProjectInstanceTranslationMode.Partial: _translateEntireState = false; break; default: // if EscapeHatches.ProjectInstanceTranslation has an unexpected value, do not force TranslateEntireStateMode. // Just leave it as is. break; } } } internal void TranslateMinimalState(ITranslator translator) { translator.TranslateDictionary(ref _globalProperties, ProjectPropertyInstance.FactoryForDeserialization); translator.TranslateDictionary(ref _properties, ProjectPropertyInstance.FactoryForDeserialization); translator.Translate(ref _requestedProjectStateFilter); translator.Translate(ref _isImmutable); TranslateItems(translator); } private void TranslateAllState(ITranslator translator) { TranslateProperties(translator); TranslateItems(translator); TranslateTargets(translator); TranslateToolsetSpecificState(translator); translator.Translate(ref _directory); translator.Translate(ref _projectFileLocation, ElementLocation.FactoryForDeserialization); translator.Translate(ref _taskRegistry, TaskRegistry.FactoryForDeserialization); translator.Translate(ref _isImmutable); translator.Translate(ref _evaluationId); translator.TranslateDictionary( ref _itemDefinitions, ProjectItemDefinitionInstance.FactoryForDeserialization, capacity => new RetrievableEntryHashSet(capacity, MSBuildNameIgnoreCaseComparer.Default)); // ignore _importPaths/ImportPaths. Only used by public API users, not nodes // ignore _importPathsIncludingDuplicates/ImportPathsIncludingDuplicates. Only used by public API users, not nodes } private void TranslateToolsetSpecificState(ITranslator translator) { translator.Translate(ref _toolset, Toolset.FactoryForDeserialization); translator.Translate(ref _usingDifferentToolsVersionFromProjectFile); translator.Translate(ref _explicitToolsVersionSpecified); translator.Translate(ref _originalProjectToolsVersion); translator.Translate(ref _subToolsetVersion); } private void TranslateProperties(ITranslator translator) { translator.TranslateDictionary(ref _environmentVariableProperties, ProjectPropertyInstance.FactoryForDeserialization); translator.TranslateDictionary(ref _globalProperties, ProjectPropertyInstance.FactoryForDeserialization); translator.TranslateDictionary(ref _properties, ProjectPropertyInstance.FactoryForDeserialization); var globalPropertiesToTreatAsLocal = (HashSet)_globalPropertiesToTreatAsLocal; translator.Translate(ref globalPropertiesToTreatAsLocal); if (translator.Mode == TranslationDirection.ReadFromStream) { _globalPropertiesToTreatAsLocal = globalPropertiesToTreatAsLocal; } } private void TranslateTargets(ITranslator translator) { translator.TranslateDictionary(ref _targets, ProjectTargetInstance.FactoryForDeserialization, capacity => new RetrievableEntryHashSet(capacity, MSBuildNameIgnoreCaseComparer.Default)); translator.TranslateDictionary(ref _beforeTargets, TranslatorForTargetSpecificDictionaryKey, TranslatorForTargetSpecificDictionaryValue, count => new Dictionary>(count)); translator.TranslateDictionary(ref _afterTargets, TranslatorForTargetSpecificDictionaryKey, TranslatorForTargetSpecificDictionaryValue, count => new Dictionary>(count)); translator.Translate(ref _defaultTargets); translator.Translate(ref _initialTargets); } // todo move to nested function after c#7 private static void TranslatorForTargetSpecificDictionaryKey(ITranslator translator, ref string key) { translator.Translate(ref key); } // todo move to nested function after c#7 private static void TranslatorForTargetSpecificDictionaryValue(ITranslator translator, ref List value) { translator.Translate(ref value, TargetSpecification.FactoryForDeserialization); } private void TranslateItems(ITranslator translator) { // ignore EvaluatedItemElements. Only used by public API users, not nodes // ignore itemsByEvaluatedInclude. Only used by public API users, not nodes if (translator.Mode == TranslationDirection.ReadFromStream) { int typeCount = default(int); translator.Translate(ref typeCount); _items = new ItemDictionary(typeCount); for (int typeIndex = 0; typeIndex < typeCount; typeIndex++) { int itemCount = default(int); translator.Translate(ref itemCount); for (int i = 0; i < itemCount; i++) { ProjectItemInstance item = null; translator.Translate(ref item, delegate { return ProjectItemInstance.FactoryForDeserialization(translator, this); }); _items.Add(item); } } } else { int typeCount = _items.ItemTypes.Count; translator.Translate(ref typeCount); foreach (string itemType in _items.ItemTypes) { ICollection itemList = _items[itemType]; int itemCount = itemList.Count; translator.Translate(ref itemCount); foreach (ProjectItemInstance item in itemList) { ProjectItemInstance temp = item; translator.Translate(ref temp, delegate { return ProjectItemInstance.FactoryForDeserialization(translator, this); }); } } } } #endregion /// /// Creates a set of project instances which represent the project dependency graph for a solution build. /// [RequiresUnreferencedCode("Evaluates a solution's projects, which resolves SDKs and reflects over their types; incompatible with trimming.")] internal static ProjectInstance[] LoadSolutionForBuild( string projectFile, PropertyDictionary globalPropertiesInstances, string toolsVersion, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext projectBuildEventContext, bool isExplicitlyLoaded, IReadOnlyCollection targetNames, ISdkResolverService sdkResolverService, int submissionId) { ArgumentException.ThrowIfNullOrEmpty(projectFile); ArgumentNullException.ThrowIfNull(globalPropertiesInstances); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion)); ArgumentNullException.ThrowIfNull(buildParameters); Assumed.True(FileUtilities.IsSolutionFilename(projectFile), $"Project file {projectFile} is not a solution."); ProjectInstance[] projectInstances = null; Dictionary globalProperties = new Dictionary(globalPropertiesInstances.Count, StringComparer.OrdinalIgnoreCase); foreach (ProjectPropertyInstance propertyInstance in globalPropertiesInstances) { globalProperties[propertyInstance.Name] = ((IProperty)propertyInstance).EvaluatedValueEscaped; } // If a ToolsVersion has been passed in using the /tv:xx switch, we want to generate an // old-style solution wrapper project if it's < 4.0, to work around ordering issues. if (toolsVersion != null) { if ( String.Equals(toolsVersion, "2.0", StringComparison.OrdinalIgnoreCase) || String.Equals(toolsVersion, "3.0", StringComparison.OrdinalIgnoreCase) || String.Equals(toolsVersion, "3.5", StringComparison.OrdinalIgnoreCase)) { // Spawn the Orcas SolutionWrapperProject generator. loggingService.LogComment(projectBuildEventContext, MessageImportance.Low, "OldWrapperGeneratedExplicitToolsVersion", toolsVersion); projectInstances = GenerateSolutionWrapperUsingOldOM(projectFile, globalProperties, toolsVersion, buildParameters.ProjectRootElementCache, buildParameters, loggingService, projectBuildEventContext, isExplicitlyLoaded, sdkResolverService, submissionId); } else { projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersion, loggingService, projectBuildEventContext, targetNames, sdkResolverService, submissionId); } } // If the user didn't pass in a ToolsVersion, still try to make a best-effort guess as to whether // we should be generating a 4.0+ or a 3.5-style wrapper project based on the version of the solution. else { projectInstances = CalculateToolsVersionAndGenerateSolutionWrapper( projectFile, buildParameters, loggingService, projectBuildEventContext, globalProperties, isExplicitlyLoaded, targetNames, sdkResolverService, submissionId); } return projectInstances; } [RequiresUnreferencedCode("Evaluates a solution's projects, which resolves SDKs and reflects over their types; incompatible with trimming.")] private static ProjectInstance[] CalculateToolsVersionAndGenerateSolutionWrapper( string projectFile, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext projectBuildEventContext, Dictionary globalProperties, bool isExplicitlyLoaded, IReadOnlyCollection targetNames, ISdkResolverService sdkResolverService, int submissionId) { string solutionFileName = projectFile; if (FileUtilities.IsSolutionFilterFilename(projectFile)) { solutionFileName = SolutionFile.ParseSolutionFromSolutionFilter(projectFile, out _); } if (SolutionFile.ShouldUseNewParser(solutionFileName)) { // For the new parser we use Current tools version. return GenerateSolutionWrapper(projectFile, globalProperties, "Current", loggingService, projectBuildEventContext, targetNames, sdkResolverService, submissionId); } // For the old parser we try to make a best-effort guess based on the version of the solution. string toolsVersion = null; ProjectInstance[] projectInstances = null; SolutionFile.GetSolutionFileAndVisualStudioMajorVersions(solutionFileName, out int solutionVersion, out int visualStudioVersion); // If we get to this point, it's because it's a valid version. Map the solution version // to the equivalent MSBuild ToolsVersion, and unless it's Dev10 or newer, spawn the old // engine to generate the solution wrapper. if (solutionVersion <= 9) /* Whidbey or before */ { loggingService.LogComment(projectBuildEventContext, MessageImportance.Low, "OldWrapperGeneratedOldSolutionVersion", "2.0", solutionVersion); projectInstances = GenerateSolutionWrapperUsingOldOM(projectFile, globalProperties, "2.0", buildParameters.ProjectRootElementCache, buildParameters, loggingService, projectBuildEventContext, isExplicitlyLoaded, sdkResolverService, submissionId); } else if (solutionVersion == 10) /* Orcas */ { loggingService.LogComment(projectBuildEventContext, MessageImportance.Low, "OldWrapperGeneratedOldSolutionVersion", "3.5", solutionVersion); projectInstances = GenerateSolutionWrapperUsingOldOM(projectFile, globalProperties, "3.5", buildParameters.ProjectRootElementCache, buildParameters, loggingService, projectBuildEventContext, isExplicitlyLoaded, sdkResolverService, submissionId); } else { if ((solutionVersion == 11) || (solutionVersion == 12 && visualStudioVersion == 0)) /* Dev 10 and Dev 11 */ { toolsVersion = "4.0"; } else /* Dev 12 and above */ { toolsVersion = #if NET string.Create(CultureInfo.InvariantCulture, $"{visualStudioVersion}.0"); #else $"{visualStudioVersion.ToString(CultureInfo.InvariantCulture)}.0"; #endif } string toolsVersionToUse = Utilities.GenerateToolsVersionToUse( explicitToolsVersion: null, toolsVersionFromProject: FileUtilities.IsSolutionFilterFilename(projectFile) ? "Current" : toolsVersion, getToolset: buildParameters.GetToolset, defaultToolsVersion: Constants.defaultSolutionWrapperProjectToolsVersion, usingDifferentToolsVersionFromProjectFile: out _); projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersionToUse, loggingService, projectBuildEventContext, targetNames, sdkResolverService, submissionId); } return projectInstances; } /// /// Factory for deserialization. /// internal static ProjectInstance FactoryForDeserialization(ITranslator translator) { return new ProjectInstance(translator); } /// /// Throws invalid operation exception if the project instance is immutable. /// Called before an edit. /// internal static void VerifyThrowNotImmutable(bool isImmutable) { if (isImmutable) { ErrorUtilities.ThrowInvalidOperation("OM_ProjectInstanceImmutable"); } } /// /// Builds a list of targets with the specified loggers. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] internal bool Build(string[] targets, IEnumerable loggers, IEnumerable remoteLoggers, ILoggingService loggingService, int maxNodeCount, out IDictionary targetOutputs) { VerifyThrowNotImmutable(); if (targets == null) { targets = []; } BuildResult results; BuildManager buildManager = BuildManager.DefaultBuildManager; BuildRequestData data = new BuildRequestData(this, targets, _hostServices); BuildParameters parameters = new BuildParameters(); if (loggers != null) { parameters.Loggers = (loggers is ICollection loggersCollection) ? loggersCollection : new List(loggers); // Enables task parameter logging based on whether any of the loggers attached // to the Project have their verbosity set to Diagnostic. If no logger has // been set to log diagnostic then the existing/default value will be persisted. parameters.LogTaskInputs = parameters.LogTaskInputs || loggers.Any(logger => logger.Verbosity == LoggerVerbosity.Diagnostic) || loggingService?.IncludeTaskInputs == true; parameters.EnableTargetOutputLogging = parameters.EnableTargetOutputLogging || loggers.Any(logger => logger.Verbosity == LoggerVerbosity.Diagnostic) || loggingService?.EnableTargetOutputLogging == true; } if (remoteLoggers != null) { parameters.ForwardingLoggers = remoteLoggers is ICollection records ? records : new List(remoteLoggers); } parameters.EnvironmentPropertiesInternal = _environmentVariableProperties; parameters.ProjectRootElementCache = ProjectRootElementCache; parameters.MaxNodeCount = maxNodeCount; results = buildManager.Build(parameters, data); targetOutputs = results.ResultsByTarget; return results.OverallResult == BuildResultCode.Success; } /// /// Builds a list of targets with the specified loggers. /// [RequiresUnreferencedCode("Initializes loggers and project cache plugins by reflecting over assemblies discovered at runtime, which is incompatible with trimming.")] internal bool Build(string[] targets, IEnumerable loggers, IEnumerable remoteLoggers, ILoggingService loggingService, out IDictionary targetOutputs) { return Build(targets, loggers, remoteLoggers, loggingService, 1, out targetOutputs); } /// /// Retrieves the list of targets which should run before the specified target. /// Never returns null. /// internal IList GetTargetsWhichRunBefore(string target) { List beforeTargetsForTarget; if (((IEvaluatorData)this).BeforeTargets.TryGetValue(target, out beforeTargetsForTarget)) { return beforeTargetsForTarget; } else { return Array.Empty(); } } /// /// Retrieves the list of targets which should run after the specified target. /// Never returns null. /// internal IList GetTargetsWhichRunAfter(string target) { List afterTargetsForTarget; if (((IEvaluatorData)this).AfterTargets.TryGetValue(target, out afterTargetsForTarget)) { return afterTargetsForTarget; } else { return Array.Empty(); } } /// /// Cache the contents of this project instance to the translator. /// The object is retained, but the bulk of its content is released. /// internal void Cache(ITranslator translator) { ((ITranslatable)this).Translate(translator); if (translator.Mode == TranslationDirection.WriteToStream) { _globalProperties = null; _properties = null; _items = null; } } /// /// Retrieve the contents of this project from the translator. /// internal void RetrieveFromCache(ITranslator translator) { ((ITranslatable)this).Translate(translator); } /// /// Adds the specified target to the instance. /// internal ProjectTargetInstance AddTarget( string targetName, string condition, string inputs, string outputs, string returns, string keepDuplicateOutputs, string dependsOnTargets, string beforeTargets, string afterTargets, bool parentProjectSupportsReturnsAttribute) { VerifyThrowNotImmutable(); Assumed.NotNullOrEmpty(targetName); Assumed.False(_actualTargets.ContainsKey(targetName), $"Target {targetName} already exists."); ProjectTargetInstance target = new ProjectTargetInstance( targetName, condition ?? String.Empty, inputs ?? String.Empty, outputs ?? String.Empty, returns, // returns may be null keepDuplicateOutputs ?? String.Empty, dependsOnTargets ?? String.Empty, beforeTargets ?? String.Empty, afterTargets ?? String.Empty, _projectFileLocation, String.IsNullOrEmpty(condition) ? null : ElementLocation.EmptyLocation, String.IsNullOrEmpty(inputs) ? null : ElementLocation.EmptyLocation, String.IsNullOrEmpty(outputs) ? null : ElementLocation.EmptyLocation, String.IsNullOrEmpty(returns) ? null : ElementLocation.EmptyLocation, String.IsNullOrEmpty(keepDuplicateOutputs) ? null : ElementLocation.EmptyLocation, String.IsNullOrEmpty(dependsOnTargets) ? null : ElementLocation.EmptyLocation, String.IsNullOrEmpty(beforeTargets) ? null : ElementLocation.EmptyLocation, String.IsNullOrEmpty(afterTargets) ? null : ElementLocation.EmptyLocation, new ObjectModel.ReadOnlyCollection(new List()), new ObjectModel.ReadOnlyCollection(new List()), parentProjectSupportsReturnsAttribute); _actualTargets[targetName] = target; return target; } /// /// Removes the specified target from the instance. /// internal void RemoveTarget(string targetName) { VerifyThrowNotImmutable(); _actualTargets.Remove(targetName); } /// /// Throws invalid operation exception if the project instance is immutable. /// Called before an edit. /// internal void VerifyThrowNotImmutable() { VerifyThrowNotImmutable(_isImmutable); } /// /// Generate a 4.0+-style solution wrapper project. /// /// The solution file to generate a wrapper for. /// The global properties of this solution. /// The ToolsVersion to use when generating the wrapper. /// The logging service used to log messages etc. from the solution wrapper generator. /// The build event context in which this project is being constructed. /// A collection of target names that the user requested be built. /// /// /// The ProjectRootElement for the root traversal and each of the metaprojects. [RequiresUnreferencedCode("Evaluates a generated solution metaproject, which resolves SDKs and loads loggers by reflection at runtime; incompatible with trimming.")] private static ProjectInstance[] GenerateSolutionWrapper( string projectFile, IDictionary globalProperties, string toolsVersion, ILoggingService loggingService, BuildEventContext projectBuildEventContext, IReadOnlyCollection targetNames, ISdkResolverService sdkResolverService, int submissionId) { SolutionFile sp = SolutionFile.Parse(projectFile); // Log any comments from the solution parser if (sp.SolutionParserComments.Count > 0) { foreach (string comment in sp.SolutionParserComments) { loggingService.LogCommentFromText(projectBuildEventContext, MessageImportance.Low, comment); } } // Pass the toolsVersion of this project through, which will be not null if there was a /tv:nn switch // It's needed to determine which tags to put in, whether to put a ToolsVersion parameter // on the task tags, and what MSBuildToolsPath to use when scanning child projects // for dependency information. ProjectInstance[] instances = SolutionProjectGenerator.Generate(sp, globalProperties, toolsVersion, projectBuildEventContext, loggingService, targetNames, sdkResolverService, submissionId); return instances; } /// /// Spawn the old engine to generate a solution wrapper project, so that our build ordering is somewhat more correct /// when solutions with toolsVersions < 4.0 are passed to us. /// /// /// ############################################################################################# /// #### Segregated into another method to avoid loading the old Engine in the regular case. #### /// ####################### Do not move back in to the main code path! ########################## /// ############################################################################################# /// We have marked this method as NoInlining because we do not want Microsoft.Build.Engine.dll to be loaded unless we really execute this code path /// /// The solution file to generate a wrapper for. /// The global properties of this solution. /// The ToolsVersion to use when generating the wrapper. /// The root element cache which should be used for the generated project. /// The build parameters. /// The logging service used to log messages etc. from the solution wrapper generator. /// The build event context in which this project is being constructed. /// true if the project is explicitly loaded, otherwise false. /// An to use when resolving SDKs. /// /// An appropriate ProjectRootElement [MethodImpl(MethodImplOptions.NoInlining)] [RequiresUnreferencedCode("Evaluates a generated solution metaproject, which resolves SDKs and reflects over their types; incompatible with trimming.")] private static ProjectInstance[] GenerateSolutionWrapperUsingOldOM( string projectFile, IDictionary globalProperties, string toolsVersion, ProjectRootElementCacheBase projectRootElementCache, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext projectBuildEventContext, bool isExplicitlyLoaded, ISdkResolverService sdkResolverService, int submissionId) { // Pass the toolsVersion of this project through, which will never be null -- either we passed the /tv:nn // switch straight through, or we fabricated a ToolsVersion based on the solution version. // It's needed to determine which tags to put in, whether to put a ToolsVersion parameter // on the task tags, and what MSBuildToolsPath to use when scanning child projects // for dependency information. string wrapperProjectXml; List clearedVariables = null; try { // We need to make sure we unset any enviroment variable which is a reserved property or has an illegal name before we call the oldOM as it may crash it. foreach (DictionaryEntry environmentVariable in Environment.GetEnvironmentVariables()) { // We're going to just skip environment variables that contain names // with characters we can't handle. There's no logger registered yet // when this method is called, so we can't really log anything. string environmentVariableName = environmentVariable.Key as string; if (environmentVariableName != null && (!XmlUtilities.IsValidElementName(environmentVariableName) || XMakeElements.ReservedItemNames.Contains(environmentVariableName) || ReservedPropertyNames.IsReservedProperty(environmentVariableName))) { if (clearedVariables == null) { clearedVariables = new List(); } Environment.SetEnvironmentVariable(environmentVariableName, null); clearedVariables.Add(environmentVariable); } } wrapperProjectXml = ""; } finally { // Set the cleared environment variables back to what they were. if (clearedVariables != null) { foreach (DictionaryEntry clearedVariable in clearedVariables) { Environment.SetEnvironmentVariable(clearedVariable.Key as string, clearedVariable.Value as string); } } } XmlReaderSettings xrs = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }; StringReader sr = new StringReader(wrapperProjectXml); using (XmlReader xmlReader = XmlReader.Create(sr, xrs)) { ProjectRootElement projectRootElement = new( xmlReader, projectRootElementCache, isExplicitlyLoaded, preserveFormatting: false) { DirectoryPath = Path.GetDirectoryName(projectFile) }; ProjectInstance instance = new(projectRootElement, globalProperties, toolsVersion, buildParameters, loggingService, projectBuildEventContext, sdkResolverService, submissionId); return [instance]; } } /// /// Creates a copy of a dictionary and returns a read-only dictionary around the results. /// /// The value stored in the dictionary /// Dictionary to clone. /// The to use for the cloned dictionary. private static ObjectModel.ReadOnlyDictionary CreateCloneDictionary(IDictionary dictionary, StringComparer strComparer) { Dictionary clone; if (dictionary == null) { clone = new Dictionary(0); } else { clone = new Dictionary(dictionary, strComparer); } return new ObjectModel.ReadOnlyDictionary(clone); } /// /// Creates a copy of a dictionary and returns a read-only dictionary around the results. /// /// The value stored in the dictionary /// Dictionary to clone. private static IDictionary CreateCloneDictionary(IDictionary dictionary) where TValue : class, IKeyed { if (dictionary == null) { return ReadOnlyEmptyDictionary.Instance; } else { return new ObjectModel.ReadOnlyDictionary(dictionary); } } private static ProjectPropertyInstance InstantiateProjectPropertyInstance(ProjectProperty property, bool isImmutable) { // Allow reserved property names, since this is how they are added to the project instance. // The caller has prevented users setting them themselves. var instance = ProjectPropertyInstance.Create( property.Name, ((IProperty)property).EvaluatedValueEscaped, true /* MAY be reserved name */, isImmutable, property.IsEnvironmentProperty); return instance; } /// /// Logging context - set during the evaluation. /// Can be null - especially if the project was fetched from the cache. /// private LoggingContext _loggingContext; /// /// Common code for the constructors that evaluate directly. /// Global properties may be null. /// Tools version may be null. /// Does not set mutability. /// private void Initialize( ProjectRootElement xml, IDictionary globalProperties, string explicitToolsVersion, string explicitSubToolsetVersion, int visualStudioVersionFromSolution, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext buildEventContext, ISdkResolverService sdkResolverService = null, int submissionId = BuildEventContext.InvalidSubmissionId, ProjectLoadSettings? projectLoadSettings = null, EvaluationContext evaluationContext = null, IDirectoryCacheFactory directoryCacheFactory = null, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { ArgumentNullException.ThrowIfNull(xml); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(explicitToolsVersion, "toolsVersion"); ArgumentNullException.ThrowIfNull(buildParameters); _directory = xml.DirectoryPath; _projectFileLocation = xml.ProjectFileLocation ?? ElementLocation.EmptyLocation; _properties = new PropertyDictionary(); _items = new ItemDictionary(); _actualTargets = new RetrievableEntryHashSet(StringComparer.OrdinalIgnoreCase); _targets = new ObjectModel.ReadOnlyDictionary(_actualTargets); _importPaths = new List(); ImportPaths = new ObjectModel.ReadOnlyCollection(_importPaths); _importPathsIncludingDuplicates = new List(); ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection(_importPathsIncludingDuplicates); _globalProperties = new PropertyDictionary((globalProperties == null) ? 0 : globalProperties.Count); _environmentVariableProperties = buildParameters.EnvironmentPropertiesInternal; _itemDefinitions = new RetrievableEntryHashSet(MSBuildNameIgnoreCaseComparer.Default); _hostServices = buildParameters.HostServices; this.ProjectRootElementCache = buildParameters.ProjectRootElementCache; _loggingContext = new GenericLoggingContext(loggingService, buildEventContext); this.EvaluatedItemElements = new List(); _explicitToolsVersionSpecified = (explicitToolsVersion != null); ElementLocation toolsVersionLocation = xml.Location; if (xml.ToolsVersion.Length > 0) { _originalProjectToolsVersion = xml.ToolsVersion; toolsVersionLocation = xml.ToolsVersionLocation; } var toolsVersionToUse = Utilities.GenerateToolsVersionToUse( explicitToolsVersion, xml.ToolsVersion, buildParameters.GetToolset, buildParameters.DefaultToolsVersion, out var usingDifferentToolsVersionFromProjectFile); _usingDifferentToolsVersionFromProjectFile = usingDifferentToolsVersionFromProjectFile; this.Toolset = buildParameters.GetToolset(toolsVersionToUse); if (this.Toolset == null) { string toolsVersionList = Utilities.CreateToolsVersionListString(buildParameters.Toolsets); ProjectErrorUtilities.ThrowInvalidProject(toolsVersionLocation, "UnrecognizedToolsVersion", toolsVersionToUse, toolsVersionList); } if (explicitSubToolsetVersion != null) { this.SubToolsetVersion = explicitSubToolsetVersion; } else { this.SubToolsetVersion = this.Toolset.GenerateSubToolsetVersionUsingVisualStudioVersion(globalProperties, visualStudioVersionFromSolution); } // Create a task registry which will fall back on the toolset task registry if necessary. this.TaskRegistry = new TaskRegistry(this.Toolset, ProjectRootElementCache); if (globalProperties != null) { foreach (KeyValuePair globalProperty in globalProperties) { if (String.Equals(globalProperty.Key, Constants.SubToolsetVersionPropertyName, StringComparison.OrdinalIgnoreCase) && explicitSubToolsetVersion != null) { // if we have a sub-toolset version explicitly provided by the ProjectInstance constructor, AND a sub-toolset version provided as a global property, // make sure that the one passed in with the constructor wins. If there isn't a matching global property, the sub-toolset version will be set at // a later point. _globalProperties.Set(ProjectPropertyInstance.Create(globalProperty.Key, explicitSubToolsetVersion, false /* may not be reserved */, _isImmutable)); } else { _globalProperties.Set(ProjectPropertyInstance.Create(globalProperty.Key, globalProperty.Value, false /* may not be reserved */, _isImmutable)); } } } if (Traits.Instance.EscapeHatches.DebugEvaluation) { Trace.WriteLine($"MSBUILD: Creating a ProjectInstance from an unevaluated state [{FullPath}]"); } Assumed.Equal(EvaluationId, BuildEventContext.InvalidEvaluationId, "Evaluation ID is invalid prior to evaluation"); evaluationContext = evaluationContext?.ContextForNewProject() ?? EvaluationContext.Create(EvaluationContext.SharingPolicy.Isolated); Evaluator.Evaluate( data: this, project: null, xml, projectLoadSettings ?? buildParameters.ProjectLoadSettings, /* Use override ProjectLoadSettings if specified */ buildParameters.MaxNodeCount, buildParameters.EnvironmentPropertiesInternal, buildParameters.PropertiesFromCommandLine, loggingService, new ProjectItemInstanceFactory(this), buildParameters.ToolsetProvider, directoryCacheFactory, ProjectRootElementCache, buildEventContext, sdkResolverService ?? evaluationContext.SdkResolverService, /* Use override ISdkResolverService if specified */ submissionId, evaluationContext, interactive: buildParameters.Interactive, evaluationStage: evaluationStage); _evaluationStage = evaluationStage; Assumed.NotEqual(EvaluationId, BuildEventContext.InvalidEvaluationId, "Evaluation should produce an evaluation ID"); } /// /// Get items by evaluatedInclude value /// private IEnumerable GetItemsByEvaluatedInclude(string evaluatedInclude) { // Even if there are no items in itemsByEvaluatedInclude[], it will return an IEnumerable, which is non-null return _itemsByEvaluatedInclude[evaluatedInclude]; } /// /// Create various target snapshots /// private void CreateTargetsSnapshot( IDictionary targets, List defaultTargets, List initialTargets, IDictionary> beforeTargets, IDictionary> afterTargets) { // ProjectTargetInstances are immutable so only the dictionary must be cloned _targets = CreateCloneDictionary(targets); InitializeTargetsData(defaultTargets, initialTargets, beforeTargets, afterTargets); } private void InitializeTargetsData(List defaultTargets, List initialTargets, IDictionary> beforeTargets, IDictionary> afterTargets) { DefaultTargets = defaultTargets == null ? new List(0) : new List(defaultTargets); InitialTargets = initialTargets == null ? new List(0) : new List(initialTargets); ((IEvaluatorData)this).BeforeTargets = CreateCloneDictionary(beforeTargets, StringComparer.OrdinalIgnoreCase); ((IEvaluatorData)this).AfterTargets = CreateCloneDictionary(afterTargets, StringComparer.OrdinalIgnoreCase); } /// /// Create various imports snapshots /// private void CreateImportsSnapshot(IList importClosure, IList importClosureWithDuplicates) { var importPaths = new List(Math.Max(0, importClosure.Count - 1) /* outer project */); foreach (var resolvedImport in importClosure) { // Exclude outer project itself if (resolvedImport.ImportingElement != null) { importPaths.Add(resolvedImport.ImportedProject.FullPath); } } _importPaths = importPaths; ImportPaths = importPaths.AsReadOnly(); var importPathsIncludingDuplicates = new List(Math.Max(0, importClosureWithDuplicates.Count - 1) /* outer project */); foreach (var resolvedImport in importClosureWithDuplicates) { // Exclude outer project itself if (resolvedImport.ImportingElement != null) { importPathsIncludingDuplicates.Add(resolvedImport.ImportedProject.FullPath); } } _importPathsIncludingDuplicates = importPathsIncludingDuplicates; ImportPathsIncludingDuplicates = importPathsIncludingDuplicates.AsReadOnly(); } /// /// Create environment variable properties snapshot /// private void CreateEnvironmentVariablePropertiesSnapshot(PropertyDictionary environmentVariableProperties) { _environmentVariableProperties = new PropertyDictionary(environmentVariableProperties.Count); foreach (ProjectPropertyInstance environmentProperty in environmentVariableProperties) { _environmentVariableProperties.Set(environmentProperty.DeepClone()); } } /// /// Create global properties snapshot /// private void CreateGlobalPropertiesSnapshot(PropertyDictionary globalPropertiesDictionary) { _globalProperties = new PropertyDictionary(globalPropertiesDictionary.Count); foreach (ProjectPropertyInstance globalProperty in globalPropertiesDictionary) { _globalProperties.Set(globalProperty.DeepClone()); } } /// /// Create evaluated include cache snapshot /// private void CreateEvaluatedIncludeSnapshotIfRequested(bool keepEvaluationCache, ICollection items, Dictionary projectItemToInstanceMap) { if (!keepEvaluationCache) { return; } var multiDictionary = new MultiDictionary(items.Count, StringComparer.OrdinalIgnoreCase); foreach (var item in items) { multiDictionary.Add(item.EvaluatedInclude, projectItemToInstanceMap[item]); } _itemsByEvaluatedInclude = multiDictionary; } /// /// Create Items snapshot /// private Dictionary CreateItemsSnapshot(ICollection items, int itemTypeCount, bool keepEvaluationCache) { _items = new ItemDictionary(itemTypeCount); var projectItemToInstanceMap = keepEvaluationCache ? new Dictionary(items.Count) : null; foreach (ProjectItem item in items) { ProjectItemInstance instance = InstantiateProjectItemInstance(item); _items.Add(instance); projectItemToInstanceMap?.Add(item, instance); } return projectItemToInstanceMap; } private ProjectItemInstance InstantiateProjectItemInstance(ProjectItem item) { List inheritedItemDefinitions = null; if (item.InheritedItemDefinitions != null) { inheritedItemDefinitions = new List(item.InheritedItemDefinitions.Count); foreach (ProjectItemDefinition inheritedItemDefinition in item.InheritedItemDefinitions) { // All item definitions in this list should be present in the collection of item definitions // on the project we are cloning. inheritedItemDefinitions.Add(_itemDefinitions[inheritedItemDefinition.ItemType]); } } ImmutableDictionary directMetadata = null; if (item.DirectMetadata != null) { IEnumerable> projectMetadataInstances = item.DirectMetadata.Select(directMetadatum => new KeyValuePair(directMetadatum.Name, directMetadatum.EvaluatedValueEscaped)); directMetadata = ImmutableDictionaryExtensions.EmptyMetadata .SetItems(projectMetadataInstances, ProjectMetadataInstance.VerifyThrowReservedName); } GetEvaluatedIncludesFromProjectItem( item, out string evaluatedIncludeEscaped, out string evaluatedIncludeBeforeWildcardExpansionEscaped); var instance = new ProjectItemInstance( this, item.ItemType, evaluatedIncludeEscaped, evaluatedIncludeBeforeWildcardExpansionEscaped, directMetadata, inheritedItemDefinitions, item.Xml.ContainingProject.EscapedFullPath, useItemDefinitionsWithoutModification: false); return instance; } private static void GetEvaluatedIncludesFromProjectItem( ProjectItem item, out string evaluatedIncludeEscaped, out string evaluatedIncludeBeforeWildcardExpansionEscaped) { // For externally constructed ProjectItem, fall back to the publicly available EvaluateInclude evaluatedIncludeEscaped = ((IItem)item).EvaluatedIncludeEscaped; evaluatedIncludeEscaped ??= item.EvaluatedInclude; evaluatedIncludeBeforeWildcardExpansionEscaped = item.EvaluatedIncludeBeforeWildcardExpansionEscaped; evaluatedIncludeBeforeWildcardExpansionEscaped ??= item.EvaluatedInclude; } private static ProjectItemInstance InstantiateProjectItemInstanceFromImmutableProjectSource( Project linkedProject, ProjectInstance projectInstance, ProjectItem item) { linkedProject.ItemDefinitions.TryGetValue(item.ItemType, out ProjectItemDefinition itemTypeDefinition); IList inheritedItemDefinitions = new ImmutableItemDefinitionsListConverter( item.InheritedItemDefinitions, itemTypeDefinition, ConvertCachedItemDefinitionToInstance); IReadOnlyDictionary directMetadata = null; if (item.DirectMetadata is not null) { if (item.DirectMetadata is IDictionary metadataDict) { directMetadata = new ImmutableProjectMetadataCollectionConverter(item, metadataDict); } else { IEnumerable> projectMetadataInstances = item.DirectMetadata.Select(directMetadatum => new KeyValuePair(directMetadatum.Name, directMetadatum.EvaluatedValueEscaped)); directMetadata = ImmutableDictionaryExtensions.EmptyMetadata .SetItems(projectMetadataInstances, ProjectMetadataInstance.VerifyThrowReservedName); } } GetEvaluatedIncludesFromProjectItem( item, out string evaluatedIncludeEscaped, out string evaluatedIncludeBeforeWildcardExpansionEscaped); ProjectItemInstance instance = new ProjectItemInstance( projectInstance, item.ItemType, evaluatedIncludeEscaped, evaluatedIncludeBeforeWildcardExpansionEscaped, directMetadata, inheritedItemDefinitions, item.Xml.ContainingProject.EscapedFullPath, useItemDefinitionsWithoutModification: true); return instance; } /// /// Create ItemDefinitions snapshot /// private void CreateItemDefinitionsSnapshot(IDictionary itemDefinitions) { _itemDefinitions = new RetrievableEntryHashSet(itemDefinitions.Count, MSBuildNameIgnoreCaseComparer.Default); foreach (ProjectItemDefinition definition in itemDefinitions.Values) { _itemDefinitions.Add(new ProjectItemDefinitionInstance(definition)); } } /// /// create property snapshot /// private void CreatePropertiesSnapshot(ICollection properties, bool isImmutable) { _properties = new PropertyDictionary(properties.Count); foreach (ProjectProperty property in properties) { ProjectPropertyInstance instance = InstantiateProjectPropertyInstance(property, isImmutable); _properties.Set(instance); } } internal class GenericLoggingContext : LoggingContext { public GenericLoggingContext(ILoggingService loggingService, BuildEventContext eventContext) : base(loggingService, eventContext) => IsValid = true; } } }