|
// 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.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 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;
/// <summary>
/// Enum for controlling project instance creation
/// </summary>
[Flags]
[SuppressMessage("Microsoft.Usage", "CA2217:DoNotMarkEnumsWithFlags", Justification = "ImmutableWithFastItemLookup is a variation on Immutable")]
public enum ProjectInstanceSettings
{
/// <summary>
/// no options
/// </summary>
None = 0x0,
/// <summary>
/// create immutable version of project instance
/// </summary>
Immutable = 0x1,
/// <summary>
/// create project instance with some look up table that improves performance
/// </summary>
ImmutableWithFastItemLookup = Immutable | 0x2
}
/// <summary>
/// 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.
/// </summary>
/// <comments>
/// 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.
/// </comments>
[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<ProjectPropertyInstance>, IItemProvider<ProjectItemInstance>, IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>, ITranslatable
{
/// <summary>
/// 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.
/// </summary>
private RetrievableEntryHashSet<ProjectTargetInstance> _actualTargets;
/// <summary>
/// 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 <see cref="_actualTargets">actualTargets</see>.
/// </summary>
private IDictionary<string, ProjectTargetInstance> _targets;
private List<string> _defaultTargets;
private List<string> _initialTargets;
private IList<string> _importPaths;
private IList<string> _importPathsIncludingDuplicates;
/// <summary>
/// The global properties evaluation occurred with.
/// Needed by the build as they traverse between projects.
/// </summary>
private PropertyDictionary<ProjectPropertyInstance> _globalProperties;
/// <summary>
/// List of names of the properties that, while global, are still treated as overridable
/// </summary>
private ISet<string> _globalPropertiesToTreatAsLocal;
/// <summary>
/// Whether the tools version used originated from an explicit specification,
/// for example from an MSBuild task or /tv switch.
/// </summary>
private bool _explicitToolsVersionSpecified;
/// <summary>
/// Properties in the project. This is a dictionary of name, value pairs.
/// </summary>
private PropertyDictionary<ProjectPropertyInstance> _properties;
/// <summary>
/// Properties originating from environment variables, gotten from the project collection
/// </summary>
private PropertyDictionary<ProjectPropertyInstance> _environmentVariableProperties;
/// <summary>
/// Items in the project. This is a dictionary of ordered lists of a single type of items keyed by item type.
/// </summary>
private IItemDictionary<ProjectItemInstance> _items;
/// <summary>
/// Items organized by evaluatedInclude value
/// </summary>
private IMultiDictionary<string, ProjectItemInstance> _itemsByEvaluatedInclude;
/// <summary>
/// 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.
/// </summary>
private string _directory;
/// <summary>
/// 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.
/// </summary>
private ElementLocation _projectFileLocation;
/// <summary>
/// The item definitions from the parent Project.
/// </summary>
private IRetrievableEntryHashSet<ProjectItemDefinitionInstance> _itemDefinitions;
/// <summary>
/// The HostServices to use during a build.
/// </summary>
private HostServices _hostServices;
/// <summary>
/// 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.
/// </summary>
private bool _usingDifferentToolsVersionFromProjectFile;
/// <summary>
/// The toolsversion that was originally on the project's Project root element
/// </summary>
private string _originalProjectToolsVersion;
/// <summary>
/// Whether the instance is immutable.
/// The object is always mutable during evaluation.
/// </summary>
private bool _isImmutable;
private IDictionary<string, List<TargetSpecification>> _beforeTargets;
private IDictionary<string, List<TargetSpecification>> _afterTargets;
private Toolset _toolset;
private string _subToolsetVersion;
private TaskRegistry _taskRegistry;
private bool _translateEntireState;
private int _evaluationId = BuildEventContext.InvalidEvaluationId;
/// <summary>
/// The property and item filter used when creating this instance, or null if this is not a filtered copy
/// of another ProjectInstance. <seealso cref="ProjectInstance(ProjectInstance, bool, RequestedProjectState)"/>
/// </summary>
private RequestedProjectState _requestedProjectStateFilter;
/// <summary>
/// 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.
/// </summary>
/// <param name="projectFile">The name of the project file.</param>
/// <returns>A new project instance</returns>
public ProjectInstance(string projectFile)
: this(projectFile, null, (string)null)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="projectFile">The name of the project file.</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version.</param>
/// <returns>A new project instance</returns>
public ProjectInstance(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion)
: this(projectFile, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="projectFile">The name of the project file.</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version.</param>
/// <param name="projectCollection">Project collection</param>
/// <returns>A new project instance</returns>
public ProjectInstance(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection)
: this(projectFile, globalProperties, toolsVersion, null /* no sub-toolset version */, projectCollection)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="projectFile">The name of the project file.</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version.</param>
/// <param name="subToolsetVersion">The sub-toolset version, used in tandem with the ToolsVersion to determine the set of toolset properties.</param>
/// <param name="projectCollection">Project collection</param>
/// <returns>A new project instance</returns>
public ProjectInstance(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection)
: this(projectFile, globalProperties, toolsVersion, subToolsetVersion, projectCollection, projectLoadSettings: null, evaluationContext: null, directoryCacheFactory: null, interactive: false)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="projectFile">The path to the project file.</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version. May be <see langword="null"/>.</param>
/// <param name="subToolsetVersion">The sub-toolset version, used in tandem with <paramref name="toolsVersion"/> to determine the set of toolset properties. May be <see langword="null"/>.</param>
/// <param name="projectCollection">Project collection</param>
/// <param name="context">Context to evaluate inside, potentially sharing caches with other evaluations.</param>
/// <param name="interactive">Indicates if loading the project is allowed to interact with the user.</param>
internal ProjectInstance(string projectFile, IDictionary<string, string> 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)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="projectFile">The name of the project file.</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version.</param>
/// <param name="subToolsetVersion">The sub-toolset version, used in tandem with the ToolsVersion to determine the set of toolset properties.</param>
/// <param name="projectCollection">Project collection</param>
/// <param name="projectLoadSettings">Project load settings</param>
/// <param name="evaluationContext">The context to use for evaluation.</param>
/// <param name="directoryCacheFactory">The directory cache factory to use for file I/O.</param>
/// <param name="interactive">Indicates if loading the project is allowed to interact with the user.</param>
/// <returns>A new project instance</returns>
private ProjectInstance(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection,
ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive)
{
ErrorUtilities.VerifyThrowArgumentLength(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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="xml">The project root element</param>
/// <returns>A new project instance</returns>
public ProjectInstance(ProjectRootElement xml)
: this(xml, null, null, ProjectCollection.GlobalProjectCollection)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="xml">The project root element</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version.</param>
/// <param name="projectCollection">Project collection</param>
/// <returns>A new project instance</returns>
public ProjectInstance(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection)
: this(xml, globalProperties, toolsVersion, null, projectCollection)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="xml">The project root element</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version.</param>
/// <param name="subToolsetVersion">The sub-toolset version, used in tandem with the ToolsVersion to determine the set of toolset properties.</param>
/// <param name="projectCollection">Project collection</param>
/// <returns>A new project instance</returns>
public ProjectInstance(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection)
: this(xml, globalProperties, toolsVersion, subToolsetVersion, projectCollection, projectLoadSettings: null, evaluationContext: null, directoryCacheFactory: null, interactive: false)
{
}
/// <summary>
/// Creates a ProjectInstance from an external created <see cref="Project"/>.
/// Properties and items are cloned immediately and only the instance data is stored.
/// </summary>
public ProjectInstance(Project project, ProjectInstanceSettings settings)
{
ErrorUtilities.VerifyThrowInternalNull(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<ProjectPropertyInstance>(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<ProjectItemElement>(project.Items.Count);
foreach (var item in project.Items)
{
this.EvaluatedItemElements.Add(item.Xml);
}
_usingDifferentToolsVersionFromProjectFile = false;
_originalProjectToolsVersion = project.ToolsVersion;
_explicitToolsVersionSpecified = project.SubToolsetVersion != null;
_isImmutable = immutable;
}
/// <summary>
/// Creates a ProjectInstance from an immutable <see cref="Project"/>.
/// The resulting <see cref="ProjectInstance"/> object wraps the <see cref="Project"/>
/// object. Unlike the ProjectInstance(Project project, ProjectInstanceSettings settings)
/// constructor, the properties and items are not cloned.
/// </summary>
/// <param name="linkedProject">The immutable <see cref="Project"/>.</param>
/// <param name="fastItemLookupNeeded">Whether the fast item lookup cache is required.</param>
private ProjectInstance(Project linkedProject, bool fastItemLookupNeeded)
{
ErrorUtilities.VerifyThrowInternalNull(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<string, ProjectItem, ProjectItemInstance>(
linkedProject.GetItemsByEvaluatedInclude,
item => ConvertCachedProjectItemToInstance(linkedProject, this, item));
}
// GlobalProperties
var globalPropertiesRetrievableHashSet = new ImmutableGlobalPropertiesCollectionConverter(linkedProject.GlobalProperties, _properties);
_globalProperties = new PropertyDictionary<ProjectPropertyInstance>(globalPropertiesRetrievableHashSet);
// EnvironmentVariableProperties
_environmentVariableProperties = linkedProject.ProjectCollection.SharedReadOnlyEnvironmentProperties;
// Targets
_targets = linkedProject.Targets;
InitializeTargetsData(null, null, null, null);
// Imports
var importsListConverter = new ImmutableStringValuedListConverter<ResolvedImport>(linkedProject.Imports, GetImportFullPath);
_importPaths = importsListConverter;
ImportPaths = importsListConverter;
importsListConverter = new ImmutableStringValuedListConverter<ResolvedImport>(linkedProject.ImportsIncludingDuplicates, GetImportFullPath);
_importPathsIncludingDuplicates = importsListConverter;
ImportPathsIncludingDuplicates = importsListConverter;
Toolset = linkedProject.ProjectCollection.GetToolset(linkedProject.ToolsVersion);
SubToolsetVersion = linkedProject.SubToolsetVersion;
TaskRegistry = new TaskRegistry(Toolset, linkedProject.ProjectCollection.ProjectRootElementCache);
ProjectRootElementCache = linkedProject.ProjectCollection.ProjectRootElementCache;
EvaluatedItemElements = new List<ProjectItemElement>(linkedProject.Items.Count);
foreach (var item in linkedProject.Items)
{
EvaluatedItemElements.Add(item.Xml);
}
_usingDifferentToolsVersionFromProjectFile = false;
_originalProjectToolsVersion = linkedProject.ToolsVersion;
_explicitToolsVersionSpecified = linkedProject.SubToolsetVersion != null;
_isImmutable = true;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="xml">The project root element</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version.</param>
/// <param name="subToolsetVersion">The sub-toolset version, used in tandem with the ToolsVersion to determine the set of toolset properties.</param>
/// <param name="projectCollection">Project collection</param>
/// <param name="projectLoadSettings">Project load settings</param>
/// <param name="evaluationContext">The context to use for evaluation.</param>
/// <param name="directoryCacheFactory">The directory cache factory to use for file I/O.</param>
/// <param name="interactive">Indicates if loading the project is allowed to interact with the user.</param>
/// <returns>A new project instance</returns>
private ProjectInstance(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection,
ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive)
{
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);
}
/// <summary>
/// Creates a ProjectInstance directly. Used to generate solution metaprojects.
/// </summary>
/// <param name="projectFile">The full path to give to this project.</param>
/// <param name="projectToInheritFrom">The traversal project from which global properties and tools version will be inherited.</param>
/// <param name="globalProperties">An <see cref="IDictionary{String,String}"/> containing global properties.</param>
internal ProjectInstance(string projectFile, ProjectInstance projectToInheritFrom, IDictionary<string, string> globalProperties)
{
_projectFileLocation = ElementLocation.Create(projectFile);
_globalProperties = new PropertyDictionary<ProjectPropertyInstance>(globalProperties.Count);
this.Toolset = projectToInheritFrom.Toolset;
this.SubToolsetVersion = projectToInheritFrom.SubToolsetVersion;
_explicitToolsVersionSpecified = projectToInheritFrom._explicitToolsVersionSpecified;
_properties = new PropertyDictionary<ProjectPropertyInstance>(projectToInheritFrom._properties); // This brings along the reserved properties, which are important.
_items = new ItemDictionary<ProjectItemInstance>(); // We don't want any of the items. That would include things like ProjectReferences, which would just pollute our own.
_actualTargets = new RetrievableEntryHashSet<ProjectTargetInstance>(StringComparer.OrdinalIgnoreCase);
_targets = new ObjectModel.ReadOnlyDictionary<string, ProjectTargetInstance>(_actualTargets);
_environmentVariableProperties = projectToInheritFrom._environmentVariableProperties;
_itemDefinitions = new RetrievableEntryHashSet<ProjectItemDefinitionInstance>(projectToInheritFrom._itemDefinitions, MSBuildNameIgnoreCaseComparer.Default);
_hostServices = projectToInheritFrom._hostServices;
this.ProjectRootElementCache = projectToInheritFrom.ProjectRootElementCache;
_explicitToolsVersionSpecified = projectToInheritFrom._explicitToolsVersionSpecified;
this.InitialTargets = new List<string>();
this.DefaultTargets = new List<string>();
this.DefaultTargets.Add("Build");
this.TaskRegistry = projectToInheritFrom.TaskRegistry;
_isImmutable = projectToInheritFrom._isImmutable;
_importPaths = projectToInheritFrom._importPaths;
ImportPaths = new ObjectModel.ReadOnlyCollection<string>(_importPaths);
_importPathsIncludingDuplicates = projectToInheritFrom._importPathsIncludingDuplicates;
ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection<string>(_importPathsIncludingDuplicates);
this.EvaluatedItemElements = new List<ProjectItemElement>();
IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance> thisAsIEvaluatorData = this;
thisAsIEvaluatorData.AfterTargets = new Dictionary<string, List<TargetSpecification>>();
thisAsIEvaluatorData.BeforeTargets = new Dictionary<string, List<TargetSpecification>>();
foreach (KeyValuePair<string, string> property in globalProperties)
{
_globalProperties[property.Key] = ProjectPropertyInstance.Create(property.Key, property.Value, false /* may not be reserved */, _isImmutable);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="xml">The project root element</param>
/// <param name="globalProperties">The global properties to use.</param>
/// <param name="toolsVersion">The tools version.</param>
/// <param name="visualStudioVersionFromSolution">The version of the solution, used to help determine which sub-toolset to use.</param>
/// <param name="projectCollection">Project collection</param>
/// <param name="sdkResolverService">An <see cref="ISdkResolverService"/> instance to use when resolving SDKs.</param>
/// <param name="submissionId">The current build submission ID.</param>
/// <returns>A new project instance</returns>
internal ProjectInstance(ProjectRootElement xml, IDictionary<string, string> 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);
}
/// <summary>
/// Initializes a new instance of the <see cref="ProjectInstance"/> 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.
/// </summary>
internal ProjectInstance(ProjectRootElement xml, IDictionary<string, string> 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);
}
/// <summary>
/// Creates a mutable ProjectInstance directly, using the specified logging service.
/// Assumes the project path is already normalized.
/// Used by the RequestBuilder.
/// </summary>
internal ProjectInstance(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext buildEventContext, ISdkResolverService sdkResolverService, int submissionId, ProjectLoadSettings? projectLoadSettings)
{
ErrorUtilities.VerifyThrowArgumentLength(projectFile);
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion));
ErrorUtilities.VerifyThrowArgumentNull(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);
}
/// <summary>
/// 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.
/// </summary>
internal ProjectInstance(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext buildEventContext, ISdkResolverService sdkResolverService, int submissionId)
{
ErrorUtilities.VerifyThrowArgumentNull(xml);
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion));
ErrorUtilities.VerifyThrowArgumentNull(buildParameters);
Initialize(xml, globalProperties, toolsVersion, null, 0 /* no solution version specified */, buildParameters, loggingService, buildEventContext, sdkResolverService, submissionId);
}
/// <summary>
/// Constructor called by Project's constructor to create a fresh instance.
/// Properties and items are cloned immediately and only the instance data is stored.
/// </summary>
internal ProjectInstance(Evaluation.Project.Data data, string directory, string fullPath, HostServices hostServices, PropertyDictionary<ProjectPropertyInstance> environmentVariableProperties, ProjectInstanceSettings settings)
{
ErrorUtilities.VerifyThrowInternalNull(data);
ErrorUtilities.VerifyThrowInternalLength(directory, nameof(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<ProjectProperty>(data.Properties), immutable);
this.CreateItemDefinitionsSnapshot(data.ItemDefinitions);
var keepEvaluationCache = (settings & ProjectInstanceSettings.ImmutableWithFastItemLookup) == ProjectInstanceSettings.ImmutableWithFastItemLookup;
var projectItemToInstanceMap = this.CreateItemsSnapshot(new ReadOnlyCollection<ProjectItem>(data.Items), data.ItemTypes.Count, keepEvaluationCache);
this.CreateEvaluatedIncludeSnapshotIfRequested(keepEvaluationCache, new ReadOnlyCollection<ProjectItem>(data.Items), projectItemToInstanceMap);
this.CreateGlobalPropertiesSnapshot(data.GlobalPropertiesDictionary);
this.CreateEnvironmentVariablePropertiesSnapshot(environmentVariableProperties);
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<ProjectItemElement>(data.EvaluatedItemElements);
_usingDifferentToolsVersionFromProjectFile = data.UsingDifferentToolsVersionFromProjectFile;
_originalProjectToolsVersion = data.OriginalProjectToolsVersion;
_explicitToolsVersionSpecified = data.ExplicitToolsVersion != null;
_isImmutable = immutable;
}
/// <summary>
/// Constructor for deserialization.
/// </summary>
private ProjectInstance(ITranslator translator)
{
((ITranslatable)this).Translate(translator);
}
/// <summary>
/// Deep clone of this object.
/// Useful for compiling a single file; or for keeping resolved assembly references between builds.
/// </summary>
private ProjectInstance(ProjectInstance that, bool isImmutable, RequestedProjectState filter = null)
{
ErrorUtilities.VerifyThrow(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<ProjectPropertyInstance>(that._properties.Count);
foreach (ProjectPropertyInstance property in that.Properties)
{
_properties.Set(property.DeepClone(_isImmutable));
}
_items = new ItemDictionary<ProjectItemInstance>(that._items.Count);
foreach (ProjectItemInstance item in that.Items)
{
_items.Add(item.DeepClone(this));
}
_globalProperties = new PropertyDictionary<ProjectPropertyInstance>(that._globalProperties.Count);
foreach (ProjectPropertyInstance globalProperty in that.GlobalPropertiesDictionary)
{
_globalProperties.Set(globalProperty.DeepClone(_isImmutable));
}
_environmentVariableProperties =
new PropertyDictionary<ProjectPropertyInstance>(that._environmentVariableProperties.Count);
foreach (ProjectPropertyInstance environmentProperty in that._environmentVariableProperties)
{
_environmentVariableProperties.Set(environmentProperty.DeepClone(_isImmutable));
}
this.DefaultTargets = new List<string>(that.DefaultTargets);
this.InitialTargets = new List<string>(that.InitialTargets);
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance,
ProjectItemDefinitionInstance>)this).BeforeTargets = CreateCloneDictionary(
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance,
ProjectItemDefinitionInstance>)that).BeforeTargets, StringComparer.OrdinalIgnoreCase);
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance,
ProjectItemDefinitionInstance>)this).AfterTargets = CreateCloneDictionary(
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance,
ProjectItemDefinitionInstance>)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<string>(_importPaths);
_importPathsIncludingDuplicates = that._importPathsIncludingDuplicates;
ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection<string>(_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<ProjectPropertyInstance>(filter.PropertyFilters.Count);
_globalProperties = new PropertyDictionary<ProjectPropertyInstance>(filter.PropertyFilters.Count);
_environmentVariableProperties =
new PropertyDictionary<ProjectPropertyInstance>(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.GetProperty(desiredProperty);
if (environmentProperty != null)
{
_environmentVariableProperties.Set(environmentProperty.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<ProjectItemInstance>(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.MetadataNames)
{
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;
}
}
}
/// <summary>
/// Create a file based ProjectInstance.
/// </summary>
/// <param name="file">The file to evaluate the ProjectInstance from.</param>
/// <param name="options">The <see cref="ProjectOptions"/> to use.</param>
/// <returns></returns>
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);
}
/// <summary>
/// Create a <see cref="ProjectRootElement"/> based ProjectInstance.
/// </summary>
/// <param name="rootElement">The <see cref="ProjectRootElement"/> to evaluate the ProjectInstance from.</param>
/// <param name="options">The <see cref="ProjectOptions"/> to use.</param>
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);
}
/// <summary>
/// Create a ProjectInstance from an immutable project source.
/// </summary>
/// <param name="project">The immutable <see cref="Project"/> on which the ProjectInstance is based.</param>
/// <param name="settings">The <see cref="ProjectInstanceSettings"/> to use.</param>
public static ProjectInstance FromImmutableProjectSource(Project project, ProjectInstanceSettings settings)
{
bool fastItemLookupNeeded = settings.HasFlag(ProjectInstanceSettings.ImmutableWithFastItemLookup);
return new ProjectInstance(project, fastItemLookupNeeded);
}
private static IRetrievableEntryHashSet<ProjectItemDefinitionInstance> GetImmutableItemDefinitionsHashSetFromImmutableProject(Project linkedProject)
{
IDictionary<string, ProjectItemDefinition> linkedProjectItemDefinitions = linkedProject.ItemDefinitions;
VerifyCollectionImplementsRequiredDictionaryInterfaces(
linkedProjectItemDefinitions,
out IDictionary<string, ProjectItemDefinition> elementsDictionary,
out IDictionary<(string, int, int), ProjectItemDefinition> constrainedElementsDictionary);
var hashSet = new ImmutableElementCollectionConverter<ProjectItemDefinition, ProjectItemDefinitionInstance>(
elementsDictionary,
constrainedElementsDictionary,
ConvertCachedItemDefinitionToInstance);
return hashSet;
}
private static ImmutableItemDictionary<ProjectItem, ProjectItemInstance> GetImmutableItemsDictionaryFromImmutableProject(
Project linkedProject,
ProjectInstance owningProjectInstance)
{
var itemsByType = linkedProject.Items as IDictionary<string, ICollection<ProjectItem>>;
if (itemsByType == null)
{
throw new ArgumentException(nameof(linkedProject));
}
Func<ProjectItem, ProjectItemInstance> convertCachedItemToInstance =
projectItem => ConvertCachedProjectItemToInstance(linkedProject, owningProjectInstance, projectItem);
var itemDictionary = new ImmutableItemDictionary<ProjectItem, ProjectItemInstance>(
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<ProjectItemInstance> 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<ProjectItemDefinitionInstance> instanceProvider)
{
result = instanceProvider.ImmutableInstance;
if (result == null)
{
IDictionary<string, ProjectMetadataInstance> metadata = null;
if (projectItemDefinition.Metadata is IDictionary<string, ProjectMetadata> linkedMetadataDict)
{
metadata = new ImmutableElementCollectionConverter<ProjectMetadata, ProjectMetadataInstance>(
linkedMetadataDict,
constrainedProjectElements: null,
ConvertCachedProjectMetadataToInstance);
}
result = instanceProvider.GetOrSetImmutableInstance(
new ProjectItemDefinitionInstance(projectItemDefinition.ItemType, metadata));
}
}
return result;
}
private static ProjectMetadataInstance ConvertCachedProjectMetadataToInstance(ProjectMetadata projectMetadata)
{
ProjectMetadataInstance result = null;
if (projectMetadata is IImmutableInstanceProvider<ProjectMetadataInstance> instanceProvider)
{
result = instanceProvider.ImmutableInstance;
if (result == null)
{
result = instanceProvider.GetOrSetImmutableInstance(new ProjectMetadataInstance(projectMetadata));
}
}
return result;
}
private static PropertyDictionary<ProjectPropertyInstance> GetImmutablePropertyDictionaryFromImmutableProject(Project linkedProject)
{
ICollection<ProjectProperty> linkedProjectProperties = linkedProject.Properties;
VerifyCollectionImplementsRequiredDictionaryInterfaces(
linkedProjectProperties,
out IDictionary<string, ProjectProperty> elementsDictionary,
out IDictionary<(string, int, int), ProjectProperty> constrainedElementsDictionary);
var hashSet = new ImmutableValuedElementCollectionConverter<ProjectProperty, ProjectPropertyInstance>(
elementsDictionary,
constrainedElementsDictionary,
ConvertCachedPropertyToInstance);
return new PropertyDictionary<ProjectPropertyInstance>(hashSet);
}
private static ProjectPropertyInstance ConvertCachedPropertyToInstance(ProjectProperty property)
{
ProjectPropertyInstance result = null;
if (property is IImmutableInstanceProvider<ProjectPropertyInstance> instanceProvider)
{
result = instanceProvider.ImmutableInstance;
if (result == null)
{
result = instanceProvider.GetOrSetImmutableInstance(InstantiateProjectPropertyInstance(property, isImmutable: true));
}
}
return result;
}
private static void VerifyCollectionImplementsRequiredDictionaryInterfaces<TCached>(
object elementsCollection,
out IDictionary<string, TCached> 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<string, TCached> 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<T>.
// That IRetrievableEntryHashSet is then used either directly or as a backing source for
// another collection wrapper (e.g. PropertyDictionary).
if (elementsCollection is not IDictionary<string, TCached> elementsDict ||
elementsCollection is not IDictionary<(string, int, int), TCached> constrainedElementsDict)
{
throw new ArgumentException(nameof(elementsCollection));
}
elementsDictionary = elementsDict;
constrainedElementsDictionary = constrainedElementsDict;
}
/// <summary>
/// Global properties this project was evaluated with, if any.
/// Read only collection.
/// Traverses project references.
/// </summary>
/// <remarks>
/// This is the publicly exposed getter, that translates into a read-only dead IDictionary<string, string>.
/// </remarks>
public IDictionary<string, string> GlobalProperties
{
[DebuggerStepThrough]
get
{
if (_globalProperties == null /* cached */ || _globalProperties.Count == 0)
{
return ReadOnlyEmptyDictionary<string, string>.Instance;
}
Dictionary<string, string> dictionary = new Dictionary<string, string>(_globalProperties.Count, MSBuildNameIgnoreCaseComparer.Default);
foreach (ProjectPropertyInstance property in _globalProperties)
{
dictionary[property.Name] = ((IProperty)property).EvaluatedValueEscaped;
}
return new ObjectModel.ReadOnlyDictionary<string, string>(dictionary);
}
}
/// <summary>
/// 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.
/// </summary>
public string ToolsVersion
{
get { return Toolset.ToolsVersion; }
}
/// <summary>
/// Enumerator over item types of the items in this project
/// </summary>
public ICollection<string> ItemTypes
{
[DebuggerStepThrough]
get
{
// KeyCollection, which is already read-only
return _items.ItemTypes;
}
}
bool IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.CanEvaluateElementsWithFalseConditions => false;
/// <summary>
/// Enumerator over properties in this project
/// </summary>
public ICollection<ProjectPropertyInstance> Properties
{
[DebuggerStepThrough]
get
{
return (_properties == null) ?
(ICollection<ProjectPropertyInstance>)ReadOnlyEmptyCollection<ProjectPropertyInstance>.Instance :
new ReadOnlyCollection<ProjectPropertyInstance>(_properties);
}
}
/// <summary>
/// Enumerator over items in this project.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This is a reasonable choice. API review approved")]
public ICollection<ProjectItemInstance> Items
{
[DebuggerStepThrough]
get
{
return (_items == null) ?
(ICollection<ProjectItemInstance>)ReadOnlyEmptyCollection<ProjectItemInstance>.Instance :
new ReadOnlyCollection<ProjectItemInstance>(_items);
}
}
/// <summary>
/// Gets a <see cref="List{ProjectItemElement}"/> object containing evaluated items.
/// </summary>
public List<ProjectItemElement> EvaluatedItemElements
{
get;
private set;
}
/// <summary>
/// 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.
/// </summary>
public bool TranslateEntireState
{
get => _translateEntireState;
set => _translateEntireState = value;
}
/// <summary>
/// The ID of the evaluation that produced this ProjectInstance.
///
/// See <see cref="Project.LastEvaluationId"/>.
/// </summary>
public int EvaluationId
{
get { return _evaluationId; }
set { _evaluationId = value; }
}
/// <summary>
/// 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.
/// </summary>
public string Directory
{
[DebuggerStepThrough]
get
{ return _directory; }
}
/// <summary>
/// The full path to the project, for logging.
/// If the project was never given a path, returns empty string.
/// </summary>
public string FullPath
{
[DebuggerStepThrough]
get => _projectFileLocation?.File ?? string.Empty;
}
/// <summary>
/// Read-only dictionary of item definitions in this project.
/// Keyed by item type
/// </summary>
public IDictionary<string, ProjectItemDefinitionInstance> ItemDefinitions
{
[DebuggerStepThrough]
get
{ return _itemDefinitions; }
}
/// <summary>
/// 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.
/// </summary>
public IReadOnlyList<string> ImportPaths { get; private set; }
/// <summary>
/// This list will contain duplicate imports if an import is imported multiple times. However, only the first import was used in evaluation.
/// </summary>
public IReadOnlyList<string> ImportPathsIncludingDuplicates { get; private set; }
/// <summary>
/// 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.
/// </summary>
public List<string> DefaultTargets
{
get { return _defaultTargets; }
private set { _defaultTargets = value; }
}
/// <summary>
/// InitialTargets specified in the project, plus those
/// in all imports, gathered depth-first.
/// The build runs these before anything else.
/// </summary>
public List<string> InitialTargets
{
get { return _initialTargets; }
private set { _initialTargets = value; }
}
/// <summary>
/// Targets in the project. The build process can find one by looking for its name
/// in the dictionary.
/// This collection is read-only.
/// </summary>
public IDictionary<string, ProjectTargetInstance> Targets
{
[DebuggerStepThrough]
get
{ return _targets; }
}
/// <summary>
/// Whether the instance is immutable.
/// This is set permanently when the instance is created.
/// </summary>
public bool IsImmutable
{
get { return _isImmutable; }
}
/// <summary>
/// The property and item filter used when creating this instance, or null if this is not a filtered copy
/// of another ProjectInstance. <seealso cref="ProjectInstance(ProjectInstance, bool, RequestedProjectState)"/>
/// </summary>
internal RequestedProjectState RequestedProjectStateFilter => _requestedProjectStateFilter;
/// <summary>
/// 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.
/// </summary>
TaskRegistry IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.TaskRegistry
{
[DebuggerStepThrough]
get
{
return TaskRegistry;
}
set
{
TaskRegistry = value;
}
}
/// <summary>
/// Gets the Toolset
/// </summary>
Toolset IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.Toolset
{
[DebuggerStepThrough]
get
{ return Toolset; }
}
/// <summary>
/// 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.
/// </summary>
string IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.SubToolsetVersion
{
[DebuggerStepThrough]
get
{ return SubToolsetVersion; }
}
/// <summary>
/// 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.
/// </summary>
string IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.ExplicitToolsVersion
{
[DebuggerStepThrough]
get
{ return ExplicitToolsVersion; }
}
/// <summary>
/// Gets the global properties
/// </summary>
PropertyDictionary<ProjectPropertyInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.GlobalPropertiesDictionary
{
[DebuggerStepThrough]
get
{ return _globalProperties; }
}
PropertyDictionary<ProjectPropertyInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.EnvironmentVariablePropertiesDictionary
{
get => _environmentVariableProperties;
}
/// <summary>
/// List of names of the properties that, while global, are still treated as overridable
/// </summary>
ISet<string> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.GlobalPropertiesToTreatAsLocal
{
get
{
if (_globalPropertiesToTreatAsLocal == null)
{
_globalPropertiesToTreatAsLocal = new HashSet<string>(MSBuildNameIgnoreCaseComparer.Default);
}
return _globalPropertiesToTreatAsLocal;
}
}
/// <summary>
/// Gets the properties
/// </summary>
PropertyDictionary<ProjectPropertyInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.Properties
{
[DebuggerStepThrough]
get
{ return _properties; }
}
/// <summary>
/// Gets the item definitions
/// </summary>
IEnumerable<ProjectItemDefinitionInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.ItemDefinitionsEnumerable
{
[DebuggerStepThrough]
get
{ return _itemDefinitions.Values; }
}
/// <summary>
/// Gets the items
/// </summary>
IItemDictionary<ProjectItemInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.Items
{
[DebuggerStepThrough]
get
{ return _items; }
}
/// <summary>
/// Sets the initial targets
/// Only set during evaluation, so does not check for immutability.
/// </summary>
List<string> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.InitialTargets
{
[DebuggerStepThrough]
get
{
return InitialTargets;
}
set
{
InitialTargets = value;
}
}
/// <summary>
/// Gets or sets the default targets
/// Only set during evaluation, so does not check for immutability.
/// </summary>
List<string> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.DefaultTargets
{
[DebuggerStepThrough]
get
{
return DefaultTargets;
}
set
{
DefaultTargets = value;
}
}
/// <summary>
/// Gets or sets the before targets
/// Only set during evaluation, so does not check for immutability.
/// </summary>
IDictionary<string, List<TargetSpecification>> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.BeforeTargets
{
get { return _beforeTargets; }
set { _beforeTargets = value; }
}
/// <summary>
/// Gets or sets the after targets
/// Only set during evaluation, so does not check for immutability.
/// </summary>
IDictionary<string, List<TargetSpecification>> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.AfterTargets
{
get { return _afterTargets; }
set { _afterTargets = value; }
}
/// <summary>
/// List of possible values for properties inferred from certain conditions,
/// keyed by the property name.
/// </summary>
/// <remarks>
/// Because ShouldEvaluateForDesignTime returns false, this should not be called.
/// </remarks>
Dictionary<string, List<string>> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.ConditionedProperties
{
get
{
ErrorUtilities.ThrowInternalErrorUnreachable();
return null;
}
}
/// <summary>
/// Whether evaluation should collect items ignoring condition,
/// as well as items respecting condition; and collect
/// conditioned properties, as well as regular properties
/// </summary>
bool IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.ShouldEvaluateForDesignTime
{
get { return false; }
}
/// <summary>
/// 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.
/// </summary>
public ElementLocation ProjectFileLocation
{
get { return _projectFileLocation; }
}
/// <summary>
/// Gets the global properties this project was evaluated with, if any.
/// Traverses project references.
/// </summary>
internal PropertyDictionary<ProjectPropertyInstance> GlobalPropertiesDictionary
{
[DebuggerStepThrough]
get
{ return _globalProperties; }
}
/// <summary>
/// The tools version we should use during the build, used to determine which toolset we should access.
/// </summary>
internal Toolset Toolset
{
get { return _toolset; }
private set { _toolset = value; }
}
/// <summary>
/// If we are treating a missing toolset as the current ToolsVersion
/// </summary>
internal bool UsingDifferentToolsVersionFromProjectFile
{
get { return _usingDifferentToolsVersionFromProjectFile; }
}
/// <summary>
/// The toolsversion that was originally specified on the project's root element
/// </summary>
internal string OriginalProjectToolsVersion
{
get { return _originalProjectToolsVersion; }
}
/// <summary>
/// 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.
/// </summary>
internal string ExplicitToolsVersion
{
get { return _explicitToolsVersionSpecified ? Toolset.ToolsVersion : null; }
}
/// <summary>
/// Whether the tools version used originated from an explicit specification,
/// for example from an MSBuild task or /tv switch.
/// </summary>
internal bool ExplicitToolsVersionSpecified
{
get { return _explicitToolsVersionSpecified; }
}
/// <summary>
/// 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.
/// </summary>
internal string SubToolsetVersion
{
get { return _subToolsetVersion; }
private set { _subToolsetVersion = value; }
}
/// <summary>
/// Actual collection of properties in this project,
/// for the build to start with.
/// </summary>
internal PropertyDictionary<ProjectPropertyInstance> PropertiesToBuildWith
{
[DebuggerStepThrough]
get
{ return _properties; }
}
internal ICollection<ProjectPropertyInstance> TestEnvironmentalProperties => new ReadOnlyCollection<ProjectPropertyInstance>(_environmentVariableProperties);
/// <summary>
/// Actual collection of items in this project,
/// for the build to start with.
/// </summary>
internal IItemDictionary<ProjectItemInstance> ItemsToBuildWith
{
[DebuggerStepThrough]
get
{ return _items; }
}
/// <summary>
/// Task classes and locations known to this project.
/// This is the project-specific task registry, which is consulted before
/// the toolset's task registry.
/// </summary>
/// <remarks>
/// UsingTask tags have already been evaluated and entered into this task registry.
/// </remarks>
internal TaskRegistry TaskRegistry
{
get { return _taskRegistry; }
private set { _taskRegistry = value; }
}
/// <summary>
/// Number of targets in the project.
/// </summary>
internal int TargetsCount
{
get { return _targets.Count; }
}
/// <summary>
/// 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.
/// </summary>
internal ProjectRootElementCacheBase ProjectRootElementCache
{
get;
private set;
}
/// <summary>
/// Returns the evaluated, escaped value of the provided item's include.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IItem is an internal interface; this is less confusing to outside customers. ")]
public static string GetEvaluatedItemIncludeEscaped(ProjectItemInstance item)
{
ErrorUtilities.VerifyThrowArgumentNull(item);
return ((IItem)item).EvaluatedIncludeEscaped;
}
/// <summary>
/// Returns the evaluated, escaped value of the provided item definition's include.
/// </summary>
public static string GetEvaluatedItemIncludeEscaped(ProjectItemDefinitionInstance item)
{
ErrorUtilities.VerifyThrowArgumentNull(item);
return ((IItem)item).EvaluatedIncludeEscaped;
}
/// <summary>
/// Gets the escaped value of the provided metadatum.
/// </summary>
public static string GetMetadataValueEscaped(ProjectMetadataInstance metadatum)
{
ErrorUtilities.VerifyThrowArgumentNull(metadatum);
return metadatum.EvaluatedValueEscaped;
}
/// <summary>
/// Gets the escaped value of the metadatum with the provided name on the provided item.
/// </summary>
[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)
{
ErrorUtilities.VerifyThrowArgumentNull(item);
return ((IItem)item).GetMetadataValueEscaped(name);
}
/// <summary>
/// Gets the escaped value of the metadatum with the provided name on the provided item definition.
/// </summary>
public static string GetMetadataValueEscaped(ProjectItemDefinitionInstance item, string name)
{
ErrorUtilities.VerifyThrowArgumentNull(item);
return ((IItem)item).GetMetadataValueEscaped(name);
}
/// <summary>
/// Get the escaped value of the provided property
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IProperty is an internal interface; this is less confusing to outside customers. ")]
public static string GetPropertyValueEscaped(ProjectPropertyInstance property)
{
ErrorUtilities.VerifyThrowArgumentNull(property);
return ((IProperty)property).EvaluatedValueEscaped;
}
/// <summary>
/// Gets items of the specified type.
/// For internal use.
/// </summary>
/// <comments>
/// Already a readonly collection
/// </comments>
ICollection<ProjectItemInstance> IItemProvider<ProjectItemInstance>.GetItems(string itemType)
{
return _items[itemType];
}
/// <summary>
/// Initializes the object for evaluation.
/// Only called during evaluation, so does not check for immutability.
/// </summary>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.
InitializeForEvaluation(IToolsetProvider toolsetProvider, EvaluationContext evaluationContext, LoggingContext loggingContext)
{
// All been done in the constructor. We don't allow re-evaluation of project instances.
}
/// <summary>
/// Indicates to the data block that evaluation has completed,
/// so for example it can mark datastructures read-only.
/// </summary>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.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.
}
/// <summary>
/// Adds a new item
/// Only called during evaluation, so does not check for immutability.
/// </summary>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.AddItem(ProjectItemInstance item)
{
_items.Add(item);
}
/// <summary>
/// Adds a new item to the collection of all items ignoring condition
/// </summary>
/// <remarks>
/// Because ShouldEvaluateForDesignTime returns false, this should not be called.
/// </remarks>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.AddItemIgnoringCondition(ProjectItemInstance item)
{
ErrorUtilities.ThrowInternalErrorUnreachable();
}
/// <summary>
/// Adds a new item definition
/// Only called during evaluation, so does not check for immutability.
/// </summary>
IItemDefinition<ProjectMetadataInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.AddItemDefinition(string itemType)
{
ProjectItemDefinitionInstance itemDefinitionInstance = new ProjectItemDefinitionInstance(itemType);
_itemDefinitions.Add(itemDefinitionInstance);
return itemDefinitionInstance;
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// Because ShouldEvaluateForDesignTime returns false, this should not be called.
/// </remarks>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.AddToAllEvaluatedPropertiesList(ProjectPropertyInstance property)
{
ErrorUtilities.ThrowInternalErrorUnreachable();
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// Because ShouldEvaluateForDesignTime returns false, this should not be called.
/// </remarks>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.AddToAllEvaluatedItemDefinitionMetadataList(ProjectMetadataInstance itemDefinitionMetadatum)
{
ErrorUtilities.ThrowInternalErrorUnreachable();
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// Because ShouldEvaluateForDesignTime returns false, this should not be called.
/// </remarks>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.AddToAllEvaluatedItemsList(ProjectItemInstance item)
{
ErrorUtilities.ThrowInternalErrorUnreachable();
}
/// <summary>
/// Retrieves an existing item definition, if any.
/// </summary>
IItemDefinition<ProjectMetadataInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.GetItemDefinition(string itemType)
{
ProjectItemDefinitionInstance itemDefinitionInstance;
_itemDefinitions.TryGetValue(itemType, out itemDefinitionInstance);
return itemDefinitionInstance;
}
/// <summary>
/// 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.
/// </summary>
ProjectPropertyInstance IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, LoggingContext loggingContext, bool isEnvironmentVariable)
{
// 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;
}
/// <summary>
/// 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.
/// </summary>
ProjectPropertyInstance IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.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;
}
/// <summary>
/// Retrieves an existing target, if any.
/// </summary>
ProjectTargetInstance IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.GetTarget(string targetName)
{
ProjectTargetInstance targetInstance;
_targets.TryGetValue(targetName, out targetInstance);
return targetInstance;
}
/// <summary>
/// Adds a new target.
/// Only called during evaluation, so does not check for immutability.
/// </summary>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.AddTarget(ProjectTargetInstance target)
{
_actualTargets[target.Name] = target;
}
/// <summary>
/// Record an import opened during evaluation.
/// </summary>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.RecordImport(
ProjectImportElement importElement,
ProjectRootElement import,
int versionEvaluated,
SdkResult sdkResult)
{
_importPaths.Add(import.FullPath);
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>)this).RecordImportWithDuplicates(importElement, import, versionEvaluated);
}
/// <summary>
/// Record an import opened during evaluation. Include duplicates
/// </summary>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.RecordImportWithDuplicates(ProjectImportElement importElement, ProjectRootElement import, int versionEvaluated)
{
_importPathsIncludingDuplicates.Add(import.FullPath);
}
/// <summary>
/// Get any property in the item that has the specified name,
/// otherwise returns null
/// </summary>
[DebuggerStepThrough]
public ProjectPropertyInstance GetProperty(string name)
{
return _properties[name];
}
/// <summary>
/// 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.
/// </summary>
[DebuggerStepThrough]
ProjectPropertyInstance IPropertyProvider<ProjectPropertyInstance>.GetProperty(string name, int startIndex, int endIndex)
{
return _properties.GetProperty(name, startIndex, endIndex);
}
/// <summary>
/// Get the value of a property in this project, or
/// an empty string if it does not exist.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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;
}
/// <summary>
/// Add a property with the specified name and value.
/// Overwrites any property with the same name already in the collection.
/// </summary>
/// <remarks>
/// We don't take a ProjectPropertyInstance to make sure we don't have one that's already
/// in use by another ProjectPropertyInstance.
/// </remarks>
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;
}
/// <summary>
/// Adds an item with no metadata to the project
/// </summary>
/// <remarks>
/// We don't take a ProjectItemInstance to make sure we don't have one that's already
/// in use by another ProjectInstance.
/// </remarks>
/// <comments>
/// 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.
/// </comments>
public ProjectItemInstance AddItem(string itemType, string evaluatedInclude)
{
VerifyThrowNotImmutable();
ProjectItemInstance item = new ProjectItemInstance(this, itemType, evaluatedInclude, this.FullPath);
_items.Add(item);
return item;
}
/// <summary>
/// Adds an item with metadata to the project.
/// Metadata may be null.
/// </summary>
/// <remarks>
/// We don't take a ProjectItemInstance to make sure we don't have one that's already
/// in use by another ProjectInstance.
/// </remarks>
/// <comments>
/// 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.
/// </comments>
public ProjectItemInstance AddItem(string itemType, string evaluatedInclude, IEnumerable<KeyValuePair<string, string>> metadata)
{
VerifyThrowNotImmutable();
ProjectItemInstance item = new ProjectItemInstance(this, itemType, evaluatedInclude, metadata, this.FullPath);
_items.Add(item);
return item;
}
/// <summary>
/// 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.
/// </summary>
public ICollection<ProjectItemInstance> GetItems(string itemType)
{
// GetItems already returns a readonly collection
return ((IItemProvider<ProjectItemInstance>)this).GetItems(itemType);
}
/// <summary>
/// get items by item type and evaluated include value
/// </summary>
public IEnumerable<ProjectItemInstance> 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;
}
}
}
}
/// <summary>
/// Removes an item from the project, if present.
/// Returns true if it was present, false otherwise.
/// </summary>
public bool RemoveItem(ProjectItemInstance item)
{
VerifyThrowNotImmutable();
return _items.Remove(item);
}
/// <summary>
/// Removes any property with the specified name.
/// Returns true if the property had a value (possibly empty string), otherwise false.
/// </summary>
public bool RemoveProperty(string name)
{
VerifyThrowNotImmutable();
return _properties.Remove(name);
}
/// <summary>
/// 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.
/// </summary>
public ProjectInstance DeepCopy()
{
return DeepCopy(_isImmutable);
}
/// <summary>
/// Create an independent clone of this object, keeping ONLY the explicitly
/// requested project state.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="filter">Project state that should be returned.</param>
/// <returns></returns>
public ProjectInstance FilteredCopy(RequestedProjectState filter)
{
return new ProjectInstance(this, true, filter);
}
/// <summary>
/// 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.
/// </summary>
public ProjectInstance DeepCopy(bool isImmutable)
{
if (isImmutable && _isImmutable)
{
// No need to clone
return this;
}
return new ProjectInstance(this, isImmutable);
}
/// <summary>
/// Build default target/s with loggers of the project collection.
/// Returns true on success, false on failure.
/// Only valid if mutable.
/// </summary>
public bool Build()
{
return Build(null);
}
/// <summary>
/// Build default target/s with specified loggers.
/// Returns true on success, false on failure.
/// Loggers may be null.
/// Only valid if mutable.
/// </summary>
/// <remarks>
/// If any of the loggers supplied are already attached to the logging service we
/// were passed, throws InvalidOperationException.
/// </remarks>
public bool Build(IEnumerable<ILogger> loggers)
{
return Build((string[])null, loggers, null);
}
/// <summary>
/// Build default target/s with specified loggers.
/// Returns true on success, false on failure.
/// Loggers may be null.
/// Only valid if mutable.
/// </summary>
/// <remarks>
/// If any of the loggers supplied are already attached to the logging service we
/// were passed, throws InvalidOperationException.
/// </remarks>
public bool Build(IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
{
return Build((string[])null, loggers, remoteLoggers);
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// If any of the loggers supplied are already attached to the logging service we
/// were passed, throws InvalidOperationException.
/// </remarks>
public bool Build(string target, IEnumerable<ILogger> loggers)
{
return Build(target, loggers, null);
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// If any of the loggers supplied are already attached to the logging service we
/// were passed, throws InvalidOperationException.
/// </remarks>
public bool Build(string target, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
{
string[] targets = (target == null) ? [] : [target];
return Build(targets, loggers, remoteLoggers);
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// If any of the loggers supplied are already attached to the logging service we
/// were passed, throws InvalidOperationException.
/// </remarks>
public bool Build(string[] targets, IEnumerable<ILogger> loggers)
{
return Build(targets, loggers, null);
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// If any of the loggers supplied are already attached to the logging service we
/// were passed, throws InvalidOperationException.
/// </remarks>
public bool Build(string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
{
IDictionary<string, TargetResult> targetOutputs;
return Build(targets, loggers, remoteLoggers, out targetOutputs);
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// If any of the loggers supplied are already attached to the logging service we
/// were passed, throws InvalidOperationException.
/// </remarks>
public bool Build(string[] targets, IEnumerable<ILogger> loggers, out IDictionary<string, TargetResult> targetOutputs)
{
return Build(targets, loggers, null, null, out targetOutputs);
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// If any of the loggers supplied are already attached to the logging service we
/// were passed, throws InvalidOperationException.
/// </remarks>
public bool Build(string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers, out IDictionary<string, TargetResult> targetOutputs)
{
return Build(targets, loggers, remoteLoggers, null, out targetOutputs);
}
/// <summary>
/// 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.
/// </summary>
/// <comment>
/// Not for internal use.
/// </comment>
public string ExpandString(string unexpandedValue)
{
Expander<ProjectPropertyInstance, ProjectItemInstance> expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(this, this, FileSystems.Default, _loggingContext);
string result = expander.ExpandIntoStringAndUnescape(unexpandedValue, ExpanderOptions.ExpandPropertiesAndItems, ProjectFileLocation);
return result;
}
/// <summary>
/// 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.
/// </summary>
/// <comment>
/// Not for internal use.
/// </comment>
public bool EvaluateCondition(string condition)
{
Expander<ProjectPropertyInstance, ProjectItemInstance> expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(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;
}
/// <summary>
/// Creates a ProjectRootElement from the contents of this ProjectInstance.
/// </summary>
/// <returns>A ProjectRootElement which represents this instance.</returns>
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))
{
property.ToProjectPropertyElement(propertyGroupElement);
}
}
}
}
// Add all of the targets.
foreach (ProjectTargetInstance target in Targets.Values)
{
target.ToProjectTargetElement(rootElement);
}
return rootElement;
}
/// <summary>
/// Replaces the project state (<see cref="GlobalProperties"/>, <see cref="Properties"/> and <see cref="Items"/>) with that
/// from the <see cref="ProjectInstance"/> provided.
/// </summary>
/// <param name="projectState"><see cref="ProjectInstance"/> with the state to use.</param>
public void UpdateStateFrom(ProjectInstance projectState)
{
_globalProperties = new PropertyDictionary<ProjectPropertyInstance>(projectState._globalProperties);
_properties = new PropertyDictionary<ProjectPropertyInstance>(projectState._properties);
_items = new ItemDictionary<ProjectItemInstance>(projectState._items);
}
internal bool IsLoaded => ProjectRootElementCache != null && TaskRegistry.IsLoaded;
/// <summary>
/// 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
/// </summary>
internal void LateInitialize(ProjectRootElementCacheBase projectRootElementCache, HostServices hostServices)
{
ErrorUtilities.VerifyThrow(ProjectRootElementCache == null, $"{nameof(ProjectRootElementCache)} is already set. Cannot set again");
ErrorUtilities.VerifyThrow(_hostServices == null, $"{nameof(HostServices)} is already set. Cannot set again");
ErrorUtilities.VerifyThrow(TaskRegistry != null, $"{nameof(TaskRegistry)} Cannot be null after {nameof(ProjectInstance)} object creation.");
ProjectRootElementCache = projectRootElementCache;
_taskRegistry.RootElementCache = projectRootElementCache;
_hostServices = hostServices;
}
#region INodePacketTranslatable Members
/// <summary>
/// Translate the project instance to or from a stream.
/// Only translates global properties, properties, items, and mutability.
/// </summary>
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<ProjectItemDefinitionInstance>(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<string>)_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<ProjectTargetInstance>(capacity, MSBuildNameIgnoreCaseComparer.Default));
translator.TranslateDictionary(ref _beforeTargets, TranslatorForTargetSpecificDictionaryKey, TranslatorForTargetSpecificDictionaryValue, count => new Dictionary<string, List<TargetSpecification>>(count));
translator.TranslateDictionary(ref _afterTargets, TranslatorForTargetSpecificDictionaryKey, TranslatorForTargetSpecificDictionaryValue, count => new Dictionary<string, List<TargetSpecification>>(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<TargetSpecification> 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<ProjectItemInstance>(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<ProjectItemInstance> 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
/// <summary>
/// Creates a set of project instances which represent the project dependency graph for a solution build.
/// </summary>
internal static ProjectInstance[] LoadSolutionForBuild(
string projectFile,
PropertyDictionary<ProjectPropertyInstance> globalPropertiesInstances,
string toolsVersion,
BuildParameters buildParameters,
ILoggingService loggingService,
BuildEventContext projectBuildEventContext,
bool isExplicitlyLoaded,
IReadOnlyCollection<string> targetNames,
ISdkResolverService sdkResolverService,
int submissionId)
{
ErrorUtilities.VerifyThrowArgumentLength(projectFile);
ErrorUtilities.VerifyThrowArgumentNull(globalPropertiesInstances);
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion));
ErrorUtilities.VerifyThrowArgumentNull(buildParameters);
ErrorUtilities.VerifyThrow(FileUtilities.IsSolutionFilename(projectFile), "Project file {0} is not a solution.", projectFile);
ProjectInstance[] projectInstances = null;
Dictionary<string, string> globalProperties = new Dictionary<string, string>(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;
}
private static ProjectInstance[] CalculateToolsVersionAndGenerateSolutionWrapper(
string projectFile,
BuildParameters buildParameters,
ILoggingService loggingService,
BuildEventContext projectBuildEventContext,
Dictionary<string, string> globalProperties,
bool isExplicitlyLoaded,
IReadOnlyCollection<string> 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 = visualStudioVersion.ToString(CultureInfo.InvariantCulture) + ".0";
}
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;
}
/// <summary>
/// Factory for deserialization.
/// </summary>
internal static ProjectInstance FactoryForDeserialization(ITranslator translator)
{
return new ProjectInstance(translator);
}
/// <summary>
/// Throws invalid operation exception if the project instance is immutable.
/// Called before an edit.
/// </summary>
internal static void VerifyThrowNotImmutable(bool isImmutable)
{
if (isImmutable)
{
ErrorUtilities.ThrowInvalidOperation("OM_ProjectInstanceImmutable");
}
}
/// <summary>
/// Builds a list of targets with the specified loggers.
/// </summary>
internal bool Build(string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers, ILoggingService loggingService, int maxNodeCount, out IDictionary<string, TargetResult> 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<ILogger> loggersCollection) ? loggersCollection : new List<ILogger>(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;
}
if (remoteLoggers != null)
{
parameters.ForwardingLoggers = remoteLoggers is ICollection<ForwardingLoggerRecord> records ?
records :
new List<ForwardingLoggerRecord>(remoteLoggers);
}
parameters.EnvironmentPropertiesInternal = _environmentVariableProperties;
parameters.ProjectRootElementCache = ProjectRootElementCache;
parameters.MaxNodeCount = maxNodeCount;
results = buildManager.Build(parameters, data);
targetOutputs = results.ResultsByTarget;
return results.OverallResult == BuildResultCode.Success;
}
/// <summary>
/// Builds a list of targets with the specified loggers.
/// </summary>
internal bool Build(string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers, ILoggingService loggingService, out IDictionary<string, TargetResult> targetOutputs)
{
return Build(targets, loggers, remoteLoggers, loggingService, 1, out targetOutputs);
}
/// <summary>
/// Retrieves the list of targets which should run before the specified target.
/// Never returns null.
/// </summary>
internal IList<TargetSpecification> GetTargetsWhichRunBefore(string target)
{
List<TargetSpecification> beforeTargetsForTarget;
if (((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>)this).BeforeTargets.TryGetValue(target, out beforeTargetsForTarget))
{
return beforeTargetsForTarget;
}
else
{
return Array.Empty<TargetSpecification>();
}
}
/// <summary>
/// Retrieves the list of targets which should run after the specified target.
/// Never returns null.
/// </summary>
internal IList<TargetSpecification> GetTargetsWhichRunAfter(string target)
{
List<TargetSpecification> afterTargetsForTarget;
if (((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>)this).AfterTargets.TryGetValue(target, out afterTargetsForTarget))
{
return afterTargetsForTarget;
}
else
{
return Array.Empty<TargetSpecification>();
}
}
/// <summary>
/// Cache the contents of this project instance to the translator.
/// The object is retained, but the bulk of its content is released.
/// </summary>
internal void Cache(ITranslator translator)
{
((ITranslatable)this).Translate(translator);
if (translator.Mode == TranslationDirection.WriteToStream)
{
_globalProperties = null;
_properties = null;
_items = null;
}
}
/// <summary>
/// Retrieve the contents of this project from the translator.
/// </summary>
internal void RetrieveFromCache(ITranslator translator)
{
((ITranslatable)this).Translate(translator);
}
/// <summary>
/// Adds the specified target to the instance.
/// </summary>
internal ProjectTargetInstance AddTarget(
string targetName,
string condition,
string inputs,
string outputs,
string returns,
string keepDuplicateOutputs,
string dependsOnTargets,
string beforeTargets,
string afterTargets,
bool parentProjectSupportsReturnsAttribute)
{
VerifyThrowNotImmutable();
ErrorUtilities.VerifyThrowInternalLength(targetName, nameof(targetName));
ErrorUtilities.VerifyThrow(!_actualTargets.ContainsKey(targetName), "Target {0} already exists.", targetName);
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<ProjectTargetInstanceChild>(new List<ProjectTargetInstanceChild>()),
new ObjectModel.ReadOnlyCollection<ProjectOnErrorInstance>(new List<ProjectOnErrorInstance>()),
parentProjectSupportsReturnsAttribute);
_actualTargets[targetName] = target;
return target;
}
/// <summary>
/// Removes the specified target from the instance.
/// </summary>
internal void RemoveTarget(string targetName)
{
VerifyThrowNotImmutable();
_actualTargets.Remove(targetName);
}
/// <summary>
/// Throws invalid operation exception if the project instance is immutable.
/// Called before an edit.
/// </summary>
internal void VerifyThrowNotImmutable()
{
VerifyThrowNotImmutable(_isImmutable);
}
/// <summary>
/// Generate a 4.0+-style solution wrapper project.
/// </summary>
/// <param name="projectFile">The solution file to generate a wrapper for.</param>
/// <param name="globalProperties">The global properties of this solution.</param>
/// <param name="toolsVersion">The ToolsVersion to use when generating the wrapper.</param>
/// <param name="loggingService">The logging service used to log messages etc. from the solution wrapper generator.</param>
/// <param name="projectBuildEventContext">The build event context in which this project is being constructed.</param>
/// <param name="targetNames">A collection of target names that the user requested be built.</param>
/// <param name="sdkResolverService"></param>
/// <param name="submissionId"></param>
/// <returns>The ProjectRootElement for the root traversal and each of the metaprojects.</returns>
private static ProjectInstance[] GenerateSolutionWrapper(
string projectFile,
IDictionary<string, string> globalProperties,
string toolsVersion,
ILoggingService loggingService,
BuildEventContext projectBuildEventContext,
IReadOnlyCollection<string> 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 <UsingTask> tags to put in, whether to put a ToolsVersion parameter
// on the <MSBuild> 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;
}
/// <summary>
/// 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.
/// </summary>
/// <comment>
/// #############################################################################################
/// #### 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
/// </comment>
/// <param name="projectFile">The solution file to generate a wrapper for.</param>
/// <param name="globalProperties">The global properties of this solution.</param>
/// <param name="toolsVersion">The ToolsVersion to use when generating the wrapper.</param>
/// <param name="projectRootElementCache">The root element cache which should be used for the generated project.</param>
/// <param name="buildParameters">The build parameters.</param>
/// <param name="loggingService">The logging service used to log messages etc. from the solution wrapper generator.</param>
/// <param name="projectBuildEventContext">The build event context in which this project is being constructed.</param>
/// <param name="isExplicitlyLoaded"><code>true</code> if the project is explicitly loaded, otherwise <code>false</code>.</param>
/// <param name="sdkResolverService">An <see cref="ISdkResolverService"/> to use when resolving SDKs.</param>
/// <param name="submissionId"></param>
/// <returns>An appropriate ProjectRootElement</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private static ProjectInstance[] GenerateSolutionWrapperUsingOldOM(
string projectFile,
IDictionary<string, string> 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 <UsingTask> tags to put in, whether to put a ToolsVersion parameter
// on the <MSBuild> task tags, and what MSBuildToolsPath to use when scanning child projects
// for dependency information.
string wrapperProjectXml;
List<DictionaryEntry> 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<DictionaryEntry>();
}
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];
}
}
/// <summary>
/// Creates a copy of a dictionary and returns a read-only dictionary around the results.
/// </summary>
/// <typeparam name="TValue">The value stored in the dictionary</typeparam>
/// <param name="dictionary">Dictionary to clone.</param>
/// <param name="strComparer">The <see cref="StringComparer"/> to use for the cloned dictionary.</param>
private static ObjectModel.ReadOnlyDictionary<string, TValue> CreateCloneDictionary<TValue>(IDictionary<string, TValue> dictionary, StringComparer strComparer)
{
Dictionary<string, TValue> clone;
if (dictionary == null)
{
clone = new Dictionary<string, TValue>(0);
}
else
{
clone = new Dictionary<string, TValue>(dictionary, strComparer);
}
return new ObjectModel.ReadOnlyDictionary<string, TValue>(clone);
}
/// <summary>
/// Creates a copy of a dictionary and returns a read-only dictionary around the results.
/// </summary>
/// <typeparam name="TValue">The value stored in the dictionary</typeparam>
/// <param name="dictionary">Dictionary to clone.</param>
private static IDictionary<string, TValue> CreateCloneDictionary<TValue>(IDictionary<string, TValue> dictionary) where TValue : class, IKeyed
{
if (dictionary == null)
{
return ReadOnlyEmptyDictionary<string, TValue>.Instance;
}
else
{
return new ObjectModel.ReadOnlyDictionary<string, TValue>(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;
}
/// <summary>
/// Logging context - set during the evaluation.
/// Can be null - especially if the project was fetched from the cache.
/// </summary>
private LoggingContext _loggingContext;
/// <summary>
/// Common code for the constructors that evaluate directly.
/// Global properties may be null.
/// Tools version may be null.
/// Does not set mutability.
/// </summary>
private void Initialize(
ProjectRootElement xml,
IDictionary<string, string> 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)
{
ErrorUtilities.VerifyThrowArgumentNull(xml);
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(explicitToolsVersion, "toolsVersion");
ErrorUtilities.VerifyThrowArgumentNull(buildParameters);
_directory = xml.DirectoryPath;
_projectFileLocation = xml.ProjectFileLocation ?? ElementLocation.EmptyLocation;
_properties = new PropertyDictionary<ProjectPropertyInstance>();
_items = new ItemDictionary<ProjectItemInstance>();
_actualTargets = new RetrievableEntryHashSet<ProjectTargetInstance>(StringComparer.OrdinalIgnoreCase);
_targets = new ObjectModel.ReadOnlyDictionary<string, ProjectTargetInstance>(_actualTargets);
_importPaths = new List<string>();
ImportPaths = new ObjectModel.ReadOnlyCollection<string>(_importPaths);
_importPathsIncludingDuplicates = new List<string>();
ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection<string>(_importPathsIncludingDuplicates);
_globalProperties = new PropertyDictionary<ProjectPropertyInstance>((globalProperties == null) ? 0 : globalProperties.Count);
_environmentVariableProperties = buildParameters.EnvironmentPropertiesInternal;
_itemDefinitions = new RetrievableEntryHashSet<ProjectItemDefinitionInstance>(MSBuildNameIgnoreCaseComparer.Default);
_hostServices = buildParameters.HostServices;
this.ProjectRootElementCache = buildParameters.ProjectRootElementCache;
_loggingContext = new GenericLoggingContext(loggingService, buildEventContext);
this.EvaluatedItemElements = new List<ProjectItemElement>();
_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<string, string> 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(String.Format(CultureInfo.InvariantCulture, "MSBUILD: Creating a ProjectInstance from an unevaluated state [{0}]", FullPath));
}
ErrorUtilities.VerifyThrow(EvaluationId == BuildEventContext.InvalidEvaluationId, "Evaluation ID is invalid prior to evaluation");
evaluationContext = evaluationContext?.ContextForNewProject() ?? EvaluationContext.Create(EvaluationContext.SharingPolicy.Isolated);
Evaluator<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.Evaluate(
data: this,
project: null,
xml,
projectLoadSettings ?? buildParameters.ProjectLoadSettings, /* Use override ProjectLoadSettings if specified */
buildParameters.MaxNodeCount,
buildParameters.EnvironmentPropertiesInternal,
loggingService,
new ProjectItemInstanceFactory(this),
buildParameters.ToolsetProvider,
directoryCacheFactory,
ProjectRootElementCache,
buildEventContext,
sdkResolverService ?? evaluationContext.SdkResolverService, /* Use override ISdkResolverService if specified */
submissionId,
evaluationContext,
interactive: buildParameters.Interactive);
ErrorUtilities.VerifyThrow(EvaluationId != BuildEventContext.InvalidEvaluationId, "Evaluation should produce an evaluation ID");
}
/// <summary>
/// Get items by evaluatedInclude value
/// </summary>
private IEnumerable<ProjectItemInstance> GetItemsByEvaluatedInclude(string evaluatedInclude)
{
// Even if there are no items in itemsByEvaluatedInclude[], it will return an IEnumerable, which is non-null
return _itemsByEvaluatedInclude[evaluatedInclude];
}
/// <summary>
/// Create various target snapshots
/// </summary>
private void CreateTargetsSnapshot(
IDictionary<string, ProjectTargetInstance> targets,
List<string> defaultTargets,
List<string> initialTargets,
IDictionary<string, List<TargetSpecification>> beforeTargets,
IDictionary<string, List<TargetSpecification>> afterTargets)
{
// ProjectTargetInstances are immutable so only the dictionary must be cloned
_targets = CreateCloneDictionary(targets);
InitializeTargetsData(defaultTargets, initialTargets, beforeTargets, afterTargets);
}
private void InitializeTargetsData(List<string> defaultTargets,
List<string> initialTargets,
IDictionary<string, List<TargetSpecification>> beforeTargets,
IDictionary<string, List<TargetSpecification>> afterTargets)
{
DefaultTargets = defaultTargets == null ? new List<string>(0) : new List<string>(defaultTargets);
InitialTargets = initialTargets == null ? new List<string>(0) : new List<string>(initialTargets);
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>)this).BeforeTargets = CreateCloneDictionary(beforeTargets, StringComparer.OrdinalIgnoreCase);
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>)this).AfterTargets = CreateCloneDictionary(afterTargets, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Create various imports snapshots
/// </summary>
private void CreateImportsSnapshot(IList<ResolvedImport> importClosure, IList<ResolvedImport> importClosureWithDuplicates)
{
var importPaths = new List<string>(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<string>(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();
}
/// <summary>
/// Create environment variable properties snapshot
/// </summary>
private void CreateEnvironmentVariablePropertiesSnapshot(PropertyDictionary<ProjectPropertyInstance> environmentVariableProperties)
{
_environmentVariableProperties = new PropertyDictionary<ProjectPropertyInstance>(environmentVariableProperties.Count);
foreach (ProjectPropertyInstance environmentProperty in environmentVariableProperties)
{
_environmentVariableProperties.Set(environmentProperty.DeepClone());
}
}
/// <summary>
/// Create global properties snapshot
/// </summary>
private void CreateGlobalPropertiesSnapshot(PropertyDictionary<ProjectPropertyInstance> globalPropertiesDictionary)
{
_globalProperties = new PropertyDictionary<ProjectPropertyInstance>(globalPropertiesDictionary.Count);
foreach (ProjectPropertyInstance globalProperty in globalPropertiesDictionary)
{
_globalProperties.Set(globalProperty.DeepClone());
}
}
/// <summary>
/// Create evaluated include cache snapshot
/// </summary>
private void CreateEvaluatedIncludeSnapshotIfRequested(bool keepEvaluationCache, ICollection<ProjectItem> items, Dictionary<ProjectItem, ProjectItemInstance> projectItemToInstanceMap)
{
if (!keepEvaluationCache)
{
return;
}
var multiDictionary = new MultiDictionary<string, ProjectItemInstance>(StringComparer.OrdinalIgnoreCase);
foreach (var item in items)
{
multiDictionary.Add(item.EvaluatedInclude, projectItemToInstanceMap[item]);
}
_itemsByEvaluatedInclude = multiDictionary;
}
/// <summary>
/// Create Items snapshot
/// </summary>
private Dictionary<ProjectItem, ProjectItemInstance> CreateItemsSnapshot(ICollection<ProjectItem> items, int itemTypeCount, bool keepEvaluationCache)
{
_items = new ItemDictionary<ProjectItemInstance>(itemTypeCount);
var projectItemToInstanceMap = keepEvaluationCache ? new Dictionary<ProjectItem, ProjectItemInstance>(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<ProjectItemDefinitionInstance> inheritedItemDefinitions = null;
if (item.InheritedItemDefinitions != null)
{
inheritedItemDefinitions = new List<ProjectItemDefinitionInstance>(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]);
}
}
CopyOnWritePropertyDictionary<ProjectMetadataInstance> directMetadata = null;
if (item.DirectMetadata != null)
{
directMetadata = new CopyOnWritePropertyDictionary<ProjectMetadataInstance>();
IEnumerable<ProjectMetadataInstance> projectMetadataInstances = item.DirectMetadata.Select(directMetadatum => new ProjectMetadataInstance(directMetadatum));
directMetadata.ImportProperties(projectMetadataInstances);
}
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<ProjectItemDefinitionInstance> inheritedItemDefinitions =
new ImmutableItemDefinitionsListConverter<ProjectItemDefinition, ProjectItemDefinitionInstance>(
item.InheritedItemDefinitions,
itemTypeDefinition,
ConvertCachedItemDefinitionToInstance);
ICopyOnWritePropertyDictionary<ProjectMetadataInstance> directMetadata = null;
if (item.DirectMetadata is not null)
{
if (item.DirectMetadata is IDictionary<string, ProjectMetadata> metadataDict)
{
directMetadata = new ImmutablePropertyCollectionConverter<ProjectMetadata, ProjectMetadataInstance>(metadataDict, ConvertCachedProjectMetadataToInstance);
}
else
{
directMetadata = new CopyOnWritePropertyDictionary<ProjectMetadataInstance>();
IEnumerable<ProjectMetadataInstance> projectMetadataInstances = item.DirectMetadata.Select(directMetadatum => new ProjectMetadataInstance(directMetadatum));
directMetadata.ImportProperties(projectMetadataInstances);
}
}
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;
}
private static string GetImportFullPath(ResolvedImport import)
{
return import.ImportedProject.FullPath;
}
/// <summary>
/// Create ItemDefinitions snapshot
/// </summary>
private void CreateItemDefinitionsSnapshot(IDictionary<string, ProjectItemDefinition> itemDefinitions)
{
_itemDefinitions = new RetrievableEntryHashSet<ProjectItemDefinitionInstance>(itemDefinitions.Count, MSBuildNameIgnoreCaseComparer.Default);
foreach (ProjectItemDefinition definition in itemDefinitions.Values)
{
_itemDefinitions.Add(new ProjectItemDefinitionInstance(definition));
}
}
/// <summary>
/// create property snapshot
/// </summary>
private void CreatePropertiesSnapshot(ICollection<ProjectProperty> properties, bool isImmutable)
{
_properties = new PropertyDictionary<ProjectPropertyInstance>(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;
}
}
}
|