Skip to content

Commit

Permalink
Merge pull request #17 from danielklecha/master
Browse files Browse the repository at this point in the history
support GitHub token, cancellation token, custom configuration name
  • Loading branch information
danielklecha authored Mar 17, 2024
2 parents f95ca4a + d6cc76b commit d22c271
Show file tree
Hide file tree
Showing 31 changed files with 147 additions and 93 deletions.
2 changes: 1 addition & 1 deletion DotnetThirdPartyNotices.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ internal class ScanCommand : Command
{
AddArgument(new Argument<string>("scan-dir", "Path of the directory to look for projects (optional)") { Arity = ArgumentArity.ZeroOrOne });
AddOption(new Option<string>("--output-filename", () => "third-party-notices.txt", "Output filename"));
AddOption(new Option<bool>("--copy-to-outdir", () => false, "Copy output file to output directory in Release configuration"));
AddOption(new Option<bool>("--copy-to-outdir", () => false, "Copy output file to output directory in selected configuration"));
AddOption(new Option<string>("--filter", () => string.Empty, "Filter project files"));
AddOption(new Option<string>("--github-token", () => string.Empty, "GitHub's token"));
AddOption(new Option<string>("--configuration", () => "Release", "Project configuration to use"));
}

internal new class Handler(ILogger<Handler> logger, IProjectService projectService, ILicenseService licenseService) : ICommandHandler
Expand All @@ -32,7 +34,9 @@ internal class ScanCommand : Command
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; }
public string? Configuration { get; set; }

private readonly Dictionary<string, List<ResolvedFileInfo>> _licenseContents = [];
private readonly List<ResolvedFileInfo> _unresolvedFiles = [];

Expand All @@ -43,6 +47,7 @@ public int Invoke(InvocationContext context)

public async Task<int> InvokeAsync(InvocationContext context)
{
var cancellationToken = context.GetCancellationToken();
MSBuildLocator.RegisterDefaults();
ScanDir ??= Directory.GetCurrentDirectory();
var projectFilePaths = projectService.GetProjectFilePaths(ScanDir);
Expand All @@ -53,9 +58,9 @@ public async Task<int> 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;
}

Expand All @@ -67,18 +72,20 @@ 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}...", 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);
foreach (var resolvedFileInfo in resolvedFiles)
{
cancellationToken.ThrowIfCancellationRequested();
logger.LogInformation("Resolving license for {RelativeOutputPath}", resolvedFileInfo.RelativeOutputPath);
if (resolvedFileInfo.NuSpec != null)
{
Expand All @@ -88,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);
Expand All @@ -102,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());
Expand Down Expand Up @@ -134,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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<ItemGroup>
<PackageVersion Include="Microsoft.Build" Version="17.9.5" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.9.5" />
<PackageVersion Include="Microsoft.Build.Locator" Version="1.7.1" />
<PackageVersion Include="Microsoft.Build.Locator" Version="1.7.8" />
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="17.9.5" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.9.5" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<ToolCommandName>dotnet-thirdpartynotices</ToolCommandName>
<PackageId>DotnetThirdPartyNotices</PackageId>
<Description>A .NET tool to generate file with third party legal notices</Description>
<PackageVersion>0.3.2</PackageVersion>
<PackageVersion>0.3.3</PackageVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/bugproof/DotnetThirdPartyNotices</RepositoryUrl>
Expand Down
File renamed without changes.
12 changes: 12 additions & 0 deletions DotnetThirdPartyNotices/Models/ResolverOptions.cs
Original file line number Diff line number Diff line change
@@ -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 ResolverOptions
{
public string? GitHubToken { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DotnetThirdPartyNotices.Commands;
using DotnetThirdPartyNotices.Models;
using DotnetThirdPartyNotices.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
namespace DotnetThirdPartyNotices.Services;
using DotnetThirdPartyNotices.Models;
using System.Net.Http.Headers;
using System.Text;

namespace DotnetThirdPartyNotices.Services;

internal class GithubRawLicenseResolver(IHttpClientFactory httpClientFactory) : ILicenseUriLicenseResolver
{
public bool CanResolve(Uri uri) => uri.Host == "github.com";
public Task<bool> CanResolveAsync(Uri uri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(uri.Host == "github.com");

public async Task<string?> Resolve(Uri uri)
public async Task<string?> 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");
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);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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;
Expand All @@ -8,9 +10,9 @@ namespace DotnetThirdPartyNotices.Services;

internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFactory) : IProjectUriLicenseResolver, IRepositoryUriLicenseResolver
{
public bool CanResolve(Uri uri) => uri.Host == "github.com";
public Task<bool> CanResolveAsync(Uri uri, ResolverOptions resolverOptions, CancellationToken cancellationToken) => Task.FromResult(uri.Host == "github.com");

public async Task<string?> Resolve(Uri licenseUri)
public async Task<string?> ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken)
{
var repositoryPath = licenseUri.AbsolutePath.TrimEnd('/');
if (repositoryPath.EndsWith(".git"))
Expand All @@ -22,10 +24,12 @@ internal class GithubRepositoryLicenseResolver(IHttpClientFactory httpClientFact
};
// https://developer.github.com/v3/#user-agent-required
httpClient.DefaultRequestHeaders.Add("User-Agent", "DotnetLicense");
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using DotnetThirdPartyNotices.Models;
using System.Diagnostics;
using System.Threading.Tasks;

namespace DotnetThirdPartyNotices.Services;

internal interface IFileVersionInfoLicenseResolver : ILicenseResolver
{
Task<bool> CanResolveAsync(FileVersionInfo fileVersionInfo, CancellationToken cancellationToken);
Task<string?> ResolveAsync(FileVersionInfo fileVersionInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken);
}
8 changes: 8 additions & 0 deletions DotnetThirdPartyNotices/Services/ILicenseService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using DotnetThirdPartyNotices.Models;

namespace DotnetThirdPartyNotices.Services;

internal interface ILicenseService
{
Task<string?> ResolveFromResolvedFileInfoAsync(ResolvedFileInfo resolvedFileInfo, ResolverOptions resolverOptions, CancellationToken cancellationToken);
}
9 changes: 9 additions & 0 deletions DotnetThirdPartyNotices/Services/IUriLicenseResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using DotnetThirdPartyNotices.Models;

namespace DotnetThirdPartyNotices.Services;

internal interface IUriLicenseResolver
{
Task<bool> CanResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken);
Task<string?> ResolveAsync(Uri licenseUri, ResolverOptions resolverOptions, CancellationToken cancellationToken);
}
Loading

0 comments on commit d22c271

Please sign in to comment.