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

New command-line APIs and offline argument #1068

Merged
merged 1 commit into from
Jul 24, 2023
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
2 changes: 2 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="dotnet8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" />
<add key="dotnet8-transport" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8-transport/nuget/v3/index.json" />
<!-- Feeds for command-line-api -->
<add key="dotnet-libraries" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" />
<!-- Feeds for source-build command-line-api intermediate -->
<add key="dotnet-libraries-transport" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries-transport/nuget/v3/index.json" />
</packageSources>
Expand Down
16 changes: 8 additions & 8 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Dependencies>
<ProductDependencies>
<Dependency Name="System.CommandLine" Version="2.0.0-beta4.22272.1">
<Dependency Name="System.CommandLine" Version="2.0.0-beta4.23307.1">
<Uri>https://github.com/dotnet/command-line-api</Uri>
<Sha>209b724a3c843253d3071e8348c353b297b0b8b5</Sha>
<Sha>02fe27cd6a9b001c8feb7938e6ef4b3799745759</Sha>
</Dependency>
<Dependency Name="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1">
<Dependency Name="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.23307.1">
<Uri>https://github.com/dotnet/command-line-api</Uri>
<Sha>209b724a3c843253d3071e8348c353b297b0b8b5</Sha>
<Sha>02fe27cd6a9b001c8feb7938e6ef4b3799745759</Sha>
</Dependency>
<Dependency Name="System.CommandLine.Rendering" Version="0.4.0-alpha.22272.1">
<Dependency Name="System.CommandLine.Rendering" Version="0.4.0-alpha.23307.1">
<Uri>https://github.com/dotnet/command-line-api</Uri>
<Sha>209b724a3c843253d3071e8348c353b297b0b8b5</Sha>
<Sha>02fe27cd6a9b001c8feb7938e6ef4b3799745759</Sha>
</Dependency>
<Dependency Name="Microsoft.SourceBuild.Intermediate.command-line-api" Version="0.1.327201">
<Dependency Name="Microsoft.SourceBuild.Intermediate.command-line-api" Version="0.1.430701">
<Uri>https://github.com/dotnet/command-line-api</Uri>
<Sha>209b724a3c843253d3071e8348c353b297b0b8b5</Sha>
<Sha>02fe27cd6a9b001c8feb7938e6ef4b3799745759</Sha>
<SourceBuild RepoName="command-line-api" ManagedOnly="true" />
</Dependency>
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="8.0.0-alpha.1.23362.3">
Expand Down
6 changes: 3 additions & 3 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
</PropertyGroup>
<PropertyGroup>
<!-- commandline -->
<SystemCommandLineVersion>2.0.0-beta4.22272.1</SystemCommandLineVersion>
<SystemCommandLineNamingConventionBinderVersion>2.0.0-beta4.22272.1</SystemCommandLineNamingConventionBinderVersion>
<SystemCommandLineRenderingVersion>0.4.0-alpha.22272.1</SystemCommandLineRenderingVersion>
<SystemCommandLineVersion>2.0.0-beta4.23307.1</SystemCommandLineVersion>
<SystemCommandLineNamingConventionBinderVersion>2.0.0-beta4.23307.1</SystemCommandLineNamingConventionBinderVersion>
<SystemCommandLineRenderingVersion>0.4.0-alpha.23307.1</SystemCommandLineRenderingVersion>
<!-- msbuild -->
<MicrosoftBuildVersion>17.3.2</MicrosoftBuildVersion>
<MicrosoftBuildTasksCoreVersion>17.3.2</MicrosoftBuildTasksCoreVersion>
Expand Down
160 changes: 100 additions & 60 deletions src/dotnet-sourcelink/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@ private record DocumentInfo(
ImmutableArray<byte> Hash,
Guid HashAlgorithm);

private readonly IConsole _console;
private readonly ParseResult _parseResult;
private bool _errorReported;

public Program(IConsole console)
public Program(ParseResult parseResult)
{
_console = console;
_parseResult = parseResult;
}

public static async Task<int> Main(string[] args)
{
var rootCommand = GetRootCommand();
return await rootCommand.InvokeAsync(args);
return await rootCommand.Parse(args).InvokeAsync();
}

private static string GetSourceLinkVersion()
Expand All @@ -62,41 +62,77 @@ private static string GetSourceLinkVersion()
return attribute.InformationalVersion.Split('+').First();
}

