|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
#nullable disable
namespace Microsoft.Build.Tasks
{
public class AssignProjectConfiguration : ResolveProjectBase
{
#region Properties
/// <summary>
/// A special XML string containing a project configuration for each project - we need to simply
/// match the projects and assign the appropriate configuration names to them
/// </summary>
public string SolutionConfigurationContents { get; set; }
/// <summary>
/// Whether to use the solution dependency information passed in the solution blob
/// to add synthetic project references for the purposes of build ordering
/// </summary>
public bool AddSyntheticProjectReferencesForSolutionDependencies { get; set; }
/// <summary>
/// String containing a semicolon-delimited list of mappings from the platform names used
/// by most VS types to those used by .vcxprojs.
/// </summary>
/// <remarks>
/// E.g. "AnyCPU=Win32"
/// </remarks>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Vcx", Justification = "Public API that has already shipped; VCX is a recognizable short form for .vcxproj")]
public string DefaultToVcxPlatformMapping
{
get => _defaultToVcxPlatformMapping ??
(_defaultToVcxPlatformMapping = "AnyCPU=Win32;X86=Win32;X64=X64;Itanium=Itanium");
set
{
_defaultToVcxPlatformMapping = value;
if (_defaultToVcxPlatformMapping?.Length == 0)
{
_defaultToVcxPlatformMapping = null;
}
}
}
private string _defaultToVcxPlatformMapping;
/// <summary>
/// String containing a semicolon-delimited list of mappings from .vcxproj platform names
/// to the platform names use by most other VS project types.
/// </summary>
/// <remarks>
/// E.g. "Win32=AnyCPU"
/// </remarks>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Vcx", Justification = "Public API that has already shipped; VCX is a recognizable short form for .vcxproj")]
public string VcxToDefaultPlatformMapping
{
get
{
if (_vcxToDefaultPlatformMapping == null)
{
if (String.Equals("Library", OutputType, StringComparison.OrdinalIgnoreCase))
{
_vcxToDefaultPlatformMapping = "Win32=AnyCPU;X64=X64;Itanium=Itanium";
}
else
{
_vcxToDefaultPlatformMapping = "Win32=X86;X64=X64;Itanium=Itanium";
}
}
return _vcxToDefaultPlatformMapping;
}
set
{
_vcxToDefaultPlatformMapping = value;
if (_vcxToDefaultPlatformMapping?.Length == 0)
{
_vcxToDefaultPlatformMapping = null;
}
}
}
private string _vcxToDefaultPlatformMapping;
/// <summary>
/// The current project's full path
/// </summary>
public string CurrentProject { get; set; }
/// <summary>
/// The current project's platform.
/// </summary>
public string CurrentProjectConfiguration { get; set; }
/// <summary>
/// The current project's platform.
/// </summary>
public string CurrentProjectPlatform { get; set; }
/// <summary>
/// Should we build references even if they were disabled in the project configuration
/// </summary>
public bool OnlyReferenceAndBuildProjectsEnabledInSolutionConfiguration { get; set; } = false;
// Whether to set the project reference's GlobalPropertiesToRemove metadata to contain
// Configuration and Platform.
/// <summary>
/// Whether to set the GlobalPropertiesToRemove metadata on the project reference such that
/// on an MSBuild call, the Configuration and Platform metadata will be unset, allowing the
/// child project to build in its default configuration / platform.
/// </summary>
public bool ShouldUnsetParentConfigurationAndPlatform { get; set; } = false;
/// <summary>
/// The output type for the project
/// </summary>
public string OutputType { get; set; }
/// <summary>
/// True if we should use the default mappings to resolve the configuration/platform
/// of the passed in project references, false otherwise.
/// </summary>
public bool ResolveConfigurationPlatformUsingMappings { get; set; }
/// <summary>
/// The list of resolved reference paths (preserving the original project reference attributes)
/// </summary>
[Output]
public ITaskItem[] AssignedProjects { get; set; }
/// <summary>
/// The list of project reference items that could not be resolved using the pre-resolved list of outputs.
/// Since VS only pre-resolves non-MSBuild projects, this means that project references in this list
/// are in the MSBuild format.
/// </summary>
[Output]
public ITaskItem[] UnassignedProjects { get; set; }
private const string attrFullConfiguration = "FullConfiguration";
private const string buildReferenceMetadataName = "BuildReference";
private const string referenceOutputAssemblyMetadataName = "ReferenceOutputAssembly";
private const string attrConfiguration = "Configuration";
private const string attrPlatform = "Platform";
private const string attrSetConfiguration = "SetConfiguration";
private const string attrSetPlatform = "SetPlatform";
private IDictionary<string, string> _vcxToDefaultMap;
private IDictionary<string, string> _defaultToVcxMap;
private bool _mappingsPopulated;
#endregion
#region ITask Members
/// <summary>
/// Main task method
/// </summary>
/// <returns></returns>
public override bool Execute()
{
try
{
if (!VerifyProjectReferenceItems(ProjectReferences, true /* treat problems as errors */))
{
return false;
}
var resolvedReferences = new List<ITaskItem>(ProjectReferences.GetLength(0));
var unresolvedReferences = new List<ITaskItem>(ProjectReferences.GetLength(0));
if (!String.IsNullOrEmpty(SolutionConfigurationContents))
{
CacheProjectElementsFromXml(SolutionConfigurationContents);
}
if (AddSyntheticProjectReferencesForSolutionDependencies)
{
// The solution may have had project to project dependencies expressed in it, which were passed in with the blob.
// Add those to the list of project references as if they were regular project references.
AddSyntheticProjectReferences(CurrentProject);
}
foreach (ITaskItem projectRef in ProjectReferences)
{
bool resolveSuccess = ResolveProject(projectRef, out ITaskItem resolvedReference);
if (resolveSuccess)
{
resolvedReferences.Add(resolvedReference);
Log.LogMessageFromResources(MessageImportance.Low, "AssignProjectConfiguration.ProjectConfigurationResolutionSuccess", projectRef.ItemSpec, resolvedReference.GetMetadata(attrFullConfiguration));
}
else
{
// If the reference was unresolved, we want to undefine the Configuration and Platform
// global properties, so that the project will build using its default Configuration and
// Platform rather than that of its parent.
if (ShouldUnsetParentConfigurationAndPlatform)
{
string globalPropertiesToRemove = projectRef.GetMetadata("GlobalPropertiesToRemove");
if (!String.IsNullOrEmpty(globalPropertiesToRemove))
{
globalPropertiesToRemove += ";";
}
if (projectRef is ITaskItem2 item2)
{
item2.SetMetadataValueLiteral("GlobalPropertiesToRemove", globalPropertiesToRemove + "Configuration;Platform");
}
else
{
projectRef.SetMetadata("GlobalPropertiesToRemove", EscapingUtilities.Escape(globalPropertiesToRemove + "Configuration;Platform"));
}
}
unresolvedReferences.Add(projectRef);
// This is not an error - we pass unresolved references to UnresolvedProjectReferences for further
// processing in the .targets file. This means this project was not checked for building in the
// active solution configuration.
Log.LogMessageFromResources(MessageImportance.Low, "AssignProjectConfiguration.ProjectConfigurationUnresolved", projectRef.ItemSpec);
}
}
AssignedProjects = resolvedReferences.ToArray();
UnassignedProjects = unresolvedReferences.ToArray();
}
catch (XmlException e)
{
Log.LogErrorWithCodeFromResources("General.ErrorExecutingTask", GetType().Name, e.Message);
return false;
}
return true;
}
#endregion
#region Methods
/// <summary>
/// Given a project reference task item and an XML document containing project configurations,
/// find the configuration for that task item.
/// </summary>
/// <returns>true if resolved successfully</returns>
internal bool ResolveProject(ITaskItem projectRef, out ITaskItem resolvedProjectWithConfiguration)
{
XmlElement projectConfigurationElement = null;
string projectConfiguration = null;
if (!String.IsNullOrEmpty(SolutionConfigurationContents))
{
projectConfigurationElement = GetProjectElement(projectRef);
if (projectConfigurationElement != null)
{
projectConfiguration = projectConfigurationElement.InnerText;
}
}
if (projectConfiguration == null && ResolveConfigurationPlatformUsingMappings)
{
if (!_mappingsPopulated)
{
SetupDefaultPlatformMappings();
}
string transformedPlatform;
if (String.Equals(projectRef.GetMetadata("Extension"), ".vcxproj", StringComparison.OrdinalIgnoreCase))
{
if (_defaultToVcxMap.TryGetValue(CurrentProjectPlatform, out transformedPlatform))
{
projectConfiguration = CurrentProjectConfiguration + SolutionConfiguration.ConfigPlatformSeparator[0] + transformedPlatform;
}
}
else
{
if (_vcxToDefaultMap.TryGetValue(CurrentProjectPlatform, out transformedPlatform))
{
projectConfiguration = CurrentProjectConfiguration + SolutionConfiguration.ConfigPlatformSeparator[0] + transformedPlatform;
}
}
}
SetBuildInProjectAndReferenceOutputAssemblyMetadata(OnlyReferenceAndBuildProjectsEnabledInSolutionConfiguration, projectRef, projectConfigurationElement);
if (!string.IsNullOrEmpty(projectConfiguration))
{
resolvedProjectWithConfiguration = projectRef;
resolvedProjectWithConfiguration.SetMetadata(attrFullConfiguration, projectConfiguration);
string[] configurationPlatformParts = projectConfiguration.Split(SolutionConfiguration.ConfigPlatformSeparator);
resolvedProjectWithConfiguration.SetMetadata(attrSetConfiguration, "Configuration=" + configurationPlatformParts[0]);
resolvedProjectWithConfiguration.SetMetadata(attrConfiguration, configurationPlatformParts[0]);
if (configurationPlatformParts.Length > 1)
{
resolvedProjectWithConfiguration.SetMetadata(attrSetPlatform, "Platform=" + configurationPlatformParts[1]);
resolvedProjectWithConfiguration.SetMetadata(attrPlatform, configurationPlatformParts[1]);
}
else
{
resolvedProjectWithConfiguration.SetMetadata(attrSetPlatform, "Platform=");
}
return true;
}
resolvedProjectWithConfiguration = null;
return false;
}
/// <summary>
/// Given the project configuration blob and the project reference item, set the BuildInProject metadata and the ReferenceOutputAssembly metadata
/// based on the contents of the blob.
/// </summary>
internal static void SetBuildInProjectAndReferenceOutputAssemblyMetadata(bool onlyReferenceAndBuildProjectsEnabledInSolutionConfiguration, ITaskItem resolvedProjectWithConfiguration, XmlElement projectConfigurationElement)
{
if (projectConfigurationElement != null && resolvedProjectWithConfiguration != null && onlyReferenceAndBuildProjectsEnabledInSolutionConfiguration)
{
// The value of the specified attribute. An empty string is returned if a matching attribute is not found or if the attribute does not have a specified or default value.
string buildProjectInSolution = projectConfigurationElement.GetAttribute(SolutionConfiguration.BuildProjectInSolutionAttribute);
// We could not parse out what was in the attribute, act as if it was not set in the first place.
if (bool.TryParse(buildProjectInSolution, out bool buildProject))
{
// If we do not want to build references disabled in the solution configuration blob
// and the solution configuration indicates the build for this project is disabled
// We need to set the BuildReferenceMetadata to false and the ReferenceOutputAssembly to false (if they are not already set to anything)
if (!buildProject)
{
string buildReferenceMetadata = resolvedProjectWithConfiguration.GetMetadata(buildReferenceMetadataName);
string referenceOutputAssemblyMetadata = resolvedProjectWithConfiguration.GetMetadata(referenceOutputAssemblyMetadataName);
if (buildReferenceMetadata.Length == 0)
{
resolvedProjectWithConfiguration.SetMetadata(buildReferenceMetadataName, "false");
}
if (referenceOutputAssemblyMetadata.Length == 0)
{
resolvedProjectWithConfiguration.SetMetadata(referenceOutputAssemblyMetadataName, "false");
}
}
}
}
}
/// <summary>
/// Given the contents of VcxToDefaultPlatformMapping and DefaultToVcxPlatformMapping properties,
/// fill out the maps that will be used to translate between the two.
/// </summary>
private void SetupDefaultPlatformMappings()
{
_vcxToDefaultMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
_defaultToVcxMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (!String.IsNullOrEmpty(VcxToDefaultPlatformMapping))
{
PopulateMappingDictionary(_vcxToDefaultMap, VcxToDefaultPlatformMapping);
}
if (!String.IsNullOrEmpty(DefaultToVcxPlatformMapping))
{
PopulateMappingDictionary(_defaultToVcxMap, DefaultToVcxPlatformMapping);
}
_mappingsPopulated = true;
}
/// <summary>
/// Given a dictionary to populate and a string of the format "a=b;c=d", populate the
/// dictionary with the given pairs.
/// </summary>
private void PopulateMappingDictionary(IDictionary<string, string> map, string mappingList)
{
string[] mappings = mappingList.Split(MSBuildConstants.SemicolonChar);
foreach (string mapping in mappings)
{
string[] platforms = mapping.Split(MSBuildConstants.EqualsChar);
if (platforms.Length != 2)
{
Log.LogErrorFromResources("AssignProjectConfiguration.IllegalMappingString", mapping.Trim(), mappingList);
}
else
{
map.Add(platforms[0], platforms[1]);
}
}
}
#endregion
}
}
|