File: FindInvalidProjectReferences.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.Text.RegularExpressions;
using Microsoft.Build.Framework;

#nullable disable

namespace Microsoft.Build.Tasks
{
    /// <summary>
    /// Returns the reference assembly paths to the various frameworks
    /// </summary>
    [MSBuildMultiThreadableTask]
    public partial class FindInvalidProjectReferences : TaskExtension
    {
        #region Fields

        private const string PlatformMonikerFormatPattern = @"(?<PLATFORMIDENTITY>^[^,]*),\s*Version=(?<PLATFORMVERSION>.*)";

        /// <summary>
        /// Regex for breaking up the platform moniker
        /// Example: XNA, Version=8.0
        /// </summary>
#if NET
        [GeneratedRegex(PlatformMonikerFormatPattern, RegexOptions.IgnoreCase)]
        private static partial Regex PlatformMonikerRegex { get; }
#else
        private static Regex PlatformMonikerRegex { get; } = new Regex(PlatformMonikerFormatPattern, RegexOptions.IgnoreCase);
#endif

        /// <summary>
        /// Reference moniker metadata
        /// </summary>
        private const string ReferencePlatformMonikerMetadata = "TargetPlatformMoniker";

        /// <summary>
        /// SimpleName group
        /// </summary>
        private const string PlatformSimpleNameGroup = "PLATFORMIDENTITY";

        /// <summary>
        /// Version group
        /// </summary>
        private const string PlatformVersionGroup = "PLATFORMVERSION";

        #endregion

        #region Properties

        /// <summary>
        /// List of Platform monikers for each referenced project
        /// </summary>
        public ITaskItem[] ProjectReferences { get; set; }

        /// <summary>
        /// Target platform version of the current project
        /// </summary>
        [Required]
        public string TargetPlatformVersion { get; set; }

        /// <summary>
        /// Target platform identifier of the current project
        /// </summary>
        [Required]
        public string TargetPlatformIdentifier { get; set; }

        /// <summary>
        /// Invalid references to be unresolved
        /// </summary>
        [Output]
        public ITaskItem[] InvalidReferences { get; private set; }

        #endregion

        #region ITask Members

        /// <summary>
        /// Execute the task.
        /// </summary>
        public override bool Execute()
        {
            var invalidReferences = new List<ITaskItem>();

            Version.TryParse(TargetPlatformVersion, out Version targetPlatformVersionAsVersion);

            if (ProjectReferences != null)
            {
                foreach (ITaskItem item in ProjectReferences)
                {
                    string referenceIdentity = item.ItemSpec;
                    string referencePlatformMoniker = item.GetMetadata(ReferencePlatformMonikerMetadata);

                    // For each moniker, compare version, issue localized message if the referenced project targets
                    // a platform with version higher than the current project and make the reference invalid by adding it to
                    // an invalid reference list output
                    if (ParseMoniker(referencePlatformMoniker, out _, out Version version))
                    {
                        if (targetPlatformVersionAsVersion < version)
                        {
                            Log.LogWarningWithCodeFromResources("FindInvalidProjectReferences.WarnWhenVersionIsIncompatible", TargetPlatformIdentifier, TargetPlatformVersion, referenceIdentity, referencePlatformMoniker);
                            invalidReferences.Add(item);
                        }
                    }
                }
            }

            InvalidReferences = invalidReferences.ToArray();

            return true;
        }

        /// <summary>
        /// Take the identity and the version of a platform moniker
        /// </summary>
        private static bool ParseMoniker(string reference, out string platformIdentity, out Version platformVersion)
        {
            Match match = PlatformMonikerRegex.Match(reference);

            platformIdentity = String.Empty;
            bool parsedVersion = false;

            platformVersion = null;

            if (match.Success)
            {
                platformIdentity = match.Groups[PlatformSimpleNameGroup].Value.Trim();

                string rawVersion = match.Groups[PlatformVersionGroup].Value.Trim();
                parsedVersion = Version.TryParse(rawVersion, out platformVersion);
            }

            return platformIdentity.Length > 0 && parsedVersion;
        }

        #endregion
    }
}