private static RootCommand GetRootCommand()
private static CliRootCommand GetRootCommand()
{
var authArg = new Option<string>(new[] { "--auth", "-a" }, "Authentication method").FromAmong(AuthenticationMethod.Basic);
var userArg = new Option<string>(new[] { "--user", "-u" }, "Username to use to authenticate") { Arity = ArgumentArity.ExactlyOne };
var passwordArg = new Option<string>(new[] { "--password", "-p" }, "Password to use to authenticate") { Arity = ArgumentArity.ExactlyOne };
var authArg = new CliOption<string>("--auth", "-a")
{
Description = "Authentication method"
};
authArg.AcceptOnlyFromAmong(AuthenticationMethod.Basic);

var userArg = new CliOption<string>("--user", "-u")
{
Description = "Username to use to authenticate",
Arity = ArgumentArity.ExactlyOne
};

var passwordArg = new CliOption<string>("--password", "-p")
{
Description = "Password to use to authenticate",
Arity = ArgumentArity.ExactlyOne
};

var test = new Command("test", "TODO")
var offlineArg = new CliOption<bool>("--offline")
{
new Argument<string>("path", "Path to an assembly or .pdb"),
Description = "Offline mode - skip validation of sourcelink URL targets"
};

var test = new CliCommand("test", "TODO")
{
new CliArgument<string>("path")
{
Description = "Path to an assembly or .pdb"
},
authArg,
new Option<Encoding>(new[] { "--auth-encoding", "-e" }, (arg) => Encoding.GetEncoding(arg.Tokens.Single().Value), false, "Encoding to use for authentication value"),
new CliOption<Encoding>("--auth-encoding", "-e")
{
CustomParser = arg => Encoding.GetEncoding(arg.Tokens.Single().Value),
Description = "Encoding to use for authentication value"
},
userArg,
passwordArg,
offlineArg,
};
test.Handler = CommandHandler.Create<string, string?, Encoding?, string?, string?, IConsole>(TestAsync);
test.Action = CommandHandler.Create<string, string?, Encoding?, string?, string?, bool, ParseResult>(TestAsync);

var printJson = new Command("print-json", "Print Source Link JSON stored in the PDB")
var printJson = new CliCommand("print-json", "Print Source Link JSON stored in the PDB")
{
new Argument<string>("path", "Path to an assembly or .pdb"),
new CliArgument<string>("path")
{
Description = "Path to an assembly or .pdb"
}
};
printJson.Handler = CommandHandler.Create<string, IConsole>(PrintJsonAsync);
printJson.Action = CommandHandler.Create<string, ParseResult>(PrintJsonAsync);

var printDocuments = new Command("print-documents", "TODO")
var printDocuments = new CliCommand("print-documents", "TODO")
{
new Argument<string>("path", "Path to an assembly or .pdb"),
new CliArgument<string>("path")
{
Description = "Path to an assembly or .pdb"
}
};
printDocuments.Handler = CommandHandler.Create<string, IConsole>(PrintDocumentsAsync);
printDocuments.Action = CommandHandler.Create<string, ParseResult>(PrintDocumentsAsync);

var printUrls = new Command("print-urls", "TODO")
var printUrls = new CliCommand("print-urls", "TODO")
{
new Argument<string>("path", "Path to an assembly or .pdb"),
new CliArgument<string>("path")
{
Description = "Path to an assembly or .pdb"
}
};
printUrls.Handler = CommandHandler.Create<string, IConsole>(PrintUrlsAsync);
printUrls.Action = CommandHandler.Create<string, ParseResult>(PrintUrlsAsync);

var root = new RootCommand()
var root = new CliRootCommand()
{
test,
printJson,
Expand All @@ -106,13 +142,13 @@ private static RootCommand GetRootCommand()

root.Description = "dotnet-sourcelink";

root.AddValidator(commandResult =>
root.Validators.Add(commandResult =>
{
if (commandResult.FindResultFor(authArg) != null)
if (commandResult.GetResult(authArg) != null)
{
if (commandResult.FindResultFor(userArg) == null || commandResult.FindResultFor(passwordArg) == null)
if (commandResult.GetResult(userArg) == null || commandResult.GetResult(passwordArg) == null)
{
commandResult.ErrorMessage = "Specify --user and --password options";
commandResult.AddError("Specify --user and --password options");
}
}
});
Expand All @@ -122,15 +158,15 @@ private static RootCommand GetRootCommand()

private void ReportError(string message)
{
_console.Error.Write(message);
_console.Error.Write(Environment.NewLine);
_parseResult.Configuration.Error.Write(message);
_parseResult.Configuration.Error.Write(Environment.NewLine);
_errorReported = true;
}

private void WriteOutputLine(string message)
{
_console.Out.Write(message);
_console.Out.Write(Environment.NewLine);
_parseResult.Configuration.Output.Write(message);
_parseResult.Configuration.Output.Write(Environment.NewLine);
}

private static async Task<int> TestAsync(
Expand All @@ -139,7 +175,8 @@ private static async Task<int> TestAsync(
Encoding? authEncoding,
string? user,
string? password,
IConsole console)
bool offline,
ParseResult parseResult)
{
var authenticationHeader = (authMethod != null) ? GetAuthenticationHeader(authMethod, authEncoding ?? Encoding.ASCII, user!, password!) : null;

Expand All @@ -152,17 +189,17 @@ private static async Task<int> TestAsync(

try
{
return await new Program(console).TestAsync(path, authenticationHeader, cancellationSource.Token).ConfigureAwait(false);
return await new Program(parseResult).TestAsync(path, authenticationHeader, offline, cancellationSource.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
console.Error.Write("Operation canceled.");
console.Error.Write(Environment.NewLine);
parseResult.Configuration.Error.Write("Operation canceled.");
parseResult.Configuration.Error.Write(Environment.NewLine);
return -1;
}
}

private async Task<int> TestAsync(string path, AuthenticationHeaderValue? authenticationHeader, CancellationToken cancellationToken)
private async Task<int> TestAsync(string path, AuthenticationHeaderValue? authenticationHeader, bool offline, CancellationToken cancellationToken)
{
var documents = new List<DocumentInfo>();
ReadAndResolveDocuments(path, documents);
Expand All @@ -172,31 +209,34 @@ private async Task<int> TestAsync(string path, AuthenticationHeaderValue? authen
return _errorReported ? 1 : 0;
}

var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
if (!offline)
{
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

using var client = new HttpClient(handler);
client.DefaultRequestHeaders.UserAgent.Add(s_sourceLinkProductHeaderValue);
client.DefaultRequestHeaders.Authorization = authenticationHeader;
using var client = new HttpClient(handler);
client.DefaultRequestHeaders.UserAgent.Add(s_sourceLinkProductHeaderValue);
client.DefaultRequestHeaders.Authorization = authenticationHeader;

var outputLock = new object();
var outputLock = new object();

var errorReporter = new Action<string>(message =>
{
lock (outputLock)
var errorReporter = new Action<string>(message =>
{
ReportError(message);
}
});
lock (outputLock)
{
ReportError(message);
}
});

var tasks = documents.Where(document => document.Uri != null).Select(document => DownloadAndValidateDocumentAsync(client, document, errorReporter, cancellationToken));

_ = await Task.WhenAll(tasks).ConfigureAwait(false);
var tasks = documents.Where(document => document.Uri != null).Select(document => DownloadAndValidateDocumentAsync(client, document, errorReporter, cancellationToken));

if (_errorReported)
{
return 1;
_ = await Task.WhenAll(tasks).ConfigureAwait(false);

if (_errorReported)
{
return 1;
}
}

WriteOutputLine($"File '{path}' validated.");
Expand Down Expand Up @@ -277,8 +317,8 @@ private static async Task<bool> DownloadAndValidateDocumentAsync(HttpClient clie
}
}

private static Task<int> PrintJsonAsync(string path, IConsole console)
=> Task.FromResult(new Program(console).PrintJson(path));
private static Task<int> PrintJsonAsync(string path, ParseResult parseResult)
=> Task.FromResult(new Program(parseResult).PrintJson(path));

private int PrintJson(string path)
{
Expand All @@ -299,8 +339,8 @@ private int PrintJson(string path)
return _errorReported ? 1 : 0;
}

private static Task<int> PrintDocumentsAsync(string path, IConsole console)
=> Task.FromResult(new Program(console).PrintDocuments(path));
private static Task<int> PrintDocumentsAsync(string path, ParseResult parseResult)
=> Task.FromResult(new Program(parseResult).PrintDocuments(path));

public static string ToHex(byte[] bytes)
=> BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
Expand All @@ -324,8 +364,8 @@ private int PrintDocuments(string path)
return _errorReported ? 1 : 0;
}

private static Task<int> PrintUrlsAsync(string path,IConsole console)
=> Task.FromResult(new Program(console).PrintUrls(path));
private static Task<int> PrintUrlsAsync(string path, ParseResult parseResult)
=> Task.FromResult(new Program(parseResult).PrintUrls(path));

private int PrintUrls(string path)
{
Expand Down
4 changes: 1 addition & 3 deletions src/dotnet-sourcelink/dotnet-sourcelink.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@
<TargetFrameworks>$(NetCurrent)</TargetFrameworks>
<!-- Allow tool to roll forward to a newer major version. -->
<RollForward>Major</RollForward>
<!-- Will be removed by https://github.com/dotnet/sourcelink/issues/1028 -->
<ExcludeFromSourceBuild>true</ExcludeFromSourceBuild>

<!-- NuGet -->
<IsPackable>true</IsPackable>
<PackAsTool>True</PackAsTool>
<ToolCommandName>sourcelink</ToolCommandName>
<Description>Command line tool for SourceLink testing.</Description>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<PackAsToolShimRuntimeIdentifiers>win-x64;win-x86;osx-x64</PackAsToolShimRuntimeIdentifiers>
<PackAsToolShimRuntimeIdentifiers Condition="'$(DotNetBuildFromSource)' != 'true'">win-x64;win-x86;osx-x64</PackAsToolShimRuntimeIdentifiers>
</PropertyGroup>

<ItemGroup>
Expand Down