src/Build/Instance/ProjectInstance.cs (1,981 lines of code) (raw):
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//-----------------------------------------------------------------------
// </copyright>
// <summary>Definition of ProjectInstance class.</summary>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
using Evaluation = Microsoft.Build.Evaluation;
using ObjectModel = System.Collections.ObjectModel;
using Microsoft.Build.Framework;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Construction;
using Microsoft.Build.Shared;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Internal;
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord;
using ProjectItemInstanceFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.ProjectItemInstanceFactory;
#if MSBUILD_DEBUGGER
using Microsoft.Build.Debugging;
#endif
using System.Xml;
using System.IO;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Linq;
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 none 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>, INodePacketTranslatable
{
/// <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;
/// <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 ItemDictionary<ProjectItemInstance> _items;
/// <summary>
/// Items organized by evaluatedInclude value
/// </summary>
private MultiDictionary<string, ProjectItemInstance> _itemsByEvaluatedInclude;
/// <summary>
/// Items, properties and other project level values to display
/// in the debugger, if we are being debugged.
/// If not debugging, this is null.
/// </summary>
private IDictionary<string, object> _initialGlobalsForDebugging;
/// <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 RetrievableEntryHashSet<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;
/// <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)
{
ErrorUtilities.VerifyThrowArgumentLength(projectFile, "projectFile");
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, "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);
BuildEventContext buildEventContext = new BuildEventContext(buildParameters.NodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId);
ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile, globalProperties, toolsVersion, projectCollection.LoggingService, buildParameters.ProjectRootElementCache, buildEventContext, true /*Explicitly Loaded*/);
Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version provided */, buildParameters, projectCollection.LoggingService, buildEventContext);
}
/// <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)
{
BuildEventContext buildEventContext = new BuildEventContext(0, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId);
Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version specified */, new BuildParameters(projectCollection), projectCollection.LoggingService, buildEventContext);
}
/// <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>(OrdinalIgnoreCaseKeyedComparer.Instance);
_targets = new ObjectModel.ReadOnlyDictionary<string, ProjectTargetInstance>(_actualTargets);
_environmentVariableProperties = projectToInheritFrom._environmentVariableProperties;
_itemDefinitions = new RetrievableEntryHashSet<ProjectItemDefinitionInstance>((IEnumerable<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;
this.EvaluatedItemElements = new List<ProjectItemElement>();
IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance> thisAsIEvaluatorData = (IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>)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>
/// <returns>A new project instance</returns>
internal ProjectInstance(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, int visualStudioVersionFromSolution, ProjectCollection projectCollection)
{
BuildEventContext buildEventContext = new BuildEventContext(0, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId);
Initialize(xml, globalProperties, toolsVersion, null, visualStudioVersionFromSolution, new BuildParameters(projectCollection), projectCollection.LoggingService, buildEventContext);
}
/// <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)
{
ErrorUtilities.VerifyThrowArgumentLength(projectFile, "projectFile");
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, "toolsVersion");
ErrorUtilities.VerifyThrowArgumentNull(buildParameters, "buildParameters");
ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile, globalProperties, toolsVersion, loggingService, buildParameters.ProjectRootElementCache, buildEventContext, false /*Not explicitly loaded*/);
Initialize(xml, globalProperties, toolsVersion, null, 0 /* no solution version specified */, buildParameters, loggingService, buildEventContext);
}
/// <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)
{
ErrorUtilities.VerifyThrowArgumentNull(xml, "xml");
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, "toolsVersion");
ErrorUtilities.VerifyThrowArgumentNull(buildParameters, "buildParameters");
Initialize(xml, globalProperties, toolsVersion, null, 0 /* no solution version specified */, buildParameters, loggingService, buildEventContext);
}
/// <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, "data");
ErrorUtilities.VerifyThrowInternalLength(directory, "directory");
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(fullPath, "fullPath");
_directory = directory;
_projectFileLocation = ElementLocation.Create(fullPath);
_hostServices = hostServices;
var immutable = (settings & ProjectInstanceSettings.Immutable) == ProjectInstanceSettings.Immutable;
this.CreatePropertiesSnapshot(data, immutable);
this.CreateItemDefinitionsSnapshot(data);
var keepEvaluationCache = (settings & ProjectInstanceSettings.ImmutableWithFastItemLookup) == ProjectInstanceSettings.ImmutableWithFastItemLookup;
var projectItemToInstanceMap = this.CreateItemsSnapshot(data, keepEvaluationCache);
this.CreateEvaluatedIncludeSnapshotIfRequested(keepEvaluationCache, data, projectItemToInstanceMap);
this.CreateGlobalPropertiesSnapshot(data);
this.CreateEnvironmentVariablePropertiesSnapshot(environmentVariableProperties);
this.CreateTargetsSnapshot(data);
this.Toolset = data.Toolset; // UNDONE: This isn't immutable, should be cloned or made immutable; it currently has a pointer to project collection
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(INodePacketTranslator translator)
{
((INodePacketTranslatable)this).Translate(translator);
}
/// <summary>
/// Deep clone of this object.
/// Useful for compiling a single file; or for keeping resolved assembly references between builds
/// Mutability is same as original.
/// </summary>
private ProjectInstance(ProjectInstance that)
: this(that, that._isImmutable)
{
}
/// <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)
{
_directory = that._directory;
_projectFileLocation = that._projectFileLocation;
_hostServices = that._hostServices;
_isImmutable = isImmutable;
_properties = new PropertyDictionary<ProjectPropertyInstance>(that._properties.Count);
foreach (ProjectPropertyInstance property in that.Properties)
{
_properties.Set(property.DeepClone(_isImmutable));
}
_items = new ItemDictionary<ProjectItemInstance>(that._items.ItemTypes.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);
this.TaskRegistry = that.TaskRegistry; // UNDONE: This isn't immutable, should be cloned or made immutable; it currently has a pointer to project collection
// These are immutable so we don't need to clone them:
this.Toolset = that.Toolset;
this.SubToolsetVersion = that.SubToolsetVersion;
_targets = that._targets;
_itemDefinitions = that._itemDefinitions;
_explicitToolsVersionSpecified = that._explicitToolsVersionSpecified;
this.EvaluatedItemElements = that.EvaluatedItemElements;
this.ProjectRootElementCache = that.ProjectRootElementCache;
}
/// <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;
}
}
/// <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>
/// 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
{ return _projectFileLocation.File; }
}
/// <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>
/// 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;
private set;
}
/// <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;
private set;
}
/// <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>
/// 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; }
}
/// <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 global properties
/// </summary>
PropertyDictionary<ProjectPropertyInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.Properties
{
[DebuggerStepThrough]
get
{ return _properties; }
}
/// <summary>
/// Gets the global properties
/// </summary>
IEnumerable<ProjectItemDefinitionInstance> IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.ItemDefinitionsEnumerable
{
[DebuggerStepThrough]
get
{ return _itemDefinitions.Values; }
}
/// <summary>
/// Gets the items
/// </summary>
ItemDictionary<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;
set;
}
/// <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;
set;
}
/// <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;
private set;
}
/// <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;
private set;
}
/// <summary>
/// Actual collection of properties in this project,
/// for the build to start with.
/// </summary>
internal PropertyDictionary<ProjectPropertyInstance> PropertiesToBuildWith
{
[DebuggerStepThrough]
get
{ return _properties; }
}
/// <summary>
/// Actual collection of items in this project,
/// for the build to start with.
/// </summary>
internal ItemDictionary<ProjectItemInstance> ItemsToBuildWith
{
[DebuggerStepThrough]
get
{ return _items; }
}
/// <summary>
/// Items, properties and other project level values to display
/// in the debugger, if we are being debugged.
/// If not debugging, this is null.
/// </summary>
internal IDictionary<string, object> InitialGlobalsForDebugging
{
get { return _initialGlobalsForDebugging; }
}
/// <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;
private set;
}
/// <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 ProjectRootElementCache 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, "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, "item");
return ((IItem)item).EvaluatedIncludeEscaped;
}
/// <summary>
/// Gets the escaped value of the provided metadatum.
/// </summary>
public static string GetMetadataValueEscaped(ProjectMetadataInstance metadatum)
{
ErrorUtilities.VerifyThrowArgumentNull(metadatum, "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, "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, "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, "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)
{
// 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(this, 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)
{
// Mutability not verified as this is being populated during evaluation
ProjectPropertyInstance property = ProjectPropertyInstance.Create(name, evaluatedValueEscaped, mayBeReserved, _isImmutable);
_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, ProjectPropertyInstance predecessor)
{
// 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.
/// Does nothing: not needed for project instances.
/// </summary>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.RecordImport(ProjectImportElement importElement, ProjectRootElement import, int versionEvaluated)
{
}
/// <summary>
/// Record an import opened during evaluation. Include duplicates
/// Does nothing: not needed for project instances.
/// </summary>
void IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.RecordImportWithDuplicates(ProjectImportElement importElement, ProjectRootElement import, int versionEvaluated)
{
}
/// <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)
{
ProjectPropertyInstance property = _properties[name];
string value = (property == null) ? String.Empty : property.EvaluatedValue;
return value;
}
/// <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);
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)
{
if (_itemsByEvaluatedInclude == null)
{
return this.GetItems(itemType).Where(item => String.Equals(item.EvaluatedInclude, evaluatedInclude, StringComparison.OrdinalIgnoreCase));
}
return this.GetItemsByEvaluatedInclude(evaluatedInclude).Where(item => String.Equals(item.ItemType, itemType, StringComparison.OrdinalIgnoreCase));
}
/// <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, 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) ? new string[] { } : new string[] { 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);
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);
bool result = ConditionEvaluator.EvaluateCondition<ProjectPropertyInstance, ProjectItemInstance>(condition, ParserOptions.AllowPropertiesAndItemLists, expander, ExpanderOptions.ExpandPropertiesAndItems, Directory, ProjectFileLocation, null /* no logging service */, BuildEventContext.Invalid);
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.ToArray());
rootElement.DefaultTargets = String.Join(";", DefaultTargets.ToArray());
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);
}
#region INodePacketTranslatable Members
/// <summary>
/// Translate the project instance to or from a stream.
/// Only translates global properties, properties, items, and mutability.
/// </summary>
void INodePacketTranslatable.Translate(INodePacketTranslator translator)
{
translator.TranslateDictionary<PropertyDictionary<ProjectPropertyInstance>, ProjectPropertyInstance>(ref _globalProperties, ProjectPropertyInstance.FactoryForDeserialization);
translator.TranslateDictionary<PropertyDictionary<ProjectPropertyInstance>, ProjectPropertyInstance>(ref _properties, ProjectPropertyInstance.FactoryForDeserialization);
translator.Translate(ref _isImmutable);
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 (INodePacketTranslator outerTranslator) { 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 (INodePacketTranslator outerTranslator) { 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)
{
ErrorUtilities.VerifyThrowArgumentLength(projectFile, "projectFile");
ErrorUtilities.VerifyThrowArgumentNull(globalPropertiesInstances, "globalPropertiesInstances");
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, "toolsVersion");
ErrorUtilities.VerifyThrowArgumentNull(buildParameters, "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);
}
else
{
projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersion, loggingService, projectBuildEventContext, targetNames);
}
}
// 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
{
int solutionVersion;
int visualStudioVersion;
SolutionFile.GetSolutionFileAndVisualStudioMajorVersions(projectFile, out solutionVersion, out 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);
}
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);
}
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: toolsVersion, getToolset: buildParameters.GetToolset, defaultToolsVersion: Constants.defaultSolutionWrapperProjectToolsVersion);
projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersionToUse, loggingService, projectBuildEventContext, targetNames);
}
}
return projectInstances;
}
/// <summary>
/// Factory for deserialization.
/// </summary>
internal static ProjectInstance FactoryForDeserialization(INodePacketTranslator 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 (null == targets)
{
targets = new string[] { };
}
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>) ? ((ICollection<ILogger>)loggers) : 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);
}
if (remoteLoggers != null)
{
parameters.ForwardingLoggers = (remoteLoggers is ICollection<ForwardingLoggerRecord>) ?
((ICollection<ForwardingLoggerRecord>)remoteLoggers) :
new List<ForwardingLoggerRecord>(remoteLoggers);
}
parameters.EnvironmentPropertiesInternal = _environmentVariableProperties;
parameters.ProjectRootElementCache = ProjectRootElementCache;
parameters.MaxNodeCount = maxNodeCount;
results = buildManager.Build(parameters, data);
targetOutputs = results.ResultsByTarget;
// UNDONE: Does this need to happen in EndBuild?
#if false
Exception exception = results.Exception;
if (exception != null)
{
BuildEventContext buildEventContext = new BuildEventContext(1 /* UNDONE: NodeID */, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId);
InvalidProjectFileException projectException = exception as InvalidProjectFileException;
if (projectException != null)
{
loggingService.LogInvalidProjectFileError(buildEventContext, projectException);
}
else
{
loggingService.LogFatalBuildError(buildEventContext, exception, new BuildEventFileInfo(projectFileLocation));
}
}
#endif
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 ReadOnlyEmptyList<TargetSpecification>.Instance;
}
}
/// <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 ReadOnlyEmptyList<TargetSpecification>.Instance;
}
}
/// <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(INodePacketTranslator translator)
{
((INodePacketTranslatable)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(INodePacketTranslator translator)
{
((INodePacketTranslatable)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, bool parentProjectSupportsReturnsAttribute)
{
VerifyThrowNotImmutable();
ErrorUtilities.VerifyThrowInternalLength(targetName, "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,
_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,
null,
null,
new ObjectModel.ReadOnlyCollection<ProjectTargetInstanceChild>(new List<ProjectTargetInstanceChild>()),
new ObjectModel.ReadOnlyCollection<ProjectOnErrorInstance>(new List<ProjectOnErrorInstance>()),
parentProjectSupportsReturnsAttribute
);
_actualTargets[target.Name] = 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>
/// <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
)
{
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);
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>
/// <returns>An appropriate ProjectRootElement</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private static ProjectInstance[] GenerateSolutionWrapperUsingOldOM
(
string projectFile,
IDictionary<string, string> globalProperties,
string toolsVersion,
ProjectRootElementCache projectRootElementCache,
BuildParameters buildParameters,
ILoggingService loggingService,
BuildEventContext projectBuildEventContext,
bool isExplicitlyLoaded
)
{
// 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.IllegalItemPropertyNames[environmentVariableName] != null
|| ReservedPropertyNames.IsReservedProperty(environmentVariableName))
)
{
if (clearedVariables == null)
{
clearedVariables = new List<DictionaryEntry>();
}
Environment.SetEnvironmentVariable(environmentVariableName, null);
clearedVariables.Add(environmentVariable);
}
}
#if (!STANDALONEBUILD)
wrapperProjectXml = Microsoft.Build.BuildEngine.SolutionWrapperProject.Generate(projectFile, toolsVersion, projectBuildEventContext);
#else
wrapperProjectXml = "";
#endif
}
#if (!STANDALONEBUILD)
catch (Microsoft.Build.BuildEngine.InvalidProjectFileException ex)
{
// Whenever calling the old engine, we must translate its exception types into ours
throw new InvalidProjectFileException(ex.ProjectFile, ex.LineNumber, ex.ColumnNumber, ex.EndLineNumber, ex.EndColumnNumber, ex.Message, ex.ErrorSubcategory, ex.ErrorCode, ex.HelpKeyword, ex.InnerException);
}
#endif
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();
xrs.DtdProcessing = DtdProcessing.Ignore;
ProjectRootElement projectRootElement = new ProjectRootElement(XmlReader.Create(new StringReader(wrapperProjectXml), xrs), projectRootElementCache, isExplicitlyLoaded,
preserveFormatting: false);
projectRootElement.DirectoryPath = Path.GetDirectoryName(projectFile);
ProjectInstance instance = new ProjectInstance(projectRootElement, globalProperties, toolsVersion, buildParameters, loggingService, projectBuildEventContext);
return new ProjectInstance[] { 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 RetrievableEntryHashSet<TValue>(dictionary, OrdinalIgnoreCaseKeyedComparer.Instance, readOnly: true);
}
}
/// <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)
{
ErrorUtilities.VerifyThrowArgumentNull(xml, "xml");
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(explicitToolsVersion, "toolsVersion");
ErrorUtilities.VerifyThrowArgumentNull(buildParameters, "buildParameters");
_directory = xml.DirectoryPath;
_projectFileLocation = (xml.ProjectFileLocation != null) ? xml.ProjectFileLocation : ElementLocation.EmptyLocation;
_properties = new PropertyDictionary<ProjectPropertyInstance>();
_items = new ItemDictionary<ProjectItemInstance>();
_actualTargets = new RetrievableEntryHashSet<ProjectTargetInstance>(OrdinalIgnoreCaseKeyedComparer.Instance);
_targets = new ObjectModel.ReadOnlyDictionary<string, ProjectTargetInstance>(_actualTargets);
_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;
this.EvaluatedItemElements = new List<ProjectItemElement>();
string toolsVersionToUse = explicitToolsVersion;
_explicitToolsVersionSpecified = (explicitToolsVersion != null);
ElementLocation toolsVersionLocation = xml.Location;
if (xml.ToolsVersion.Length > 0)
{
_originalProjectToolsVersion = xml.ToolsVersion;
toolsVersionLocation = xml.ToolsVersionLocation;
}
toolsVersionToUse = Utilities.GenerateToolsVersionToUse
(
explicitToolsVersion,
xml.ToolsVersion,
buildParameters.GetToolset,
buildParameters.DefaultToolsVersion
);
// Don't log the message if the toolsversion is different because an explicit toolsversion was specified --
// in that case the user already knows what they're doing; the point of this warning is to give them a heads
// up if we're doing this ourselves for our own reasons.
if (!_explicitToolsVersionSpecified && !String.Equals(_originalProjectToolsVersion, toolsVersionToUse, StringComparison.OrdinalIgnoreCase))
{
_usingDifferentToolsVersionFromProjectFile = true;
}
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 (Evaluator<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.DebugEvaluation)
{
Trace.WriteLine(String.Format(CultureInfo.InvariantCulture, "MSBUILD: Creating a ProjectInstance from an unevaluated state [{0}]", FullPath));
}
_initialGlobalsForDebugging = Evaluator<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>.Evaluate(this, xml, ProjectLoadSettings.Default, buildParameters.MaxNodeCount, buildParameters.EnvironmentPropertiesInternal, loggingService, new ProjectItemInstanceFactory(this), buildParameters.ToolsetProvider, ProjectRootElementCache, buildEventContext, this /* for debugging only */);
}
/// <summary>
/// Get items by evaluatedInclude value
/// </summary>
private ICollection<ProjectItemInstance> GetItemsByEvaluatedInclude(string evaluatedInclude)
{
// Even if there are no items in itemsByEvaluatedInclude[], it will return an IEnumerable, which is non-null
return new ReadOnlyCollection<ProjectItemInstance>(_itemsByEvaluatedInclude[evaluatedInclude]);
}
/// <summary>
/// Create various target snapshots
/// </summary>
private void CreateTargetsSnapshot(Evaluation.Project.Data data)
{
this.DefaultTargets = new List<string>(data.DefaultTargets);
this.InitialTargets = new List<string>(data.InitialTargets);
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>)this).BeforeTargets = CreateCloneDictionary(data.BeforeTargets, StringComparer.OrdinalIgnoreCase);
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance, ProjectItemDefinitionInstance>)this).AfterTargets = CreateCloneDictionary(data.AfterTargets, StringComparer.OrdinalIgnoreCase);
// ProjectTargetInstances are immutable so only the dictionary must be cloned
_targets = CreateCloneDictionary(data.Targets);
}
/// <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(Evaluation.Project.Data data)
{
_globalProperties = new PropertyDictionary<ProjectPropertyInstance>(data.GlobalPropertiesDictionary.Count);
foreach (ProjectPropertyInstance globalProperty in data.GlobalPropertiesDictionary)
{
_globalProperties.Set(globalProperty.DeepClone());
}
}
/// <summary>
/// Create evaluated include cache snapshot
/// </summary>
private void CreateEvaluatedIncludeSnapshotIfRequested(bool keepEvaluationCache, Evaluation.Project.Data data, Dictionary<ProjectItem, ProjectItemInstance> projectItemToInstanceMap)
{
if (!keepEvaluationCache)
{
return;
}
_itemsByEvaluatedInclude = new MultiDictionary<string, ProjectItemInstance>(StringComparer.OrdinalIgnoreCase);
foreach (var key in data.ItemsByEvaluatedIncludeCache.Keys)
{
var projectItems = data.ItemsByEvaluatedIncludeCache[key];
foreach (var projectItem in projectItems)
{
_itemsByEvaluatedInclude.Add(key, projectItemToInstanceMap[projectItem]);
}
}
}
/// <summary>
/// Create Items snapshot
/// </summary>
private Dictionary<ProjectItem, ProjectItemInstance> CreateItemsSnapshot(Evaluation.Project.Data data, bool keepEvaluationCache)
{
_items = new ItemDictionary<ProjectItemInstance>(data.ItemTypes.Count);
var projectItemToInstanceMap = keepEvaluationCache ? new Dictionary<ProjectItem, ProjectItemInstance>(data.Items.Count) : null;
foreach (ProjectItem item in data.Items)
{
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>(item.DirectMetadataCount);
foreach (ProjectMetadata directMetadatum in item.DirectMetadata)
{
ProjectMetadataInstance directMetadatumInstance = new ProjectMetadataInstance(directMetadatum);
directMetadata.Set(directMetadatumInstance);
}
}
ProjectItemInstance instance = new ProjectItemInstance(this, item.ItemType, ((IItem)item).EvaluatedIncludeEscaped, item.EvaluatedIncludeBeforeWildcardExpansionEscaped, directMetadata, inheritedItemDefinitions, ProjectCollection.Escape(item.Xml.ContainingProject.FullPath));
_items.Add(instance);
if (projectItemToInstanceMap != null)
{
projectItemToInstanceMap.Add(item, instance);
}
}
return projectItemToInstanceMap;
}
/// <summary>
/// Create ItemDefinitions snapshot
/// </summary>
private void CreateItemDefinitionsSnapshot(Evaluation.Project.Data data)
{
_itemDefinitions = new RetrievableEntryHashSet<ProjectItemDefinitionInstance>(MSBuildNameIgnoreCaseComparer.Default);
foreach (ProjectItemDefinition definition in data.ItemDefinitions.Values)
{
_itemDefinitions.Add(new ProjectItemDefinitionInstance(this, definition));
}
}
/// <summary>
/// create property snapshot
/// </summary>
private void CreatePropertiesSnapshot(Evaluation.Project.Data data, bool isImmutable)
{
_properties = new PropertyDictionary<ProjectPropertyInstance>(data.Properties.Count);
foreach (ProjectProperty property in data.Properties)
{
// Allow reserved property names, since this is how they are added to the project instance.
// The caller has prevented users setting them themselves.
ProjectPropertyInstance instance = ProjectPropertyInstance.Create(property.Name, ((IProperty)property).EvaluatedValueEscaped, true /* MAY be reserved name */, isImmutable);
_properties.Set(instance);
}
}
}
}