From c14170023a2cbd21b03fe1beeca3530f5d7e58bc Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Fri, 15 Mar 2024 20:58:05 +0100 Subject: [PATCH 1/6] update folder structure --- DotnetThirdPartyNotices.sln | 2 +- .../Commands/ScanCommand.cs | 0 .../Directory.Packages.props | 0 .../DotnetThirdPartyNotices.csproj | 0 .../Models/NuSpec.cs | 0 .../Models/ResolvedFileInfo.cs | 0 .../Models/TargetFrameworkIdentifiers.cs | 0 .../Program.cs | 0 .../Properties/launchSettings.json | 0 .../Services/GithubRawLicenseResolver.cs | 0 .../Services/GithubRepositoryLicenseResolver.cs | 0 .../Services/IFileVersionInfoLicenseResolver.cs | 0 .../Services/ILicenseResolver.cs | 0 .../Services/ILicenseService.cs | 0 .../Services/ILicenseUriLicenseResolver.cs | 0 .../Services/ILocalPackageService.cs | 0 .../Services/IProjectService.cs | 0 .../Services/IProjectUriLicenseResolver.cs | 0 .../Services/IRepositoryUriLicenseResolver.cs | 0 .../Services/IUriLicenseResolver.cs | 0 .../Services/LicenseService.cs | 0 .../Services/LocalPackageService.cs | 0 .../Services/NetFrameworkLicenseResolver.cs | 0 .../Services/OpenSourceOrgLicenseResolver.cs | 0 .../Services/ProjectService.cs | 0 .../dotnet_library_license.txt | 0 26 files changed, 1 insertion(+), 1 deletion(-) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Commands/ScanCommand.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Directory.Packages.props (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/DotnetThirdPartyNotices.csproj (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Models/NuSpec.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Models/ResolvedFileInfo.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Models/TargetFrameworkIdentifiers.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Program.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Properties/launchSettings.json (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/GithubRawLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/GithubRepositoryLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/IFileVersionInfoLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/ILicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/ILicenseService.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/ILicenseUriLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/ILocalPackageService.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/IProjectService.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/IProjectUriLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/IRepositoryUriLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/IUriLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/LicenseService.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/LocalPackageService.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/NetFrameworkLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/OpenSourceOrgLicenseResolver.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/Services/ProjectService.cs (100%) rename {src/DotnetThirdPartyNotices => DotnetThirdPartyNotices}/dotnet_library_license.txt (100%) diff --git a/DotnetThirdPartyNotices.sln b/DotnetThirdPartyNotices.sln index e67527a..d79699d 100644 --- a/DotnetThirdPartyNotices.sln +++ b/DotnetThirdPartyNotices.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28010.2036 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetThirdPartyNotices", "src\DotnetThirdPartyNotices\DotnetThirdPartyNotices.csproj", "{052DFD6A-FD20-44C2-9448-0ACFDBFFEFCB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetThirdPartyNotices", "DotnetThirdPartyNotices\DotnetThirdPartyNotices.csproj", "{052DFD6A-FD20-44C2-9448-0ACFDBFFEFCB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/DotnetThirdPartyNotices/Commands/ScanCommand.cs b/DotnetThirdPartyNotices/Commands/ScanCommand.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Commands/ScanCommand.cs rename to DotnetThirdPartyNotices/Commands/ScanCommand.cs diff --git a/src/DotnetThirdPartyNotices/Directory.Packages.props b/DotnetThirdPartyNotices/Directory.Packages.props similarity index 100% rename from src/DotnetThirdPartyNotices/Directory.Packages.props rename to DotnetThirdPartyNotices/Directory.Packages.props diff --git a/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj b/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj similarity index 100% rename from src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj rename to DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj diff --git a/src/DotnetThirdPartyNotices/Models/NuSpec.cs b/DotnetThirdPartyNotices/Models/NuSpec.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Models/NuSpec.cs rename to DotnetThirdPartyNotices/Models/NuSpec.cs diff --git a/src/DotnetThirdPartyNotices/Models/ResolvedFileInfo.cs b/DotnetThirdPartyNotices/Models/ResolvedFileInfo.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Models/ResolvedFileInfo.cs rename to DotnetThirdPartyNotices/Models/ResolvedFileInfo.cs diff --git a/src/DotnetThirdPartyNotices/Models/TargetFrameworkIdentifiers.cs b/DotnetThirdPartyNotices/Models/TargetFrameworkIdentifiers.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Models/TargetFrameworkIdentifiers.cs rename to DotnetThirdPartyNotices/Models/TargetFrameworkIdentifiers.cs diff --git a/src/DotnetThirdPartyNotices/Program.cs b/DotnetThirdPartyNotices/Program.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Program.cs rename to DotnetThirdPartyNotices/Program.cs diff --git a/src/DotnetThirdPartyNotices/Properties/launchSettings.json b/DotnetThirdPartyNotices/Properties/launchSettings.json similarity index 100% rename from src/DotnetThirdPartyNotices/Properties/launchSettings.json rename to DotnetThirdPartyNotices/Properties/launchSettings.json diff --git a/src/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs b/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs b/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs b/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/ILicenseResolver.cs b/DotnetThirdPartyNotices/Services/ILicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/ILicenseResolver.cs rename to DotnetThirdPartyNotices/Services/ILicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/ILicenseService.cs b/DotnetThirdPartyNotices/Services/ILicenseService.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/ILicenseService.cs rename to DotnetThirdPartyNotices/Services/ILicenseService.cs diff --git a/src/DotnetThirdPartyNotices/Services/ILicenseUriLicenseResolver.cs b/DotnetThirdPartyNotices/Services/ILicenseUriLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/ILicenseUriLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/ILicenseUriLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/ILocalPackageService.cs b/DotnetThirdPartyNotices/Services/ILocalPackageService.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/ILocalPackageService.cs rename to DotnetThirdPartyNotices/Services/ILocalPackageService.cs diff --git a/src/DotnetThirdPartyNotices/Services/IProjectService.cs b/DotnetThirdPartyNotices/Services/IProjectService.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/IProjectService.cs rename to DotnetThirdPartyNotices/Services/IProjectService.cs diff --git a/src/DotnetThirdPartyNotices/Services/IProjectUriLicenseResolver.cs b/DotnetThirdPartyNotices/Services/IProjectUriLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/IProjectUriLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/IProjectUriLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/IRepositoryUriLicenseResolver.cs b/DotnetThirdPartyNotices/Services/IRepositoryUriLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/IRepositoryUriLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/IRepositoryUriLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs b/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/LicenseService.cs b/DotnetThirdPartyNotices/Services/LicenseService.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/LicenseService.cs rename to DotnetThirdPartyNotices/Services/LicenseService.cs diff --git a/src/DotnetThirdPartyNotices/Services/LocalPackageService.cs b/DotnetThirdPartyNotices/Services/LocalPackageService.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/LocalPackageService.cs rename to DotnetThirdPartyNotices/Services/LocalPackageService.cs diff --git a/src/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs b/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs b/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs rename to DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs diff --git a/src/DotnetThirdPartyNotices/Services/ProjectService.cs b/DotnetThirdPartyNotices/Services/ProjectService.cs similarity index 100% rename from src/DotnetThirdPartyNotices/Services/ProjectService.cs rename to DotnetThirdPartyNotices/Services/ProjectService.cs diff --git a/src/DotnetThirdPartyNotices/dotnet_library_license.txt b/DotnetThirdPartyNotices/dotnet_library_license.txt similarity index 100% rename from src/DotnetThirdPartyNotices/dotnet_library_license.txt rename to DotnetThirdPartyNotices/dotnet_library_license.txt From 50c558607066bd82b77a91a75f590ff6879aa309 Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Fri, 15 Mar 2024 21:52:11 +0100 Subject: [PATCH 2/6] support GitHub token --- DotnetThirdPartyNotices/Commands/ScanCommand.cs | 7 +++++-- DotnetThirdPartyNotices/Models/DynamicSettings.cs | 12 ++++++++++++ DotnetThirdPartyNotices/Program.cs | 2 ++ .../Services/GithubRawLicenseResolver.cs | 10 ++++++++-- .../Services/GithubRepositoryLicenseResolver.cs | 8 ++++++-- .../Services/OpenSourceOrgLicenseResolver.cs | 8 ++++++-- 6 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 DotnetThirdPartyNotices/Models/DynamicSettings.cs diff --git a/DotnetThirdPartyNotices/Commands/ScanCommand.cs b/DotnetThirdPartyNotices/Commands/ScanCommand.cs index bc27bc1..08a6f53 100644 --- a/DotnetThirdPartyNotices/Commands/ScanCommand.cs +++ b/DotnetThirdPartyNotices/Commands/ScanCommand.cs @@ -24,15 +24,17 @@ internal class ScanCommand : Command AddOption(new Option("--output-filename", () => "third-party-notices.txt", "Output filename")); AddOption(new Option("--copy-to-outdir", () => false, "Copy output file to output directory in Release configuration")); AddOption(new Option("--filter", () => string.Empty, "Filter project files")); + AddOption(new Option("--github-token", () => string.Empty, "GitHub's token")); } - internal new class Handler(ILogger logger, IProjectService projectService, ILicenseService licenseService) : ICommandHandler + internal new class Handler(ILogger logger, IProjectService projectService, ILicenseService licenseService, DynamicSettings dynamicSettings) : ICommandHandler { public string? ScanDir { get; set; } public string? OutputFilename { get; set; } public bool CopyToOutDir { get; set; } public string? Filter { get; set; } - public bool Merge { get; set; } + public string? GithubToken { get; set; } + private readonly Dictionary> _licenseContents = []; private readonly List _unresolvedFiles = []; @@ -45,6 +47,7 @@ public async Task InvokeAsync(InvocationContext context) { MSBuildLocator.RegisterDefaults(); ScanDir ??= Directory.GetCurrentDirectory(); + dynamicSettings.GitHubToken = GithubToken; var projectFilePaths = projectService.GetProjectFilePaths(ScanDir); projectFilePaths = GetFilteredProjectPathes(projectFilePaths); if (projectFilePaths.Length == 0) diff --git a/DotnetThirdPartyNotices/Models/DynamicSettings.cs b/DotnetThirdPartyNotices/Models/DynamicSettings.cs new file mode 100644 index 0000000..2ac18ff --- /dev/null +++ b/DotnetThirdPartyNotices/Models/DynamicSettings.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DotnetThirdPartyNotices.Models; + +internal class DynamicSettings +{ + public string? GitHubToken { get; set; } +} diff --git a/DotnetThirdPartyNotices/Program.cs b/DotnetThirdPartyNotices/Program.cs index 3918199..279691f 100644 --- a/DotnetThirdPartyNotices/Program.cs +++ b/DotnetThirdPartyNotices/Program.cs @@ -1,4 +1,5 @@ using DotnetThirdPartyNotices.Commands; +using DotnetThirdPartyNotices.Models; using DotnetThirdPartyNotices.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -34,6 +35,7 @@ x.AddSingleton(); x.AddSingleton(); x.AddSingleton(); + x.AddSingleton(); x.AddHttpClient(); var assembly = Assembly.GetExecutingAssembly(); var types = assembly.GetTypes(); diff --git a/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs b/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs index ba49b13..4f9fbdf 100644 --- a/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs @@ -1,6 +1,10 @@ -namespace DotnetThirdPartyNotices.Services; +using DotnetThirdPartyNotices.Models; +using System.Net.Http.Headers; +using System.Text; -internal class GithubRawLicenseResolver(IHttpClientFactory httpClientFactory) : ILicenseUriLicenseResolver +namespace DotnetThirdPartyNotices.Services; + +internal class GithubRawLicenseResolver(IHttpClientFactory httpClientFactory, DynamicSettings dynamicSettings) : ILicenseUriLicenseResolver { public bool CanResolve(Uri uri) => uri.Host == "github.com"; @@ -11,6 +15,8 @@ internal class GithubRawLicenseResolver(IHttpClientFactory httpClientFactory) : var httpClient = httpClientFactory.CreateClient(); // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); + if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); var httpResponseMessage = await httpClient.GetAsync(uriBuilder.Uri); if (!httpResponseMessage.IsSuccessStatusCode || httpResponseMessage.Content.Headers.ContentType?.MediaType != "text/plain") diff --git a/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs b/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs index a8452de..06fff25 100644 --- a/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs @@ -1,12 +1,14 @@ -using System; +using DotnetThirdPartyNotices.Models; +using System; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace DotnetThirdPartyNotices.Services; -internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFactory) : IProjectUriLicenseResolver, IRepositoryUriLicenseResolver +internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFactory, DynamicSettings dynamicSettings) : IProjectUriLicenseResolver, IRepositoryUriLicenseResolver { public bool CanResolve(Uri uri) => uri.Host == "github.com"; @@ -22,6 +24,8 @@ internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFact }; // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); + if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); var response = await httpClient.GetAsync(uriBuilder.Uri); if (!response.IsSuccessStatusCode) return null; diff --git a/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs b/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs index 79683ba..af96c9b 100644 --- a/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs @@ -1,11 +1,13 @@ -using System; +using DotnetThirdPartyNotices.Models; +using System; using System.Net.Http; +using System.Net.Http.Headers; using System.Text.Json; using System.Threading.Tasks; namespace DotnetThirdPartyNotices.Services; -internal class OpenSourceOrgLicenseResolver : ILicenseUriLicenseResolver +internal class OpenSourceOrgLicenseResolver(DynamicSettings dynamicSettings) : ILicenseUriLicenseResolver { public bool CanResolve(Uri licenseUri) => licenseUri.Host == "opensource.org"; @@ -20,6 +22,8 @@ internal class OpenSourceOrgLicenseResolver : ILicenseUriLicenseResolver HttpClient httpClient = new() { BaseAddress = new Uri("https://api.github.com") }; // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); + if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); var httpResponseMessage = await httpClient.GetAsync($"licenses/{licenseId}"); if (!httpResponseMessage.IsSuccessStatusCode) return null; From 2a082d10e9a5fd8ede6521f05d6d0dc0579c3ff6 Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Fri, 15 Mar 2024 21:57:23 +0100 Subject: [PATCH 3/6] add option to change configuration name --- DotnetThirdPartyNotices/Commands/ScanCommand.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/DotnetThirdPartyNotices/Commands/ScanCommand.cs b/DotnetThirdPartyNotices/Commands/ScanCommand.cs index 08a6f53..0fec57a 100644 --- a/DotnetThirdPartyNotices/Commands/ScanCommand.cs +++ b/DotnetThirdPartyNotices/Commands/ScanCommand.cs @@ -22,9 +22,10 @@ internal class ScanCommand : Command { AddArgument(new Argument("scan-dir", "Path of the directory to look for projects (optional)") { Arity = ArgumentArity.ZeroOrOne }); AddOption(new Option("--output-filename", () => "third-party-notices.txt", "Output filename")); - AddOption(new Option("--copy-to-outdir", () => false, "Copy output file to output directory in Release configuration")); + AddOption(new Option("--copy-to-outdir", () => false, "Copy output file to output directory in selected configuration")); AddOption(new Option("--filter", () => string.Empty, "Filter project files")); AddOption(new Option("--github-token", () => string.Empty, "GitHub's token")); + AddOption(new Option("--configuration", () => "Release", "Project configuration to use")); } internal new class Handler(ILogger logger, IProjectService projectService, ILicenseService licenseService, DynamicSettings dynamicSettings) : ICommandHandler @@ -34,6 +35,7 @@ internal class ScanCommand : Command public bool CopyToOutDir { get; set; } public string? Filter { get; set; } public string? GithubToken { get; set; } + public string? Configuration { get; set; } private readonly Dictionary> _licenseContents = []; private readonly List _unresolvedFiles = []; @@ -74,9 +76,9 @@ private async Task ScanProjectAsync(string projectFilePath) { var stopWatch = new Stopwatch(); stopWatch.Start(); - logger.LogInformation("Resolving files for {ProjectName}...", Path.GetFileName(projectFilePath)); + logger.LogInformation("Resolving files for {ProjectName} using {configuration} configuration...", Path.GetFileName(projectFilePath), Configuration ?? "Release"); var project = new Project(projectFilePath); - project.SetProperty("Configuration", "Release"); + project.SetProperty("Configuration", Configuration ?? "Release"); project.SetProperty("DesignTimeBuild", "true"); var resolvedFiles = projectService.ResolveFiles(project).ToList(); logger.LogInformation("Resolved files count: {ResolvedFilesCount}", resolvedFiles.Count); From 2ea7bd06ae9d7d77aba9260088c298c6b5976424 Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Fri, 15 Mar 2024 22:06:39 +0100 Subject: [PATCH 4/6] updated dependencies and readme --- DotnetThirdPartyNotices/Directory.Packages.props | 2 +- README.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DotnetThirdPartyNotices/Directory.Packages.props b/DotnetThirdPartyNotices/Directory.Packages.props index b7a153e..e50dc7c 100644 --- a/DotnetThirdPartyNotices/Directory.Packages.props +++ b/DotnetThirdPartyNotices/Directory.Packages.props @@ -6,7 +6,7 @@ - + diff --git a/README.md b/README.md index 96b8868..7cb2d53 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ Options: - `--output-filename` allow to change output filename - `--copy-to-outdir` allow to copy output file to output directory in Release configuration - `--filter` allow to use regex to filter project files +- `--configuration` allow to change configuration name (tool uses Release by default) +- `--github-token` allow to use GitHub's token Note that if you use the solution folder and don't use `--copy-to-outdir` then licenses from all projects will be merged to single file. From 6f83a0334144dec04abd530a0588924fc1f42cfd Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Sun, 17 Mar 2024 22:10:57 +0100 Subject: [PATCH 5/6] support cancellation token, pass resolver options and cancellation token to all resolvers --- .../Commands/ScanCommand.cs | 24 ++++-- ...{DynamicSettings.cs => ResolverOptions.cs} | 2 +- DotnetThirdPartyNotices/Program.cs | 1 - .../Services/GithubRawLicenseResolver.cs | 14 ++-- .../GithubRepositoryLicenseResolver.cs | 14 ++-- .../IFileVersionInfoLicenseResolver.cs | 5 +- .../Services/ILicenseService.cs | 2 +- .../Services/IUriLicenseResolver.cs | 8 +- .../Services/LicenseService.cs | 78 ++++++++++--------- .../Services/NetFrameworkLicenseResolver.cs | 11 +-- .../Services/OpenSourceOrgLicenseResolver.cs | 14 ++-- 11 files changed, 96 insertions(+), 77 deletions(-) rename DotnetThirdPartyNotices/Models/{DynamicSettings.cs => ResolverOptions.cs} (87%) diff --git a/DotnetThirdPartyNotices/Commands/ScanCommand.cs b/DotnetThirdPartyNotices/Commands/ScanCommand.cs index 0fec57a..369da83 100644 --- a/DotnetThirdPartyNotices/Commands/ScanCommand.cs +++ b/DotnetThirdPartyNotices/Commands/ScanCommand.cs @@ -28,7 +28,7 @@ internal class ScanCommand : Command AddOption(new Option("--configuration", () => "Release", "Project configuration to use")); } - internal new class Handler(ILogger logger, IProjectService projectService, ILicenseService licenseService, DynamicSettings dynamicSettings) : ICommandHandler + internal new class Handler(ILogger logger, IProjectService projectService, ILicenseService licenseService) : ICommandHandler { public string? ScanDir { get; set; } public string? OutputFilename { get; set; } @@ -47,9 +47,9 @@ public int Invoke(InvocationContext context) public async Task InvokeAsync(InvocationContext context) { + var cancellationToken = context.GetCancellationToken(); MSBuildLocator.RegisterDefaults(); ScanDir ??= Directory.GetCurrentDirectory(); - dynamicSettings.GitHubToken = GithubToken; var projectFilePaths = projectService.GetProjectFilePaths(ScanDir); projectFilePaths = GetFilteredProjectPathes(projectFilePaths); if (projectFilePaths.Length == 0) @@ -58,9 +58,9 @@ public async Task InvokeAsync(InvocationContext context) return 0; } foreach (var projectFilePath in projectFilePaths) - await ScanProjectAsync(projectFilePath); + await ScanProjectAsync(projectFilePath, cancellationToken); if (!CopyToOutDir) - await GenerateOutputFileAsync(OutputFilename); + await GenerateOutputFileAsync(OutputFilename, cancellationToken); return 0; } @@ -72,8 +72,9 @@ private string[] GetFilteredProjectPathes(string[] projectPathes) return projectPathes.Where(x => filterRegex.IsMatch(x)).ToArray(); } - private async Task ScanProjectAsync(string projectFilePath) + private async Task ScanProjectAsync(string projectFilePath, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); var stopWatch = new Stopwatch(); stopWatch.Start(); logger.LogInformation("Resolving files for {ProjectName} using {configuration} configuration...", Path.GetFileName(projectFilePath), Configuration ?? "Release"); @@ -84,6 +85,7 @@ private async Task ScanProjectAsync(string projectFilePath) logger.LogInformation("Resolved files count: {ResolvedFilesCount}", resolvedFiles.Count); foreach (var resolvedFileInfo in resolvedFiles) { + cancellationToken.ThrowIfCancellationRequested(); logger.LogInformation("Resolving license for {RelativeOutputPath}", resolvedFileInfo.RelativeOutputPath); if (resolvedFileInfo.NuSpec != null) { @@ -93,7 +95,11 @@ private async Task ScanProjectAsync(string projectFilePath) { logger.LogWarning("Package not found"); } - var licenseContent = await licenseService.ResolveFromResolvedFileInfo(resolvedFileInfo); + var resolverOptions = new ResolverOptions + { + GitHubToken = GithubToken + }; + var licenseContent = await licenseService.ResolveFromResolvedFileInfoAsync(resolvedFileInfo, resolverOptions, cancellationToken); if (licenseContent == null) { _unresolvedFiles.Add(resolvedFileInfo); @@ -107,11 +113,12 @@ private async Task ScanProjectAsync(string projectFilePath) stopWatch.Stop(); logger.LogInformation("Project {ProjectName} resolved in {StopwatchElapsedMilliseconds}ms", Path.GetFileName(projectFilePath), stopWatch.ElapsedMilliseconds); if (CopyToOutDir && !string.IsNullOrEmpty(ScanDir) && !string.IsNullOrEmpty(OutputFilename)) - await GenerateOutputFileAsync(Path.Combine(ScanDir, project.GetPropertyValue("OutDir"), Path.GetFileName(OutputFilename))); + await GenerateOutputFileAsync(Path.Combine(ScanDir, project.GetPropertyValue("OutDir"), Path.GetFileName(OutputFilename)), cancellationToken); } - private async Task GenerateOutputFileAsync(string? outputFilePath) + private async Task GenerateOutputFileAsync(string? outputFilePath, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (outputFilePath == null) return; var uniqueResolvedFilesCount = _licenseContents.Values.Sum(v => v.GroupBy(x => x.SourcePath).Count()); @@ -139,6 +146,7 @@ private async Task GenerateOutputFileAsync(string? outputFilePath) logger.LogInformation("Generate licenses in {StopwatchElapsedMilliseconds}ms", stopWatch.ElapsedMilliseconds); if (stringBuilder.Length == 0) return; + cancellationToken.ThrowIfCancellationRequested(); logger.LogInformation("Writing to {OutputFilename}...", outputFilePath); await System.IO.File.WriteAllTextAsync(outputFilePath, stringBuilder.ToString()); _licenseContents.Clear(); diff --git a/DotnetThirdPartyNotices/Models/DynamicSettings.cs b/DotnetThirdPartyNotices/Models/ResolverOptions.cs similarity index 87% rename from DotnetThirdPartyNotices/Models/DynamicSettings.cs rename to DotnetThirdPartyNotices/Models/ResolverOptions.cs index 2ac18ff..6238810 100644 --- a/DotnetThirdPartyNotices/Models/DynamicSettings.cs +++ b/DotnetThirdPartyNotices/Models/ResolverOptions.cs @@ -6,7 +6,7 @@ namespace DotnetThirdPartyNotices.Models; -internal class DynamicSettings +internal class ResolverOptions { public string? GitHubToken { get; set; } } diff --git a/DotnetThirdPartyNotices/Program.cs b/DotnetThirdPartyNotices/Program.cs index 279691f..1caf525 100644 --- a/DotnetThirdPartyNotices/Program.cs +++ b/DotnetThirdPartyNotices/Program.cs @@ -35,7 +35,6 @@ x.AddSingleton(); x.AddSingleton(); x.AddSingleton(); - x.AddSingleton(); x.AddHttpClient(); var assembly = Assembly.GetExecutingAssembly(); var types = assembly.GetTypes(); diff --git a/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs b/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs index 4f9fbdf..0f89d3f 100644 --- a/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs @@ -4,23 +4,23 @@ namespace DotnetThirdPartyNotices.Services; -internal class GithubRawLicenseResolver(IHttpClientFactory httpClientFactory, DynamicSettings dynamicSettings) : ILicenseUriLicenseResolver +internal class GithubRawLicenseResolver(IHttpClientFactory httpClientFactory) : ILicenseUriLicenseResolver { - public bool CanResolve(Uri uri) => uri.Host == "github.com"; + public Task CanResolveAsync(Uri uri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(uri.Host == "github.com"); - public async Task Resolve(Uri uri) + public async Task ResolveAsync(Uri uri, ResolverOptions resolverOptions, CancellationToken cancellationToken) { var uriBuilder = new UriBuilder(uri) { Host = "raw.githubusercontent.com" }; uriBuilder.Path = uriBuilder.Path.Replace("/blob", string.Empty); var httpClient = httpClientFactory.CreateClient(); // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); - if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); - var httpResponseMessage = await httpClient.GetAsync(uriBuilder.Uri); + if (!string.IsNullOrEmpty(resolverOptions.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolverOptions.GitHubToken); + var httpResponseMessage = await httpClient.GetAsync(uriBuilder.Uri, cancellationToken); if (!httpResponseMessage.IsSuccessStatusCode || httpResponseMessage.Content.Headers.ContentType?.MediaType != "text/plain") return null; - return await httpResponseMessage.Content.ReadAsStringAsync(); + return await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); } } diff --git a/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs b/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs index 06fff25..cd33e23 100644 --- a/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs @@ -8,11 +8,11 @@ namespace DotnetThirdPartyNotices.Services; -internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFactory, DynamicSettings dynamicSettings) : IProjectUriLicenseResolver, IRepositoryUriLicenseResolver +internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFactory) : IProjectUriLicenseResolver, IRepositoryUriLicenseResolver { - public bool CanResolve(Uri uri) => uri.Host == "github.com"; + public Task CanResolveAsync(Uri uri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(uri.Host == "github.com"); - public async Task Resolve(Uri licenseUri) + public async Task ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) { var repositoryPath = licenseUri.AbsolutePath.TrimEnd('/'); if (repositoryPath.EndsWith(".git")) @@ -24,12 +24,12 @@ internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFact }; // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); - if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); - var response = await httpClient.GetAsync(uriBuilder.Uri); + if (!string.IsNullOrEmpty(resolverOptions.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolverOptions.GitHubToken); + var response = await httpClient.GetAsync(uriBuilder.Uri, cancellationToken); if (!response.IsSuccessStatusCode) return null; - var json = await response.Content.ReadAsStringAsync(); + var json = await response.Content.ReadAsStringAsync(cancellationToken); var jsonDocument = JsonDocument.Parse(json); var rootElement = jsonDocument.RootElement; var encoding = rootElement.GetProperty("encoding").GetString(); diff --git a/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs b/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs index 01ae395..e1dc371 100644 --- a/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs @@ -1,3 +1,4 @@ +using DotnetThirdPartyNotices.Models; using System.Diagnostics; using System.Threading.Tasks; @@ -5,6 +6,6 @@ namespace DotnetThirdPartyNotices.Services; internal interface IFileVersionInfoLicenseResolver : ILicenseResolver { - bool CanResolve(FileVersionInfo fileVersionInfo); - Task Resolve(FileVersionInfo fileVersionInfo); + Task CanResolveAsync(FileVersionInfo fileVersionInfo, CancellationToken cancellationToken); + Task ResolveAsync(FileVersionInfo fileVersionInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/DotnetThirdPartyNotices/Services/ILicenseService.cs b/DotnetThirdPartyNotices/Services/ILicenseService.cs index db30bf8..82e7a73 100644 --- a/DotnetThirdPartyNotices/Services/ILicenseService.cs +++ b/DotnetThirdPartyNotices/Services/ILicenseService.cs @@ -4,5 +4,5 @@ namespace DotnetThirdPartyNotices.Services; internal interface ILicenseService { - Task ResolveFromResolvedFileInfo(ResolvedFileInfo resolvedFileInfo); + Task ResolveFromResolvedFileInfoAsync(ResolvedFileInfo resolvedFileInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs b/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs index f816542..d66efda 100644 --- a/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs @@ -1,7 +1,9 @@ -namespace DotnetThirdPartyNotices.Services; +using DotnetThirdPartyNotices.Models; + +namespace DotnetThirdPartyNotices.Services; internal interface IUriLicenseResolver { - bool CanResolve(Uri licenseUri); - Task Resolve(Uri licenseUri); + Task CanResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken); + Task ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken); } diff --git a/DotnetThirdPartyNotices/Services/LicenseService.cs b/DotnetThirdPartyNotices/Services/LicenseService.cs index c534dc4..2cfb683 100644 --- a/DotnetThirdPartyNotices/Services/LicenseService.cs +++ b/DotnetThirdPartyNotices/Services/LicenseService.cs @@ -8,21 +8,21 @@ internal partial class LicenseService(ILogger logger, IEnumerabl { private static readonly Dictionary LicenseCache = []; - public async Task ResolveFromResolvedFileInfo(ResolvedFileInfo resolvedFileInfo) + public async Task ResolveFromResolvedFileInfoAsync(ResolvedFileInfo resolvedFileInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(resolvedFileInfo); if (resolvedFileInfo.NuSpec != null && LicenseCache.TryGetValue(resolvedFileInfo.NuSpec.Id, out string? value)) return value; - return (await ResolveFromLicenseRelativePathAsync(resolvedFileInfo)) - ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, false)) - ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, false)) - ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, false)) - ?? (await ResolveFromPackagePathAsync(resolvedFileInfo)) - ?? (await ResolveFromSourcePathAsync(resolvedFileInfo)) - ?? (await ResolveFromFileVersionInfoAsync(resolvedFileInfo)) - ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, true)) - ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, true)) - ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, true)); + return (await ResolveFromLicenseRelativePathAsync(resolvedFileInfo, cancellationToken)) + ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, false, resolverOptions, cancellationToken)) + ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, false, resolverOptions, cancellationToken)) + ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, false, resolverOptions, cancellationToken)) + ?? (await ResolveFromPackagePathAsync(resolvedFileInfo, cancellationToken)) + ?? (await ResolveFromSourcePathAsync(resolvedFileInfo, cancellationToken)) + ?? (await ResolveFromFileVersionInfoAsync(resolvedFileInfo, resolverOptions, cancellationToken)) + ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, true, resolverOptions, cancellationToken)) + ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, true, resolverOptions, cancellationToken)) + ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, true, resolverOptions, cancellationToken)); } private static string? UnifyLicense(string? license) @@ -50,8 +50,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl return license; } - private async Task ResolveFromPackagePathAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromPackagePathAsync(ResolvedFileInfo resolvedFileInfo, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(resolvedFileInfo.PackagePath)) return null; if (LicenseCache.TryGetValue(resolvedFileInfo.PackagePath, out string? value)) @@ -73,8 +74,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl return license; } - private async Task ResolveFromSourcePathAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromSourcePathAsync(ResolvedFileInfo resolvedFileInfo, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(resolvedFileInfo.SourcePath)) return null; if (LicenseCache.TryGetValue(resolvedFileInfo.SourcePath, out string? value)) @@ -107,7 +109,7 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromFileVersionInfoAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromFileVersionInfoAsync(ResolvedFileInfo resolvedFileInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken) { if(resolvedFileInfo.VersionInfo == null) return null; @@ -117,9 +119,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl { foreach (var resolver in fileVersionInfoLicenseResolvers) { - if (!resolver.CanResolve(resolvedFileInfo.VersionInfo)) + if (!await resolver.CanResolveAsync(resolvedFileInfo.VersionInfo, cancellationToken)) continue; - var license = await resolver.Resolve(resolvedFileInfo.VersionInfo); + var license = await resolver.ResolveAsync(resolvedFileInfo.VersionInfo, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license != null) { @@ -136,8 +138,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromLicenseRelativePathAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromLicenseRelativePathAsync(ResolvedFileInfo resolvedFileInfo, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(resolvedFileInfo.PackagePath) || string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.LicenseRelativePath)) return null; var licenseFullPath = Path.Combine(resolvedFileInfo.PackagePath, resolvedFileInfo.NuSpec.LicenseRelativePath); @@ -163,7 +166,7 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromProjectUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl) + private async Task ResolveFromProjectUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl, ResolverOptions resolverOptions, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.ProjectUrl)) return null; @@ -174,8 +177,8 @@ internal partial class LicenseService(ILogger logger, IEnumerabl try { var license = useFinalUrl - ? await ResolveFromFinalUrlAsync(uri, projectUriLicenseResolvers) - : await ResolveFromUrlAsync(uri, projectUriLicenseResolvers); + ? await ResolveFromFinalUrlAsync(uri, projectUriLicenseResolvers, resolverOptions, cancellationToken) + : await ResolveFromUrlAsync(uri, projectUriLicenseResolvers, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license == null) return null; @@ -190,30 +193,32 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromUrlAsync(Uri uri, IEnumerable urlLicenseResolvers) + private async Task ResolveFromUrlAsync(Uri uri, IEnumerable urlLicenseResolvers, ResolverOptions resolverOptions, CancellationToken cancellationToken) { foreach (var resolver in urlLicenseResolvers) { - if (!resolver.CanResolve(uri)) + if (!await resolver.CanResolveAsync(uri, resolverOptions, cancellationToken)) continue; - var license = await resolver.Resolve(uri); + var license = await resolver.ResolveAsync(uri, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license != null) return license; + cancellationToken.ThrowIfCancellationRequested(); } return null; } - private async Task ResolveFromFinalUrlAsync(Uri uri, IEnumerable urlLicenseResolvers) + private async Task ResolveFromFinalUrlAsync(Uri uri, IEnumerable urlLicenseResolvers, ResolverOptions resolverOptions, CancellationToken cancellationToken) { using var httpClient = httpClientFactory.CreateClient(); - var httpResponseMessage = await httpClient.GetAsync(uri); + var httpResponseMessage = await httpClient.GetAsync(uri, cancellationToken); if (!httpResponseMessage.IsSuccessStatusCode && uri.AbsolutePath.EndsWith(".txt")) { + cancellationToken.ThrowIfCancellationRequested(); // try without .txt extension var fixedUri = new UriBuilder(uri); fixedUri.Path = fixedUri.Path.Remove(fixedUri.Path.Length - 4); - httpResponseMessage = await httpClient.GetAsync(fixedUri.Uri); + httpResponseMessage = await httpClient.GetAsync(fixedUri.Uri, cancellationToken); if (!httpResponseMessage.IsSuccessStatusCode) return null; } @@ -221,22 +226,24 @@ internal partial class LicenseService(ILogger logger, IEnumerabl { foreach (var resolver in urlLicenseResolvers) { - if (!resolver.CanResolve(httpResponseMessage.RequestMessage.RequestUri)) + if (!await resolver.CanResolveAsync(httpResponseMessage.RequestMessage.RequestUri, resolverOptions, cancellationToken)) continue; - var license = await resolver.Resolve(httpResponseMessage.RequestMessage.RequestUri); + var license = await resolver.ResolveAsync(httpResponseMessage.RequestMessage.RequestUri, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license != null) return license; + cancellationToken.ThrowIfCancellationRequested(); } } // Finally, if no license uri can be found despite all the redirects, try to blindly get it if (httpResponseMessage.Content.Headers.ContentType?.MediaType != "text/plain") return null; - var license2 = await httpResponseMessage.Content.ReadAsStringAsync(); + var license2 = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); return UnifyLicense(license2); } - private async Task ResolveFromRepositoryUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl) + private async Task ResolveFromRepositoryUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl, ResolverOptions resolverOptions, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.RepositoryUrl)) return null; @@ -247,8 +254,8 @@ internal partial class LicenseService(ILogger logger, IEnumerabl try { var license = useFinalUrl - ? await ResolveFromFinalUrlAsync(uri, repositoryUriLicenseResolvers) - : await ResolveFromUrlAsync(uri, repositoryUriLicenseResolvers); + ? await ResolveFromFinalUrlAsync(uri, repositoryUriLicenseResolvers, resolverOptions, cancellationToken) + : await ResolveFromUrlAsync(uri, repositoryUriLicenseResolvers, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license == null) return null; @@ -263,8 +270,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromLicenseUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl) + private async Task ResolveFromLicenseUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl, ResolverOptions resolverOptions, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.LicenseUrl)) return null; if (LicenseCache.TryGetValue(resolvedFileInfo.NuSpec.LicenseUrl, out string? value)) @@ -274,8 +282,8 @@ internal partial class LicenseService(ILogger logger, IEnumerabl try { var license = useFinalUrl - ? await ResolveFromFinalUrlAsync(uri, licenseUriLicenseResolvers) - : await ResolveFromUrlAsync(uri, licenseUriLicenseResolvers); + ? await ResolveFromFinalUrlAsync(uri, licenseUriLicenseResolvers, resolverOptions, cancellationToken) + : await ResolveFromUrlAsync(uri, licenseUriLicenseResolvers, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license == null) return null; diff --git a/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs b/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs index f5ad7d3..2ea1f55 100644 --- a/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs @@ -1,4 +1,5 @@ -using System; +using DotnetThirdPartyNotices.Models; +using System; using System.Diagnostics; using System.IO; using System.Reflection; @@ -10,11 +11,11 @@ internal class NetFrameworkLicenseResolver : ILicenseUriLicenseResolver, IFileVe { private static string? _licenseContent; - public bool CanResolve(Uri licenseUri) => licenseUri.ToString().Contains("LinkId=529443"); - public bool CanResolve(FileVersionInfo fileVersionInfo) => fileVersionInfo.ProductName == "Microsoft® .NET Framework"; + public Task CanResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(licenseUri.ToString().Contains("LinkId=529443")); + public Task CanResolveAsync(FileVersionInfo fileVersionInfo, CancellationToken cancellationToken) => Task.FromResult(fileVersionInfo.ProductName == "Microsoft® .NET Framework"); - public Task Resolve(Uri licenseUri) => GetLicenseContent(); - public Task Resolve(FileVersionInfo fileVersionInfo) => GetLicenseContent(); + public Task ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => GetLicenseContent(); + public Task ResolveAsync(FileVersionInfo fileVersionInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken) => GetLicenseContent(); public async Task GetLicenseContent() { diff --git a/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs b/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs index af96c9b..b7e4134 100644 --- a/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs @@ -7,13 +7,13 @@ namespace DotnetThirdPartyNotices.Services; -internal class OpenSourceOrgLicenseResolver(DynamicSettings dynamicSettings) : ILicenseUriLicenseResolver +internal class OpenSourceOrgLicenseResolver : ILicenseUriLicenseResolver { - public bool CanResolve(Uri licenseUri) => licenseUri.Host == "opensource.org"; + public Task CanResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(licenseUri.Host == "opensource.org"); internal static readonly char[] separator = ['/']; - public async Task Resolve(Uri licenseUri) + public async Task ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) { var s = licenseUri.AbsolutePath.Split(separator, StringSplitOptions.RemoveEmptyEntries); if (s[0] != "licenses") @@ -22,12 +22,12 @@ internal class OpenSourceOrgLicenseResolver(DynamicSettings dynamicSettings) : I HttpClient httpClient = new() { BaseAddress = new Uri("https://api.github.com") }; // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); - if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); - var httpResponseMessage = await httpClient.GetAsync($"licenses/{licenseId}"); + if (!string.IsNullOrEmpty(resolverOptions.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolverOptions.GitHubToken); + var httpResponseMessage = await httpClient.GetAsync($"licenses/{licenseId}", cancellationToken); if (!httpResponseMessage.IsSuccessStatusCode) return null; - var content = await httpResponseMessage.Content.ReadAsStringAsync(); + var content = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); var jsonDocument = JsonDocument.Parse(content); return jsonDocument.RootElement.GetProperty("body").GetString(); } From 4a54342982c98d517a906b96f728ade2411be885 Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Sun, 17 Mar 2024 22:10:57 +0100 Subject: [PATCH 6/6] support cancellation token, pass resolver options and cancellation token to all resolvers --- .../Commands/ScanCommand.cs | 24 ++++-- .../DotnetThirdPartyNotices.csproj | 2 +- ...{DynamicSettings.cs => ResolverOptions.cs} | 2 +- DotnetThirdPartyNotices/Program.cs | 1 - .../Services/GithubRawLicenseResolver.cs | 14 ++-- .../GithubRepositoryLicenseResolver.cs | 14 ++-- .../IFileVersionInfoLicenseResolver.cs | 5 +- .../Services/ILicenseService.cs | 2 +- .../Services/IUriLicenseResolver.cs | 8 +- .../Services/LicenseService.cs | 78 ++++++++++--------- .../Services/NetFrameworkLicenseResolver.cs | 11 +-- .../Services/OpenSourceOrgLicenseResolver.cs | 14 ++-- 12 files changed, 97 insertions(+), 78 deletions(-) rename DotnetThirdPartyNotices/Models/{DynamicSettings.cs => ResolverOptions.cs} (87%) diff --git a/DotnetThirdPartyNotices/Commands/ScanCommand.cs b/DotnetThirdPartyNotices/Commands/ScanCommand.cs index 0fec57a..369da83 100644 --- a/DotnetThirdPartyNotices/Commands/ScanCommand.cs +++ b/DotnetThirdPartyNotices/Commands/ScanCommand.cs @@ -28,7 +28,7 @@ internal class ScanCommand : Command AddOption(new Option("--configuration", () => "Release", "Project configuration to use")); } - internal new class Handler(ILogger logger, IProjectService projectService, ILicenseService licenseService, DynamicSettings dynamicSettings) : ICommandHandler + internal new class Handler(ILogger logger, IProjectService projectService, ILicenseService licenseService) : ICommandHandler { public string? ScanDir { get; set; } public string? OutputFilename { get; set; } @@ -47,9 +47,9 @@ public int Invoke(InvocationContext context) public async Task InvokeAsync(InvocationContext context) { + var cancellationToken = context.GetCancellationToken(); MSBuildLocator.RegisterDefaults(); ScanDir ??= Directory.GetCurrentDirectory(); - dynamicSettings.GitHubToken = GithubToken; var projectFilePaths = projectService.GetProjectFilePaths(ScanDir); projectFilePaths = GetFilteredProjectPathes(projectFilePaths); if (projectFilePaths.Length == 0) @@ -58,9 +58,9 @@ public async Task InvokeAsync(InvocationContext context) return 0; } foreach (var projectFilePath in projectFilePaths) - await ScanProjectAsync(projectFilePath); + await ScanProjectAsync(projectFilePath, cancellationToken); if (!CopyToOutDir) - await GenerateOutputFileAsync(OutputFilename); + await GenerateOutputFileAsync(OutputFilename, cancellationToken); return 0; } @@ -72,8 +72,9 @@ private string[] GetFilteredProjectPathes(string[] projectPathes) return projectPathes.Where(x => filterRegex.IsMatch(x)).ToArray(); } - private async Task ScanProjectAsync(string projectFilePath) + private async Task ScanProjectAsync(string projectFilePath, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); var stopWatch = new Stopwatch(); stopWatch.Start(); logger.LogInformation("Resolving files for {ProjectName} using {configuration} configuration...", Path.GetFileName(projectFilePath), Configuration ?? "Release"); @@ -84,6 +85,7 @@ private async Task ScanProjectAsync(string projectFilePath) logger.LogInformation("Resolved files count: {ResolvedFilesCount}", resolvedFiles.Count); foreach (var resolvedFileInfo in resolvedFiles) { + cancellationToken.ThrowIfCancellationRequested(); logger.LogInformation("Resolving license for {RelativeOutputPath}", resolvedFileInfo.RelativeOutputPath); if (resolvedFileInfo.NuSpec != null) { @@ -93,7 +95,11 @@ private async Task ScanProjectAsync(string projectFilePath) { logger.LogWarning("Package not found"); } - var licenseContent = await licenseService.ResolveFromResolvedFileInfo(resolvedFileInfo); + var resolverOptions = new ResolverOptions + { + GitHubToken = GithubToken + }; + var licenseContent = await licenseService.ResolveFromResolvedFileInfoAsync(resolvedFileInfo, resolverOptions, cancellationToken); if (licenseContent == null) { _unresolvedFiles.Add(resolvedFileInfo); @@ -107,11 +113,12 @@ private async Task ScanProjectAsync(string projectFilePath) stopWatch.Stop(); logger.LogInformation("Project {ProjectName} resolved in {StopwatchElapsedMilliseconds}ms", Path.GetFileName(projectFilePath), stopWatch.ElapsedMilliseconds); if (CopyToOutDir && !string.IsNullOrEmpty(ScanDir) && !string.IsNullOrEmpty(OutputFilename)) - await GenerateOutputFileAsync(Path.Combine(ScanDir, project.GetPropertyValue("OutDir"), Path.GetFileName(OutputFilename))); + await GenerateOutputFileAsync(Path.Combine(ScanDir, project.GetPropertyValue("OutDir"), Path.GetFileName(OutputFilename)), cancellationToken); } - private async Task GenerateOutputFileAsync(string? outputFilePath) + private async Task GenerateOutputFileAsync(string? outputFilePath, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (outputFilePath == null) return; var uniqueResolvedFilesCount = _licenseContents.Values.Sum(v => v.GroupBy(x => x.SourcePath).Count()); @@ -139,6 +146,7 @@ private async Task GenerateOutputFileAsync(string? outputFilePath) logger.LogInformation("Generate licenses in {StopwatchElapsedMilliseconds}ms", stopWatch.ElapsedMilliseconds); if (stringBuilder.Length == 0) return; + cancellationToken.ThrowIfCancellationRequested(); logger.LogInformation("Writing to {OutputFilename}...", outputFilePath); await System.IO.File.WriteAllTextAsync(outputFilePath, stringBuilder.ToString()); _licenseContents.Clear(); diff --git a/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj b/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj index 8a4b9a5..c2f12dc 100644 --- a/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj +++ b/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj @@ -10,7 +10,7 @@ dotnet-thirdpartynotices DotnetThirdPartyNotices A .NET tool to generate file with third party legal notices - 0.3.2 + 0.3.3 MIT git https://github.com/bugproof/DotnetThirdPartyNotices diff --git a/DotnetThirdPartyNotices/Models/DynamicSettings.cs b/DotnetThirdPartyNotices/Models/ResolverOptions.cs similarity index 87% rename from DotnetThirdPartyNotices/Models/DynamicSettings.cs rename to DotnetThirdPartyNotices/Models/ResolverOptions.cs index 2ac18ff..6238810 100644 --- a/DotnetThirdPartyNotices/Models/DynamicSettings.cs +++ b/DotnetThirdPartyNotices/Models/ResolverOptions.cs @@ -6,7 +6,7 @@ namespace DotnetThirdPartyNotices.Models; -internal class DynamicSettings +internal class ResolverOptions { public string? GitHubToken { get; set; } } diff --git a/DotnetThirdPartyNotices/Program.cs b/DotnetThirdPartyNotices/Program.cs index 279691f..1caf525 100644 --- a/DotnetThirdPartyNotices/Program.cs +++ b/DotnetThirdPartyNotices/Program.cs @@ -35,7 +35,6 @@ x.AddSingleton(); x.AddSingleton(); x.AddSingleton(); - x.AddSingleton(); x.AddHttpClient(); var assembly = Assembly.GetExecutingAssembly(); var types = assembly.GetTypes(); diff --git a/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs b/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs index 4f9fbdf..0f89d3f 100644 --- a/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/GithubRawLicenseResolver.cs @@ -4,23 +4,23 @@ namespace DotnetThirdPartyNotices.Services; -internal class GithubRawLicenseResolver(IHttpClientFactory httpClientFactory, DynamicSettings dynamicSettings) : ILicenseUriLicenseResolver +internal class GithubRawLicenseResolver(IHttpClientFactory httpClientFactory) : ILicenseUriLicenseResolver { - public bool CanResolve(Uri uri) => uri.Host == "github.com"; + public Task CanResolveAsync(Uri uri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(uri.Host == "github.com"); - public async Task Resolve(Uri uri) + public async Task ResolveAsync(Uri uri, ResolverOptions resolverOptions, CancellationToken cancellationToken) { var uriBuilder = new UriBuilder(uri) { Host = "raw.githubusercontent.com" }; uriBuilder.Path = uriBuilder.Path.Replace("/blob", string.Empty); var httpClient = httpClientFactory.CreateClient(); // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); - if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); - var httpResponseMessage = await httpClient.GetAsync(uriBuilder.Uri); + if (!string.IsNullOrEmpty(resolverOptions.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolverOptions.GitHubToken); + var httpResponseMessage = await httpClient.GetAsync(uriBuilder.Uri, cancellationToken); if (!httpResponseMessage.IsSuccessStatusCode || httpResponseMessage.Content.Headers.ContentType?.MediaType != "text/plain") return null; - return await httpResponseMessage.Content.ReadAsStringAsync(); + return await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); } } diff --git a/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs b/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs index 06fff25..cd33e23 100644 --- a/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/GithubRepositoryLicenseResolver.cs @@ -8,11 +8,11 @@ namespace DotnetThirdPartyNotices.Services; -internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFactory, DynamicSettings dynamicSettings) : IProjectUriLicenseResolver, IRepositoryUriLicenseResolver +internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFactory) : IProjectUriLicenseResolver, IRepositoryUriLicenseResolver { - public bool CanResolve(Uri uri) => uri.Host == "github.com"; + public Task CanResolveAsync(Uri uri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(uri.Host == "github.com"); - public async Task Resolve(Uri licenseUri) + public async Task ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) { var repositoryPath = licenseUri.AbsolutePath.TrimEnd('/'); if (repositoryPath.EndsWith(".git")) @@ -24,12 +24,12 @@ internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFact }; // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); - if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); - var response = await httpClient.GetAsync(uriBuilder.Uri); + if (!string.IsNullOrEmpty(resolverOptions.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolverOptions.GitHubToken); + var response = await httpClient.GetAsync(uriBuilder.Uri, cancellationToken); if (!response.IsSuccessStatusCode) return null; - var json = await response.Content.ReadAsStringAsync(); + var json = await response.Content.ReadAsStringAsync(cancellationToken); var jsonDocument = JsonDocument.Parse(json); var rootElement = jsonDocument.RootElement; var encoding = rootElement.GetProperty("encoding").GetString(); diff --git a/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs b/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs index 01ae395..e1dc371 100644 --- a/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/IFileVersionInfoLicenseResolver.cs @@ -1,3 +1,4 @@ +using DotnetThirdPartyNotices.Models; using System.Diagnostics; using System.Threading.Tasks; @@ -5,6 +6,6 @@ namespace DotnetThirdPartyNotices.Services; internal interface IFileVersionInfoLicenseResolver : ILicenseResolver { - bool CanResolve(FileVersionInfo fileVersionInfo); - Task Resolve(FileVersionInfo fileVersionInfo); + Task CanResolveAsync(FileVersionInfo fileVersionInfo, CancellationToken cancellationToken); + Task ResolveAsync(FileVersionInfo fileVersionInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/DotnetThirdPartyNotices/Services/ILicenseService.cs b/DotnetThirdPartyNotices/Services/ILicenseService.cs index db30bf8..82e7a73 100644 --- a/DotnetThirdPartyNotices/Services/ILicenseService.cs +++ b/DotnetThirdPartyNotices/Services/ILicenseService.cs @@ -4,5 +4,5 @@ namespace DotnetThirdPartyNotices.Services; internal interface ILicenseService { - Task ResolveFromResolvedFileInfo(ResolvedFileInfo resolvedFileInfo); + Task ResolveFromResolvedFileInfoAsync(ResolvedFileInfo resolvedFileInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs b/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs index f816542..d66efda 100644 --- a/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs @@ -1,7 +1,9 @@ -namespace DotnetThirdPartyNotices.Services; +using DotnetThirdPartyNotices.Models; + +namespace DotnetThirdPartyNotices.Services; internal interface IUriLicenseResolver { - bool CanResolve(Uri licenseUri); - Task Resolve(Uri licenseUri); + Task CanResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken); + Task ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken); } diff --git a/DotnetThirdPartyNotices/Services/LicenseService.cs b/DotnetThirdPartyNotices/Services/LicenseService.cs index c534dc4..2cfb683 100644 --- a/DotnetThirdPartyNotices/Services/LicenseService.cs +++ b/DotnetThirdPartyNotices/Services/LicenseService.cs @@ -8,21 +8,21 @@ internal partial class LicenseService(ILogger logger, IEnumerabl { private static readonly Dictionary LicenseCache = []; - public async Task ResolveFromResolvedFileInfo(ResolvedFileInfo resolvedFileInfo) + public async Task ResolveFromResolvedFileInfoAsync(ResolvedFileInfo resolvedFileInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(resolvedFileInfo); if (resolvedFileInfo.NuSpec != null && LicenseCache.TryGetValue(resolvedFileInfo.NuSpec.Id, out string? value)) return value; - return (await ResolveFromLicenseRelativePathAsync(resolvedFileInfo)) - ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, false)) - ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, false)) - ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, false)) - ?? (await ResolveFromPackagePathAsync(resolvedFileInfo)) - ?? (await ResolveFromSourcePathAsync(resolvedFileInfo)) - ?? (await ResolveFromFileVersionInfoAsync(resolvedFileInfo)) - ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, true)) - ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, true)) - ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, true)); + return (await ResolveFromLicenseRelativePathAsync(resolvedFileInfo, cancellationToken)) + ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, false, resolverOptions, cancellationToken)) + ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, false, resolverOptions, cancellationToken)) + ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, false, resolverOptions, cancellationToken)) + ?? (await ResolveFromPackagePathAsync(resolvedFileInfo, cancellationToken)) + ?? (await ResolveFromSourcePathAsync(resolvedFileInfo, cancellationToken)) + ?? (await ResolveFromFileVersionInfoAsync(resolvedFileInfo, resolverOptions, cancellationToken)) + ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, true, resolverOptions, cancellationToken)) + ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, true, resolverOptions, cancellationToken)) + ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, true, resolverOptions, cancellationToken)); } private static string? UnifyLicense(string? license) @@ -50,8 +50,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl return license; } - private async Task ResolveFromPackagePathAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromPackagePathAsync(ResolvedFileInfo resolvedFileInfo, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(resolvedFileInfo.PackagePath)) return null; if (LicenseCache.TryGetValue(resolvedFileInfo.PackagePath, out string? value)) @@ -73,8 +74,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl return license; } - private async Task ResolveFromSourcePathAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromSourcePathAsync(ResolvedFileInfo resolvedFileInfo, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(resolvedFileInfo.SourcePath)) return null; if (LicenseCache.TryGetValue(resolvedFileInfo.SourcePath, out string? value)) @@ -107,7 +109,7 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromFileVersionInfoAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromFileVersionInfoAsync(ResolvedFileInfo resolvedFileInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken) { if(resolvedFileInfo.VersionInfo == null) return null; @@ -117,9 +119,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl { foreach (var resolver in fileVersionInfoLicenseResolvers) { - if (!resolver.CanResolve(resolvedFileInfo.VersionInfo)) + if (!await resolver.CanResolveAsync(resolvedFileInfo.VersionInfo, cancellationToken)) continue; - var license = await resolver.Resolve(resolvedFileInfo.VersionInfo); + var license = await resolver.ResolveAsync(resolvedFileInfo.VersionInfo, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license != null) { @@ -136,8 +138,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromLicenseRelativePathAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromLicenseRelativePathAsync(ResolvedFileInfo resolvedFileInfo, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(resolvedFileInfo.PackagePath) || string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.LicenseRelativePath)) return null; var licenseFullPath = Path.Combine(resolvedFileInfo.PackagePath, resolvedFileInfo.NuSpec.LicenseRelativePath); @@ -163,7 +166,7 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromProjectUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl) + private async Task ResolveFromProjectUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl, ResolverOptions resolverOptions, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.ProjectUrl)) return null; @@ -174,8 +177,8 @@ internal partial class LicenseService(ILogger logger, IEnumerabl try { var license = useFinalUrl - ? await ResolveFromFinalUrlAsync(uri, projectUriLicenseResolvers) - : await ResolveFromUrlAsync(uri, projectUriLicenseResolvers); + ? await ResolveFromFinalUrlAsync(uri, projectUriLicenseResolvers, resolverOptions, cancellationToken) + : await ResolveFromUrlAsync(uri, projectUriLicenseResolvers, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license == null) return null; @@ -190,30 +193,32 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromUrlAsync(Uri uri, IEnumerable urlLicenseResolvers) + private async Task ResolveFromUrlAsync(Uri uri, IEnumerable urlLicenseResolvers, ResolverOptions resolverOptions, CancellationToken cancellationToken) { foreach (var resolver in urlLicenseResolvers) { - if (!resolver.CanResolve(uri)) + if (!await resolver.CanResolveAsync(uri, resolverOptions, cancellationToken)) continue; - var license = await resolver.Resolve(uri); + var license = await resolver.ResolveAsync(uri, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license != null) return license; + cancellationToken.ThrowIfCancellationRequested(); } return null; } - private async Task ResolveFromFinalUrlAsync(Uri uri, IEnumerable urlLicenseResolvers) + private async Task ResolveFromFinalUrlAsync(Uri uri, IEnumerable urlLicenseResolvers, ResolverOptions resolverOptions, CancellationToken cancellationToken) { using var httpClient = httpClientFactory.CreateClient(); - var httpResponseMessage = await httpClient.GetAsync(uri); + var httpResponseMessage = await httpClient.GetAsync(uri, cancellationToken); if (!httpResponseMessage.IsSuccessStatusCode && uri.AbsolutePath.EndsWith(".txt")) { + cancellationToken.ThrowIfCancellationRequested(); // try without .txt extension var fixedUri = new UriBuilder(uri); fixedUri.Path = fixedUri.Path.Remove(fixedUri.Path.Length - 4); - httpResponseMessage = await httpClient.GetAsync(fixedUri.Uri); + httpResponseMessage = await httpClient.GetAsync(fixedUri.Uri, cancellationToken); if (!httpResponseMessage.IsSuccessStatusCode) return null; } @@ -221,22 +226,24 @@ internal partial class LicenseService(ILogger logger, IEnumerabl { foreach (var resolver in urlLicenseResolvers) { - if (!resolver.CanResolve(httpResponseMessage.RequestMessage.RequestUri)) + if (!await resolver.CanResolveAsync(httpResponseMessage.RequestMessage.RequestUri, resolverOptions, cancellationToken)) continue; - var license = await resolver.Resolve(httpResponseMessage.RequestMessage.RequestUri); + var license = await resolver.ResolveAsync(httpResponseMessage.RequestMessage.RequestUri, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license != null) return license; + cancellationToken.ThrowIfCancellationRequested(); } } // Finally, if no license uri can be found despite all the redirects, try to blindly get it if (httpResponseMessage.Content.Headers.ContentType?.MediaType != "text/plain") return null; - var license2 = await httpResponseMessage.Content.ReadAsStringAsync(); + var license2 = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); return UnifyLicense(license2); } - private async Task ResolveFromRepositoryUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl) + private async Task ResolveFromRepositoryUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl, ResolverOptions resolverOptions, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.RepositoryUrl)) return null; @@ -247,8 +254,8 @@ internal partial class LicenseService(ILogger logger, IEnumerabl try { var license = useFinalUrl - ? await ResolveFromFinalUrlAsync(uri, repositoryUriLicenseResolvers) - : await ResolveFromUrlAsync(uri, repositoryUriLicenseResolvers); + ? await ResolveFromFinalUrlAsync(uri, repositoryUriLicenseResolvers, resolverOptions, cancellationToken) + : await ResolveFromUrlAsync(uri, repositoryUriLicenseResolvers, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license == null) return null; @@ -263,8 +270,9 @@ internal partial class LicenseService(ILogger logger, IEnumerabl } } - private async Task ResolveFromLicenseUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl) + private async Task ResolveFromLicenseUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl, ResolverOptions resolverOptions, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.LicenseUrl)) return null; if (LicenseCache.TryGetValue(resolvedFileInfo.NuSpec.LicenseUrl, out string? value)) @@ -274,8 +282,8 @@ internal partial class LicenseService(ILogger logger, IEnumerabl try { var license = useFinalUrl - ? await ResolveFromFinalUrlAsync(uri, licenseUriLicenseResolvers) - : await ResolveFromUrlAsync(uri, licenseUriLicenseResolvers); + ? await ResolveFromFinalUrlAsync(uri, licenseUriLicenseResolvers, resolverOptions, cancellationToken) + : await ResolveFromUrlAsync(uri, licenseUriLicenseResolvers, resolverOptions, cancellationToken); license = UnifyLicense(license); if (license == null) return null; diff --git a/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs b/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs index f5ad7d3..2ea1f55 100644 --- a/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/NetFrameworkLicenseResolver.cs @@ -1,4 +1,5 @@ -using System; +using DotnetThirdPartyNotices.Models; +using System; using System.Diagnostics; using System.IO; using System.Reflection; @@ -10,11 +11,11 @@ internal class NetFrameworkLicenseResolver : ILicenseUriLicenseResolver, IFileVe { private static string? _licenseContent; - public bool CanResolve(Uri licenseUri) => licenseUri.ToString().Contains("LinkId=529443"); - public bool CanResolve(FileVersionInfo fileVersionInfo) => fileVersionInfo.ProductName == "Microsoft® .NET Framework"; + public Task CanResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(licenseUri.ToString().Contains("LinkId=529443")); + public Task CanResolveAsync(FileVersionInfo fileVersionInfo, CancellationToken cancellationToken) => Task.FromResult(fileVersionInfo.ProductName == "Microsoft® .NET Framework"); - public Task Resolve(Uri licenseUri) => GetLicenseContent(); - public Task Resolve(FileVersionInfo fileVersionInfo) => GetLicenseContent(); + public Task ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => GetLicenseContent(); + public Task ResolveAsync(FileVersionInfo fileVersionInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken) => GetLicenseContent(); public async Task GetLicenseContent() { diff --git a/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs b/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs index af96c9b..b7e4134 100644 --- a/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs +++ b/DotnetThirdPartyNotices/Services/OpenSourceOrgLicenseResolver.cs @@ -7,13 +7,13 @@ namespace DotnetThirdPartyNotices.Services; -internal class OpenSourceOrgLicenseResolver(DynamicSettings dynamicSettings) : ILicenseUriLicenseResolver +internal class OpenSourceOrgLicenseResolver : ILicenseUriLicenseResolver { - public bool CanResolve(Uri licenseUri) => licenseUri.Host == "opensource.org"; + public Task CanResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(licenseUri.Host == "opensource.org"); internal static readonly char[] separator = ['/']; - public async Task Resolve(Uri licenseUri) + public async Task ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken) { var s = licenseUri.AbsolutePath.Split(separator, StringSplitOptions.RemoveEmptyEntries); if (s[0] != "licenses") @@ -22,12 +22,12 @@ internal class OpenSourceOrgLicenseResolver(DynamicSettings dynamicSettings) : I HttpClient httpClient = new() { BaseAddress = new Uri("https://api.github.com") }; // https://developer.github.com/v3/#user-agent-required httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense"); - if (!string.IsNullOrEmpty(dynamicSettings.GitHubToken)) - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", dynamicSettings.GitHubToken); - var httpResponseMessage = await httpClient.GetAsync($"licenses/{licenseId}"); + if (!string.IsNullOrEmpty(resolverOptions.GitHubToken)) + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolverOptions.GitHubToken); + var httpResponseMessage = await httpClient.GetAsync($"licenses/{licenseId}", cancellationToken); if (!httpResponseMessage.IsSuccessStatusCode) return null; - var content = await httpResponseMessage.Content.ReadAsStringAsync(); + var content = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); var jsonDocument = JsonDocument.Parse(content); return jsonDocument.RootElement.GetProperty("body").GetString(); }