Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ApiCompat: Add ValidateAssemblies msbuild task and add global tool #26616

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cf4a93c
Resource updates
ViktorHofer Jul 13, 2022
0e8678c
Solution file updates
ViktorHofer Jul 13, 2022
da1a4e3
msbuild targets file updates
ViktorHofer Jul 13, 2022
6310adb
Project file updates
ViktorHofer Jul 13, 2022
b10739b
Frontend code (task & console + shared)
ViktorHofer Jul 13, 2022
8a26cc4
ApiCompatibility layer updates
ViktorHofer Jul 13, 2022
8c868a6
PackageValidation layer updates
ViktorHofer Jul 13, 2022
1a93640
Test updates and additions
ViktorHofer Jul 13, 2022
2c4b7a3
Update CODEOWNERS file
ViktorHofer Jul 13, 2022
40f5d74
Adjust namespaces
ViktorHofer Jul 21, 2022
0c6c651
PR feedback for RoslynResolver
ViktorHofer Jul 21, 2022
4640173
Merge remote-tracking branch 'upstream/main' into BigRefactoringOfCom…
ViktorHofer Jul 21, 2022
0f991d9
Fix build failure and address PR feedback
ViktorHofer Jul 21, 2022
0b977a6
Rename helper to SuppressionFileHelper
ViktorHofer Jul 28, 2022
b539c0e
Default RunApiCompat to true in ValidatePackage task
ViktorHofer Jul 28, 2022
c278364
Refine RoslynResolver usage
ViktorHofer Jul 28, 2022
6c64a49
Update documentation
ViktorHofer Jul 28, 2022
d83c3a8
Use windows directory separators chars
ViktorHofer Jul 28, 2022
4f3c499
Fix build break
ViktorHofer Jul 28, 2022
5d2730e
Make RoslynAssembliesPath non required
ViktorHofer Jul 29, 2022
eaa817b
Add doc to keep parameters in sync in frontends
ViktorHofer Jul 29, 2022
7c90903
nit style clean-up
ViktorHofer Jul 29, 2022
82db8dc
Add regex match timeout and add a test for it
ViktorHofer Jul 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable
ViktorHofer marked this conversation as resolved.
Show resolved Hide resolved

