|
// 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()
{
SuppressibleMSBuildLog logFactory(ISuppressionEngine suppressionEngine) => new(Log, suppressionEngine, NoWarn);
ValidateAssemblies.Run(logFactory,
GenerateSuppressionFile,
PreserveUnnecessarySuppressions,
PermitUnnecessarySuppressions,
SuppressionFiles,
SuppressionOutputFile,
NoWarn,
RespectInternals,
EnableRuleAttributesMustMatch,
ExcludeAttributesFiles,
EnableRuleCannotChangeParameterName,
LeftAssemblies!,
RightAssemblies!,
EnableStrictMode,
ParseAssembliesReferences(LeftAssembliesReferences),
ParseAssembliesReferences(RightAssembliesReferences),
CreateWorkItemPerAssembly,
ParseTransformationPattern(LeftAssembliesTransformationPattern),
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;
}
}
}
|