File: ItemUtilities.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.NET.Build.Tasks\Microsoft.NET.Build.Tasks.csproj (Microsoft.NET.Build.Tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Build.Framework;
using Microsoft.DotNet.Cli;
using Microsoft.NET.Build.Tasks.ConflictResolution;
 
namespace Microsoft.NET.Build.Tasks
{
    internal static partial class ItemUtilities
    {
        public static bool? GetBooleanMetadata(this ITaskItem item, string metadataName)
        {
            bool? result = null;
 
            string value = item.GetMetadata(metadataName);
            bool parsedResult;
            if (bool.TryParse(value, out parsedResult))
            {
                result = parsedResult;
            }
 
            return result;
        }
 
        public static bool HasMetadataValue(this ITaskItem item, string name)
        {
            string value = item.GetMetadata(name);
 
            return !string.IsNullOrEmpty(value);
        }
 
        public static bool HasMetadataValue(this ITaskItem item, string name, string expectedValue)
        {
            string value = item.GetMetadata(name);
 
            return string.Equals(value, expectedValue, StringComparison.OrdinalIgnoreCase);
        }
 
        /// <summary>
        /// Get's the filename to use for identifying reference conflicts
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public static string? GetReferenceFileName(ITaskItem? item)
        {
            var aliases = item?.GetMetadata(MetadataNames.Aliases);
 
            if (!string.IsNullOrEmpty(aliases))
            {
                // skip compile-time conflict detection for aliased assemblies.
                // An alias is the way to avoid a conflict
                //   eg: System, v1.0.0.0 in global will not conflict with System, v2.0.0.0 in `private` alias
                // We could model each alias scope and try to check for conflicts within that scope,
                // but this is a ton of complexity for a fringe feature.
                // Instead, we'll treat an alias as an indication that the developer has opted out of 
                // conflict resolution.
                return null;
            }
 
            // We only handle references that have path information since we're only concerned
            // with resolving conflicts between file references.  If conflicts exist between 
            // named references that are found from AssemblySearchPaths we'll leave those to
            // RAR to handle or not as it sees fit.
            var sourcePath = GetSourcePath(item);
 
            if (string.IsNullOrEmpty(sourcePath))
            {
                return null;
            }
 
            try
            {
                return Path.GetFileName(sourcePath);
            }
            catch (ArgumentException)
            {
                // We won't even try to resolve a conflict if we can't open the file, so ignore invalid paths
                return null;
            }
        }
 
        public static string? GetReferenceTargetPath(ITaskItem? item)
        {
            // Determine if the reference will be copied local.  
            // We're only dealing with primary file references.  For these RAR will 
            // copy local if Private is true or unset.
 
            var isPrivate = MSBuildUtilities.ConvertStringToBool(item?.GetMetadata(MetadataNames.Private), defaultValue: true);
 
            if (!isPrivate)
            {
                // Private = false means the reference shouldn't be copied.
                return null;
            }
 
            return GetTargetPath(item);
        }
 
        public static string? GetReferenceTargetFileName(ITaskItem? item)
        {
            var targetPath = GetReferenceTargetPath(item);
 
            return targetPath != null ? Path.GetFileName(targetPath) : null;
        }
 
        public static string? GetSourcePath(ITaskItem? item)
        {
            var sourcePath = item?.GetMetadata(MetadataNames.HintPath)?.Trim();
 
            if (string.IsNullOrWhiteSpace(sourcePath))
            {
                // assume item-spec points to the file.
                // this won't work if it comes from a targeting pack or SDK, but
                // in that case the file won't exist and we'll skip it.
                sourcePath = item?.ItemSpec;
            }
 
            return sourcePath;
        }
 
        static readonly string[] s_targetPathMetadata = new[] { MetadataNames.TargetPath, MetadataNames.DestinationSubPath };
        public static string? GetTargetPath(ITaskItem? item)
        {
            // first use TargetPath, then DestinationSubPath, then fallback to filename+extension alone
            // Can't use Path, as this is the path of the file in the package, which is usually not the target path
            // (for example the target path for lib/netcoreapp2.0/lib.dll is just lib.dll)
            foreach (var metadata in s_targetPathMetadata)
            {
                var value = item?.GetMetadata(metadata)?.Trim();
 
                if (!string.IsNullOrWhiteSpace(value))
                {
                    // normalize path
                    return value?.Replace('\\', '/');
                }
            }
 
            var sourcePath = GetSourcePath(item);
 
            var fileName = Path.GetFileName(sourcePath);
 
            //  Get subdirectory for satellite assemblies / runtime targets
            var destinationSubDirectory = item?.GetMetadata("DestinationSubDirectory");
 
            if (!string.IsNullOrWhiteSpace(destinationSubDirectory) && fileName is not null)
            {
                return Path.Combine(destinationSubDirectory, fileName);
            }
 
            return fileName;
        }
    }
}