| File: ResolveProjectBase.cs | Web Access |
| Project: src\msbuild\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core) |
// 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; using Microsoft.Build.Utilities; #nullable disable namespace Microsoft.Build.Tasks { /// <summary> /// Base class for ResolveNonMSBuildProjectOutput and AssignProjectConfiguration, since they have /// similar architecture /// </summary> public abstract class ResolveProjectBase : TaskExtension { #region Properties /// <summary> /// The list of project references /// </summary> [Required] public ITaskItem[] ProjectReferences { get { ArgumentNullException.ThrowIfNull(_projectReferences, nameof(ProjectReferences)); return _projectReferences; } set => _projectReferences = value; } private ITaskItem[] _projectReferences; // This field stores all the distinct project references by project absolute path private readonly HashSet<string> _cachedProjectReferencesByAbsolutePath = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private SolutionConfiguration _solutionConfiguration = SolutionConfiguration.Empty; private const string attributeProject = "Project"; #endregion #region Methods /// <summary> /// Checks if a project reference task item contains all the required attributes. /// Currently, the only required attribute is project GUID for inside the IDE mode. /// </summary> internal bool VerifyReferenceAttributes(ITaskItem reference, out string missingAttribute) { missingAttribute = attributeProject; string attrValue = reference.GetMetadata(missingAttribute); // missing project GUID? (no longer required, but if it's there then validate it) if (attrValue.Length > 0) { // invalid project GUID format? if (!Guid.TryParse(attrValue, out _)) { return false; } } missingAttribute = null; return true; } /// <summary> /// Checks all project reference task items for required attributes /// Internal for unit testing /// </summary> internal bool VerifyProjectReferenceItems(ITaskItem[] references, bool treatAsError) { bool referencesValid = true; foreach (ITaskItem reference in references) { _cachedProjectReferencesByAbsolutePath.Add(reference.GetMetadata("FullPath")); // metadata is cached and used again later if (!VerifyReferenceAttributes(reference, out string missingAttribute)) { if (treatAsError) { Log.LogErrorWithCodeFromResources("General.MissingOrUnknownProjectReferenceAttribute", reference.ItemSpec, missingAttribute); referencesValid = false; } else { Log.LogWarningWithCodeFromResources("General.MissingOrUnknownProjectReferenceAttribute", reference.ItemSpec, missingAttribute); } } } return referencesValid; } /// <summary> /// Pre-cache individual project elements from the XML string in a hashtable for quicker access. /// </summary> internal void CacheProjectElementsFromXml(string xmlString) => _solutionConfiguration = new SolutionConfiguration(xmlString); /// <summary> /// Helper method for retrieving whatever was stored in the XML string for the given project /// </summary> protected string GetProjectItem(ITaskItem projectRef) { XmlElement projectElement = GetProjectElement(projectRef); return projectElement?.InnerText; } /// <summary> /// Helper method for retrieving the XML element for the given project /// </summary> [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode", Justification = "protected method on a public base class that has previously shipped, so changing this now would be a breaking change.")] protected XmlElement GetProjectElement(ITaskItem projectRef) { string projectGuid = projectRef.GetMetadata(attributeProject); if (_solutionConfiguration.TryGetProjectByGuid(projectGuid, out XmlElement projectElement)) { return projectElement; } // We didn't find the project element by locating a project guid on the P2P reference // next we'll try a lookup by the absolute path of the project string projectFullPath = projectRef.GetMetadata("FullPath"); // reserved metadata "FullPath" is used at it will cache the value if (_solutionConfiguration.TryGetProjectByAbsolutePath(projectFullPath, out projectElement)) { return projectElement; } return null; } /// <summary> /// Helper method for retrieving the extra "project references" passed in the solution blob. /// These came from dependencies expressed in the solution file itself. /// </summary> protected void AddSyntheticProjectReferences(string currentProjectAbsolutePath) { // Get the guid for this project if (!_solutionConfiguration.TryGetProjectGuidByAbsolutePath(currentProjectAbsolutePath, out string projectGuid)) { // We were passed a blob, but we weren't listed in it. Odd. Return. return; } // Use the guid to look up the dependencies for it if (!_solutionConfiguration.TryGetProjectDependencies(projectGuid, out List<string> guids)) { // We didn't have dependencies listed in the blob return; } // ProjectReferences is a fixed size array, so start aggregating in a list List<ITaskItem> updatedProjectReferenceList = new List<ITaskItem>(_projectReferences); foreach (string guid in guids) { // Get the absolute path of the dependency, using the blob if (!_solutionConfiguration.TryGetProjectPathByGuid(guid, out string path)) { // We had a dependency listed in the blob that wasn't itself in the blob. Odd. Return. continue; } // If the dependency's already specified as a project reference, ignore it; no sense referencing it twice if (!_cachedProjectReferencesByAbsolutePath.Contains(path)) { _cachedProjectReferencesByAbsolutePath.Add(path); var item = new TaskItem(path); // Unfortunately we've used several different metadata names to trigger // project references to do stuff other than trigger a build item.SetMetadata("ReferenceOutputAssembly", "false"); item.SetMetadata("LinkLibraryDependencies", "false"); item.SetMetadata("CopyLocal", "false"); item.SetMetadata("SkipGetTargetFrameworkProperties", "true"); item.SetMetadata("GlobalPropertiesToRemove", "TargetFramework"); updatedProjectReferenceList.Add(item); } } // Finally, set our new augmented project references list as the official list; // note that this means that the output parameter may include project references that weren't passed in _projectReferences = new ITaskItem[updatedProjectReferenceList.Count]; updatedProjectReferenceList.CopyTo(_projectReferences); } #endregion } }