| File: ValidateAssembliesTask.cs | Web Access |
| Project: src\sdk\src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Task\Microsoft.DotNet.ApiCompat.Task.csproj (Microsoft.DotNet.ApiCompat.Task) |
// 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.ApiCompatibility.Logging; using Microsoft.NET.Build.Tasks; namespace Microsoft.DotNet.ApiCompat.Task { /// <summary> /// ApiCompat's ValidateAssemblies MSBuild frontend. /// </summary> public class ValidateAssembliesTask : TaskBase { // Important: Keep properties exposed in sync with the CLI frontend. /// <summary> /// The assemblies that represent the contract. /// </summary> [Required] public string[]? LeftAssemblies { get; set; } /// <summary> /// The assemblies that act as the implementation. /// </summary> [Required] public string[]? RightAssemblies { get; set; } /// <summary> /// The path to the roslyn assemblies that should be loaded. /// </summary> [Required] public string? RoslynAssembliesPath { get; set; } /// <summary> /// If true, generates a suppression file that contains the api compatibility errors. /// </summary> public bool GenerateSuppressionFile { get; set; } /// <summary> /// If true, preserves unnecessary suppressions when re-generating the suppression file. /// </summary> public bool PreserveUnnecessarySuppressions { get; set; } /// <summary> /// If true, permits unnecessary suppressions in the suppression file. /// </summary> public bool PermitUnnecessarySuppressions { get; set; } /// <summary> /// The path to suppression files. If provided, the suppressions are read and stored. /// </summary> public string[]? SuppressionFiles { get; set; } /// <summary> /// The path to the suppression output file that is written to, when <see cref="GenerateSuppressionFile"/> is true. /// </summary> public string? SuppressionOutputFile { get; set; } /// <summary> /// A NoWarn string contains the error codes that should be ignored. /// </summary> public string? NoWarn { get; set; } /// <summary> /// If true, includes both internal and public API. /// </summary> public bool RespectInternals { get; set; } /// <summary> /// Enables rule to check that attributes match. /// </summary> public bool EnableRuleAttributesMustMatch { get; set; } /// <summary> /// Set of files with types in DocId format of which attributes to exclude. /// </summary> public string[]? ExcludeAttributesFiles { get; set; } /// <summary> /// Enables rule to check that the parameter names between public methods do not change. /// </summary> public bool EnableRuleCannotChangeParameterName { get; set; } /// <summary> /// Performs api comparison checks in strict mode. /// </summary> public bool EnableStrictMode { get; set; } /// <summary> /// The left assemblies' references. The index in the array maps to the index of the passed in left assembly. /// </summary> public string[]? LeftAssembliesReferences { get; set; } /// <summary> /// The right assemblies' references. The index in the array maps to the index of the passed in right assembly. /// </summary> public string[]? RightAssembliesReferences { get; set; } /// <summary> /// The path to a semaphore file that acts as the touched output which is useful for incremental invocation of this task. /// </summary> public string? SemaphoreFile { get; set; } /// <summary> /// Create dedicated api compatibility checks for each left and right assembly tuples. /// </summary> public bool CreateWorkItemPerAssembly { get; set; } /// <summary> /// Regex transformation patterns (regex + replacement string) that transform left assemblies paths to (i.e. relative) paths that can be encoded into the suppression file. /// </summary> public ITaskItem[]? LeftAssembliesTransformationPattern { get; set; } /// <summary> /// Regex transformation patterns (regex + replacement string) that transform right assemblies paths to (i.e. relative) paths that can be encoded into the suppression file. /// </summary> public ITaskItem[]? RightAssembliesTransformationPattern { get; set; } public override bool Execute() { RoslynResolver roslynResolver = RoslynResolver.Register(RoslynAssembliesPath!); try { return base.Execute(); } finally { roslynResolver.Unregister(); } } protected override void ExecuteCore() { if (LeftAssemblies is null) throw new ArgumentNullException(nameof(LeftAssemblies)); if (RightAssemblies is null) throw new ArgumentNullException(nameof(RightAssemblies)); SuppressibleMSBuildLog logFactory(ISuppressionEngine suppressionEngine) => new(Log, suppressionEngine, NoWarn); ValidateAssemblies.Run(logFactory, new ValidateAssembliesOptions(LeftAssemblies, RightAssemblies) { GenerateSuppressionFile = GenerateSuppressionFile, PreserveUnnecessarySuppressions = PreserveUnnecessarySuppressions, PermitUnnecessarySuppressions = PermitUnnecessarySuppressions, SuppressionFiles = SuppressionFiles, SuppressionOutputFile = SuppressionOutputFile, NoWarn = NoWarn, RespectInternals = RespectInternals, EnableRuleAttributesMustMatch = EnableRuleAttributesMustMatch, ExcludeAttributesFiles = ExcludeAttributesFiles, EnableRuleCannotChangeParameterName = EnableRuleCannotChangeParameterName, EnableStrictMode = EnableStrictMode, LeftAssembliesReferences = ParseAssembliesReferences(LeftAssembliesReferences), RightAssembliesReferences = ParseAssembliesReferences(RightAssembliesReferences), CreateWorkItemPerAssembly = CreateWorkItemPerAssembly, LeftAssembliesTransformationPatterns = ParseTransformationPattern(LeftAssembliesTransformationPattern), RightAssembliesTransformationPatterns = ParseTransformationPattern(RightAssembliesTransformationPattern), }); if (SemaphoreFile != null) { if (Log.HasLoggedErrors) { // To force incremental builds to show failures again, delete the passed in semaphore file. if (File.Exists(SemaphoreFile)) File.Delete(SemaphoreFile); } else { // If ValidateAssemblies was successful, create/update the semaphore file. File.Create(SemaphoreFile).Dispose(); File.SetLastWriteTimeUtc(SemaphoreFile, DateTime.UtcNow); } } } private static string[][]? ParseAssembliesReferences(string[]? assembliesReferences) { if (assembliesReferences == null || assembliesReferences.Length == 0) return null; string[][] assembliesReferencesArray = new string[assembliesReferences.Length][]; for (int i = 0; i < assembliesReferences.Length; i++) { assembliesReferencesArray[i] = assembliesReferences[i].Split(','); } return assembliesReferencesArray; } private static (string CaptureGroupPattern, string ReplacementString)[]? ParseTransformationPattern(ITaskItem[]? transformationPatterns) { const string ReplacementStringMetadataName = "ReplacementString"; if (transformationPatterns == null) return null; var patterns = new (string CaptureGroupPattern, string ReplacementPattern)[transformationPatterns.Length]; for (int i = 0; i < transformationPatterns.Length; i++) { string captureGroupPattern = transformationPatterns[i].ItemSpec; string replacementString = transformationPatterns[i].GetMetadata(ReplacementStringMetadataName); if (string.IsNullOrWhiteSpace(replacementString)) { throw new ArgumentException(string.Format(CommonResources.InvalidRexegStringTransformationPattern, captureGroupPattern, replacementString)); } patterns[i] = (captureGroupPattern, replacementString); } return patterns; } } }