Skip to content

Commit

Permalink
New command-line APIs (#1068)
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolaMilosavljevic authored Jul 24, 2023
1 parent 12c1566 commit 8ec80a1
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 74 deletions.
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.23371.1">
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

0 comments on commit 8ec80a1

Please sign in to comment.