using System.IO;

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
/// <summary>
/// Provider to retrieve the stream of a <see cref="MetadataInformation"/>.
/// </summary>
public interface IMetadataStreamProvider
{
/// <summary>
/// Get the stream from a <see cref="MetadataInformation"/>.
/// </summary>
/// <param name="metadata">Pass in a <see cref="MetadataInformation"/> to be read.</param>
/// <returns>Returns the stream from a provided <see cref="MetadataInformation"/> object.</returns>
Stream GetStream(MetadataInformation metadata);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.Collections.Generic;

Expand All @@ -12,34 +14,64 @@ namespace Microsoft.DotNet.ApiCompatibility.Abstractions
/// </summary>
public readonly struct MetadataInformation : IEquatable<MetadataInformation>
{
/// <summary>
/// The name of the assembly.
/// </summary>
public readonly string AssemblyName;
public readonly string TargetFramework;

/// <summary>
/// The unique assembly id.
/// </summary>
public readonly string AssemblyId;

/// <summary>
/// Returns the assembly's full path or if it's part of an archive, the path to the archive.
/// </summary>
public readonly string FullPath;

/// <summary>
/// The assembly references.
/// </summary>
public readonly IEnumerable<string>? References;

/// <summary>
/// The assembly's display string.
/// </summary>
public readonly string DisplayString;

public MetadataInformation(string name, string targetFramework, string assemblyId, string displayString = null)
public MetadataInformation(string assemblyName, string assemblyId, string? fullPath = null, IEnumerable<string>? references = null, string? displayString = null)
{
AssemblyName = name ?? string.Empty;
TargetFramework = targetFramework ?? string.Empty;
AssemblyId = assemblyId ?? string.Empty;
AssemblyName = assemblyName;
AssemblyId = assemblyId;
FullPath = fullPath ?? assemblyId;
References = references;
DisplayString = displayString ?? assemblyId;
}

public override bool Equals(object obj) =>
/// <inheritdoc />
public override bool Equals(object? obj) =>
obj is MetadataInformation information && Equals(information);

/// <inheritdoc />
public bool Equals(MetadataInformation other) =>
string.Equals(AssemblyName, other.AssemblyName, StringComparison.OrdinalIgnoreCase) &&
string.Equals(TargetFramework, other.TargetFramework, StringComparison.OrdinalIgnoreCase) &&
string.Equals(AssemblyId, other.AssemblyId, StringComparison.Ordinal);
string.Equals(AssemblyId, other.AssemblyId, StringComparison.Ordinal) &&
string.Equals(FullPath, other.FullPath, StringComparison.OrdinalIgnoreCase);

/// <inheritdoc />
public override int GetHashCode()
{
int hashCode = 1447485498;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(AssemblyName);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(TargetFramework);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(AssemblyId);
if (FullPath != null)
{
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FullPath);
}
return hashCode;
}

/// <inheritdoc />
public override string ToString() => DisplayString;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System.Collections.Generic;
using System.IO;
using System.IO.Compression;

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
/// <summary>
/// Provider to retrieve the stream of a <see cref="MetadataInformation"/>.
/// </summary>
public class MetadataStreamProvider : IMetadataStreamProvider
{
private readonly HashSet<string> _knownArchiveExtensions = new(new string[] { ".zip", ".nupkg" });

/// <summary>
/// Get the stream from a <see cref="MetadataInformation"/>.
/// </summary>
/// <param name="metadata">Pass in a <see cref="MetadataInformation"/> to be read.</param>
/// <returns>Returns the stream from a provided <see cref="MetadataInformation"/> object.</returns>
public Stream GetStream(MetadataInformation metadata)
{
string fileExtension = Path.GetExtension(metadata.FullPath);
bool isArchive = _knownArchiveExtensions.Contains(fileExtension);

// If the assembly isn't part of an archive, read from the full path directly.
if (!isArchive)
{
return File.OpenRead(metadata.FullPath);
}

// If the assembly is part of an archive, AssemblyId is set to the relative path inside it.
MemoryStream ms = new();
using (FileStream stream = File.OpenRead(metadata.FullPath))
{
var zipFile = new ZipArchive(stream);
zipFile.GetEntry(metadata.AssemblyId)?.Open().CopyTo(ms);
ms.Seek(0, SeekOrigin.Begin);
}

return ms;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,23 @@
namespace Microsoft.DotNet.ApiCompatibility
{
/// <summary>
/// Class that performs api comparison based on ISymbol inputs.
/// Performs api comparison based on ISymbol inputs.
/// </summary>
public class ApiComparer
public class ApiComparer : IApiComparer
{
/// <summary>
/// Flag indicating whether internal symbols should be included in the comparisons or not.
/// </summary>
/// <inheritdoc />
public bool IncludeInternalSymbols { get; set; }

/// <summary>
/// Flag indicating whether we should run on strict mode or not.
/// If StrictMode is set, the behavior of some rules will change and some other rules will be
/// executed when getting the differences. This is useful when you want both sides we are comparing
/// to be strictly the same on their surface area.
/// </summary>
/// <inheritdoc />
public bool StrictMode { get; set; }

/// <summary>
/// Flag indicating whether the ApiComparison will warn if there are missing references
/// </summary>
/// <inheritdoc />
public bool WarnOnMissingReferences { get; set; }

/// <summary>
/// Callback function to get the <see cref="ComparingSettings"/> to be used when creating the settings to get the differences.
/// The callback takes a string leftName and string[] rightNames parameters to indicate API Compat via the settings what the
/// name for the left and right the user specified.
/// This callback is called at the beginning of every <see cref="GetDifferences"/> overload.
/// </summary>
/// <inheritdoc />
public Func<string, string[], ComparingSettings> GetComparingSettings { get; set; }

/// <summary>
/// Get's the differences when comparing Left vs Right based on the settings at the moment this method is called.
/// It compares two lists of symbols.
/// </summary>
/// <param name="left">Left symbols to compare against.</param>
/// <param name="right">Right symbols to compare against.</param>
/// <returns>List of found differences.</returns>
/// <inheritdoc />
public IEnumerable<CompatDifference> GetDifferences(IEnumerable<IAssemblySymbol> left, IEnumerable<IAssemblySymbol> right, string leftName = null, string rightName = null)
{
if (left == null)
Expand All @@ -68,13 +48,7 @@ public IEnumerable<CompatDifference> GetDifferences(IEnumerable<IAssemblySymbol>
return visitor.DiagnosticCollections.First();
}

/// <summary>
/// Get's the differences when comparing Left vs Right based on the settings at the moment this method is called.
/// It compares two symbols.
/// </summary>
/// <param name="left">Left symbol to compare against.</param>
/// <param name="right">Right symbol to compare against.</param>
/// <returns>List of found differences.</returns>
/// <inheritdoc />
public IEnumerable<CompatDifference> GetDifferences(IAssemblySymbol left, IAssemblySymbol right, string leftName = null, string rightName = null)
{
if (left == null)
Expand All @@ -96,14 +70,7 @@ public IEnumerable<CompatDifference> GetDifferences(IAssemblySymbol left, IAssem
return visitor.DiagnosticCollections.First();
}

/// <summary>
/// Get the differences for all the combinations of <paramref name="left"/> against each <paramref name="right"/>
/// </summary>
/// <param name="left">The left that we are going to use to compare against the multiple rights.</param>
/// <param name="right">Multiple elements to compare as the right hand side against the provided left.</param>
/// <returns>Return a list containing the (left, right) tuple and it's list of <see cref="CompatDifference"/>.
/// The returning list contains one element per (left, right) combination, which is the same length as <paramref name="right"/>.
/// </returns>
/// <inheritdoc />
public IEnumerable<(MetadataInformation left, MetadataInformation right, IEnumerable<CompatDifference> differences)> GetDifferences(ElementContainer<IAssemblySymbol> left, IList<ElementContainer<IAssemblySymbol>> right)
{
if (left == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable
ViktorHofer marked this conversation as resolved.
Show resolved Hide resolved

namespace Microsoft.DotNet.ApiCompatibility
{
/// <summary>
/// Factory to create an ApiComparer
/// </summary>
public interface IApiComparerFactory
{
/// <summary>
/// Create an ApiComparer
/// </summary>
/// <returns>Returns an ApiComparer instance</returns>
IApiComparer Create();
}

/// <summary>
/// Factory to create an ApiComparer
/// </summary>
public sealed class ApiComparerFactory : IApiComparerFactory
{
/// <inheritdoc />
public IApiComparer Create() => new ApiComparer();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.DotNet.ApiCompatibility.Abstractions;
using System;

namespace Microsoft.DotNet.ApiCompatibility
{
/// <summary>
/// Class that represents a warning that occurred while trying to load a specific assembly.
/// </summary>
public class AssemblyLoadWarning : IDiagnostic, IEquatable<AssemblyLoadWarning>
{
private readonly StringComparer _ordinalComparer = StringComparer.Ordinal;

/// <summary>
/// Creates a new instance of an <see cref="AssemblyLoadWarning"/> class with a given <paramref name="diagnosticId"/>,
/// <paramref name="referenceId"/> and <paramref name="message"/>.
/// </summary>
/// <param name="diagnosticId">String representing the diagnostic ID.</param>
/// <param name="referenceId">String representing the ID for the object that the diagnostic was created for.</param>
/// <param name="message">String describing the diagnostic.</param>
public AssemblyLoadWarning(string diagnosticId, string referenceId, string message)
{
DiagnosticId = diagnosticId;
ReferenceId = referenceId;
Message = message;
}

/// <inheritdoc/>
public string DiagnosticId { get; }

/// <inheritdoc/>
public string ReferenceId { get; }

/// <inheritdoc/>
public string Message { get; }

/// <inheritdoc/>
public bool Equals(AssemblyLoadWarning other) => _ordinalComparer.Equals(DiagnosticId, other.DiagnosticId) &&
_ordinalComparer.Equals(ReferenceId, other.ReferenceId) &&
_ordinalComparer.Equals(Message, other.Message);
}
}
Loading