Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -18,96 +18,96 @@ public static class Program

public static async Task Main(string[] args)
{
RootCommand rootCommand = new("genapidiff");
RootCommand rootCommand = new("ApiDiff - Tool for generating a markdown diff of two different versions of the same assembly.");

Option<string> optionBeforeAssembliesFolderPath = new(name: "", aliases: ["--before", "-b"])
Option<string> optionBeforeAssembliesFolderPath = new("--before", "-b")
{
Description = "The path to the folder containing the old (before) assemblies to be included in the diff.",
Arity = ArgumentArity.ExactlyOne,
Required = true
};

Option<string> optionBeforeRefAssembliesFolderPath = new(name: "", aliases: ["--refbefore", "-rb"])
Option<string> optionBeforeRefAssembliesFolderPath = new("--refbefore", "-rb")
{
Description = "The path to the folder containing the references required by old (before) assemblies, not to be included in the diff.",
Arity = ArgumentArity.ExactlyOne,
Required = false
};

Option<string> optionAfterAssembliesFolderPath = new(name: "", aliases: ["--after", "-a"])
Option<string> optionAfterAssembliesFolderPath = new("--after", "-a")
{
Description = "The path to the folder containing the new (after) assemblies to be included in the diff.",
Arity = ArgumentArity.ExactlyOne,
Required = true
};

Option<string> optionAfterRefAssembliesFolderPath = new(name: "", aliases: ["--refafter", "-ra"])
Option<string> optionAfterRefAssembliesFolderPath = new("--refafter", "-ra")
{
Description = "The path to the folder containing references required by the new (after) reference assemblies, not to be included in the diff.",
Arity = ArgumentArity.ExactlyOne,
Required = false
};

Option<string> optionOutputFolderPath = new(name: "", aliases: ["--output", "-o"])
Option<string> optionOutputFolderPath = new("--output", "-o")
{
Description = "The path to the output folder.",
Arity = ArgumentArity.ExactlyOne,
Required = true
};

Option<string> optionBeforeFriendlyName = new(name: "", aliases: ["--beforeFriendlyName", "-bfn"])
Option<string> optionBeforeFriendlyName = new("--beforeFriendlyName", "-bfn")
{
Description = "The friendly name to describe the 'before' assembly.",
Arity = ArgumentArity.ExactlyOne,
Required = true
};

Option<string> optionAfterFriendlyName = new(name: "", aliases: ["--afterFriendlyName", "-afn"])
Option<string> optionAfterFriendlyName = new("--afterFriendlyName", "-afn")
{
Description = "The friendly name to describe the 'after' assembly.",
Arity = ArgumentArity.ExactlyOne,
Required = true
};

Option<string> optionTableOfContentsTitle = new(name: "", aliases: ["--tableOfContentsTitle", "-tc"])
Option<string> optionTableOfContentsTitle = new("--tableOfContentsTitle", "-tc")
{
Description = $"The optional title of the markdown table of contents file that is placed in the output folder.",
Arity = ArgumentArity.ZeroOrMore,
Arity = ArgumentArity.ExactlyOne,
Required = false,
DefaultValueFactory = _ => "api_diff"
};

Option<FileInfo[]?> optionFilesWithAssembliesToExclude = new(name: "", aliases: ["--assembliesToExclude", "-eas"])
Option<FileInfo[]?> optionFilesWithAssembliesToExclude = new("--assembliesToExclude", "-eas")
{
Description = "An optional array of filepaths, each containing a list of assemblies that should be excluded from the diff. Each file should contain one assembly name per line, with no extensions.",
Arity = ArgumentArity.ZeroOrMore,
Required = false,
DefaultValueFactory = _ => null
};

Option<FileInfo[]?> optionFilesWithAttributesToExclude = new(name: "", aliases: ["--attributesToExclude", "-eattrs"])
Option<FileInfo[]?> optionFilesWithAttributesToExclude = new("--attributesToExclude", "-eattrs")
{
Description = $"An optional array of filepaths, each containing a list of attributes to exclude from the diff. Each file should contain one API full name per line. You can either modify the default file '{AttributesToExcludeDefaultFileName}' to add your own attributes, or include additional files using this command line option.",
Arity = ArgumentArity.ZeroOrMore,
Required = false,
DefaultValueFactory = _ => [new FileInfo(AttributesToExcludeDefaultFileName)]
};

Option<FileInfo[]?> optionFilesWithApisToExclude = new(name: "", aliases: ["--apisToExclude", "-eapis"])
Option<FileInfo[]?> optionFilesWithApisToExclude = new("--apisToExclude", "-eapis")
{
Description = "An optional array of filepaths, each containing a list of APIs to exclude from the diff. Each file should contain one API full name per line.",
Arity = ArgumentArity.ZeroOrMore,
Required = false,
DefaultValueFactory = _ => null
};

Option<bool> optionAddPartialModifier = new(name: "", aliases: ["--addPartialModifier", "-apm"])
Option<bool> optionAddPartialModifier = new("--addPartialModifier", "-apm")
{
Description = "Add the 'partial' modifier to types.",
DefaultValueFactory = _ => false
};

Option<bool> optionAttachDebugger = new(name: "", aliases: ["--attachDebugger", "-d"])
Option<bool> optionAttachDebugger = new("--attachDebugger", "-d")
{
Description = "Stops the tool at startup, prints the process ID and waits for a debugger to attach.",
DefaultValueFactory = _ => false
Expand All @@ -128,9 +128,9 @@ public static async Task Main(string[] args)
rootCommand.Options.Add(optionAddPartialModifier);
rootCommand.Options.Add(optionAttachDebugger);

rootCommand.SetAction(async (ParseResult result) =>
rootCommand.SetAction(async (ParseResult result, CancellationToken cancellationToken) =>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only functional fix here. The rest is cleanup after a5fe497

{
DiffConfiguration c = new(
DiffConfiguration diffConfig = new(
BeforeAssembliesFolderPath: result.GetValue(optionBeforeAssembliesFolderPath) ?? throw new NullReferenceException("Null before assemblies directory"),
BeforeAssemblyReferencesFolderPath: result.GetValue(optionBeforeRefAssembliesFolderPath),
AfterAssembliesFolderPath: result.GetValue(optionAfterAssembliesFolderPath) ?? throw new NullReferenceException("Null after assemblies directory"),
Expand All @@ -145,13 +145,15 @@ public static async Task Main(string[] args)
AddPartialModifier: result.GetValue(optionAddPartialModifier),
AttachDebugger: result.GetValue(optionAttachDebugger)
);
await HandleCommandAsync(c).ConfigureAwait(false);
await HandleCommandAsync(diffConfig, cancellationToken).ConfigureAwait(false);
});
await rootCommand.Parse(args).InvokeAsync();
}

private static Task HandleCommandAsync(DiffConfiguration diffConfig)
private static Task HandleCommandAsync(DiffConfiguration diffConfig, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

var log = new ConsoleLog(MessageImportance.Normal);

string assembliesToExclude = string.Join(", ", diffConfig.FilesWithAssembliesToExclude?.Select(a => a.FullName) ?? []);
Expand Down Expand Up @@ -197,7 +199,7 @@ private static Task HandleCommandAsync(DiffConfiguration diffConfig)
diagnosticOptions: null // TODO: If needed, add CLI option to pass specific diagnostic options
);

return diffGenerator.RunAsync();
return diffGenerator.RunAsync(cancellationToken);
}

private static void WaitForDebugger()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,13 @@ internal FileOutputDiffGenerator(ILog log,
public IReadOnlyDictionary<string, string> Results => _results.AsReadOnly();

/// <inheritdoc/>
public async Task RunAsync()
public async Task RunAsync(CancellationToken cancellationToken)
{
Debug.Assert(_beforeAssembliesFolderPaths.Length == 1);
Debug.Assert(_afterAssembliesFolderPaths.Length == 1);

cancellationToken.ThrowIfCancellationRequested();

(IAssemblySymbolLoader beforeLoader, Dictionary<string, IAssemblySymbol> beforeAssemblySymbols) =
AssemblySymbolLoader.CreateFromFiles(
_log,
Expand All @@ -118,7 +120,7 @@ public async Task RunAsync()
_addPartialModifier,
_diagnosticOptions);

await generator.RunAsync().ConfigureAwait(false);
await generator.RunAsync(cancellationToken).ConfigureAwait(false);

// If true, output is disk. Otherwise, it's the Results dictionary.
if (_writeToDisk)
Expand All @@ -135,6 +137,8 @@ public async Task RunAsync()

foreach ((string assemblyName, string text) in generator.Results.OrderBy(r => r.Key))
{
cancellationToken.ThrowIfCancellationRequested();

string fileName = $"{_tableOfContentsTitle}_{assemblyName}.md";
tableOfContents.AppendLine($"* [{assemblyName}]({fileName})");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public interface IDiffGenerator
/// <summary>
/// Asynchronously runs the diff generator and may populate the <see cref="Results"/> dictionary depending on the use case.
/// </summary>
Task RunAsync();
Task RunAsync(CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,23 @@ internal MemoryOutputDiffGenerator(
public IReadOnlyDictionary<string, string> Results => _results.AsReadOnly();

/// <inheritdoc/>
public async Task RunAsync()
public async Task RunAsync(CancellationToken cancellationToken)
{
Stopwatch swRun = Stopwatch.StartNew();

foreach ((string beforeAssemblyName, IAssemblySymbol beforeAssemblySymbol) in _beforeAssemblySymbols)
{
cancellationToken.ThrowIfCancellationRequested();

// Needs to block so the _afterAssemblySymbols dictionary gets updated.
await ProcessBeforeAndAfterAssemblyAsync(beforeAssemblyName, beforeAssemblySymbol).ConfigureAwait(false);
}

// Needs to happen after processing the before and after assemblies and filtering out the existing ones.
foreach ((string afterAssemblyName, IAssemblySymbol afterAssemblySymbol) in _afterAssemblySymbols)
{
cancellationToken.ThrowIfCancellationRequested();

await ProcessNewAssemblyAsync(afterAssemblyName, afterAssemblySymbol).ConfigureAwait(false);
}

Expand Down
2 changes: 1 addition & 1 deletion test/Microsoft.DotNet.ApiDiff.Tests/Diff.Base.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ protected async Task RunTestAsync(
addPartialModifier,
DiffGeneratorFactory.DefaultDiagnosticOptions);

await generator.RunAsync();
await generator.RunAsync(CancellationToken.None);

foreach ((string expectedAssemblyName, string expectedCode) in expected)
{
Expand Down
12 changes: 6 additions & 6 deletions test/Microsoft.DotNet.ApiDiff.Tests/Diff.Disk.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public async Task DiskRead_DiskWrite()
outputFolderPath.DirPath,
writeToDisk: true);

await generator.RunAsync();
await generator.RunAsync(CancellationToken.None);

VerifyDiskWrite(outputFolderPath.DirPath, DefaultTableOfContentsTitle, ExpectedTableOfContents, DefaultExpectedAssemblyMarkdowns);
}
Expand Down Expand Up @@ -255,7 +255,7 @@ Lines preceded by a '+' are additions and a '-' indicates removal.
outputFolderPath.DirPath,
writeToDisk: true);

await generator.RunAsync();
await generator.RunAsync(CancellationToken.None);

VerifyDiskWrite(outputFolderPath.DirPath, DefaultTableOfContentsTitle, expectedTableOfContents, expectedAssemblyMarkdowns);
}
Expand Down Expand Up @@ -286,7 +286,7 @@ public async Task DiskRead_DiskWrite_ExcludeAssembly()
writeToDisk: true,
filesWithAssembliesToExclude: await GetFileWithListsAsync(root, [DefaultAssemblyName]));

await generator.RunAsync();
await generator.RunAsync(CancellationToken.None);

VerifyDiskWrite(outputFolderPath.FullName, DefaultTableOfContentsTitle, ExpectedEmptyTableOfContents, []);
}
Expand Down Expand Up @@ -376,7 +376,7 @@ public class MySubClass
outputFolderPath.DirPath,
writeToDisk: true);

await generator.RunAsync();
await generator.RunAsync(CancellationToken.None);

VerifyDiskWrite(outputFolderPath.DirPath, DefaultTableOfContentsTitle, ExpectedTableOfContents, expectedAssemblyMarkdowns);
}
Expand All @@ -401,7 +401,7 @@ public async Task DiskRead_MemoryWrite()
outputFolderPath.DirPath,
writeToDisk: false);

await generator.RunAsync();
await generator.RunAsync(CancellationToken.None);

string tableOfContentsMarkdownFilePath = Path.Join(outputFolderPath.DirPath, $"{DefaultTableOfContentsTitle}.md");
Assert.Contains(tableOfContentsMarkdownFilePath, generator.Results.Keys);
Expand Down Expand Up @@ -440,7 +440,7 @@ public async Task DiskRead_MemoryWrite_ExcludeAssembly()
writeToDisk: false,
filesWithAssembliesToExclude: await GetFileWithListsAsync(root, [DefaultAssemblyName]));

await generator.RunAsync();
await generator.RunAsync(CancellationToken.None);

string tableOfContentsMarkdownFilePath = Path.Join(outputFolderPath.FullName, $"{DefaultTableOfContentsTitle}.md");
Assert.Contains(tableOfContentsMarkdownFilePath, generator.Results.Keys);
Expand Down