From a3dcfa432d69f56e9536fcef2eac3f986834d461 Mon Sep 17 00:00:00 2001 From: Simon Ensslen Date: Fri, 1 Nov 2024 21:27:13 +0100 Subject: [PATCH] Add option to output to a file instead of the console --- README.md | 1 + src/NuGetUtility/NuGetUtility.csproj | 2 +- src/NuGetUtility/Program.cs | 73 ++++++++++++++----- .../HttpClientWrapper/FileDownloader.cs | 4 +- .../NuGetUtility.Test.csproj | 2 +- 5 files changed, 58 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index e2110893..9ceece9a 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Usage: nuget-license [options] | `-include-ignored\|--include-ignored-packages` | If this option is set, the packages that are ignored from validation are still included in the output. | | `-exclude-projects\|--exclude-projects-matching ` | This option allows to specify project name(s) to exclude from the analysis. This can be useful to exclude test projects from the analysis when supplying a solution file as input. Wildcard characters (*) are supported to specify ranges of ignored projects. The input can either be a file name containing a list of project names in json format or a plain string that is then used as a single entry. | | `-f\|--target-framework ` | This option allows to select a Target framework moniker (https://learn.microsoft.com/en-us/dotnet/standard/frameworks) for which to analyze dependencies. | +| `-fo\|--file-output ` | The destination file to put the valiation output to. If omitted, the output is printed to the console. | | `-?\|-h\|--help` | Show help information. | ## Example tool commands diff --git a/src/NuGetUtility/NuGetUtility.csproj b/src/NuGetUtility/NuGetUtility.csproj index a6313c1f..8c6ccbfb 100644 --- a/src/NuGetUtility/NuGetUtility.csproj +++ b/src/NuGetUtility/NuGetUtility.csproj @@ -37,7 +37,7 @@ - + diff --git a/src/NuGetUtility/Program.cs b/src/NuGetUtility/Program.cs index 52087973..216a0eab 100644 --- a/src/NuGetUtility/Program.cs +++ b/src/NuGetUtility/Program.cs @@ -98,10 +98,17 @@ public class Program Description = "This option allows to select a Target framework moniker (https://learn.microsoft.com/en-us/dotnet/standard/frameworks) for which to analyze dependencies.")] public string? TargetFramework { get; } = null; + [Option(LongName = "file-output", + ShortName = "fo", + Description = "The destination file to put the valiation output to. If omitted, the output is printed to the console.")] + public string? DestinationFile { get; } = null; + private static string GetVersion() => typeof(Program).Assembly.GetCustomAttribute()?.InformationalVersion ?? string.Empty; +#pragma warning disable S1144 // Unused private types or members should be removed private async Task OnExecuteAsync(CancellationToken cancellationToken) +#pragma warning restore S1144 // Unused private types or members should be removed { using var httpClient = new HttpClient(); string[] inputFiles = GetInputFiles(); @@ -119,37 +126,41 @@ private async Task OnExecuteAsync(CancellationToken cancellationToken) allowedLicenses, urlLicenseFileDownloader, ignoredPackages); - var projectReaderExceptions = new List(); string[] excludedProjects = GetExcludedProjects(); IEnumerable projects = inputFiles.SelectMany(projectCollector.GetProjects).Where(p => !Array.Exists(excludedProjects, ignored => p.Like(ignored))); - IEnumerable packagesForProject = projects.Select(p => - { - IEnumerable? installedPackages = null; - try - { - installedPackages = projectReader.GetInstalledPackages(p, IncludeTransitive, TargetFramework); - } - catch (Exception e) - { - projectReaderExceptions.Add(e); - } - return new ProjectWithReferencedPackages(p, installedPackages ?? Enumerable.Empty()); - }); + IEnumerable packagesForProject = GetPackagesPerProject(projects, projectReader, out IReadOnlyCollection? projectReaderExceptions); IAsyncEnumerable downloadedLicenseInformation = packagesForProject.SelectMany(p => GetPackageInformations(p, overridePackageInformation, cancellationToken)); var results = (await validator.Validate(downloadedLicenseInformation, cancellationToken)).ToList(); - if (projectReaderExceptions.Any()) + if (projectReaderExceptions.Count > 0) { await WriteValidationExceptions(projectReaderExceptions); return -1; } - using Stream outputStream = Console.OpenStandardOutput(); - await output.Write(outputStream, results.OrderBy(r => r.PackageId).ThenBy(r => r.PackageVersion).ToList()); - return results.Count(r => r.ValidationErrors.Any()); + try + { + using Stream outputStream = GetOutputStream(); + await output.Write(outputStream, results.OrderBy(r => r.PackageId).ThenBy(r => r.PackageVersion).ToList()); + return results.Count(r => r.ValidationErrors.Any()); + } + catch (Exception e) + { + await Console.Error.WriteLineAsync(e.ToString()); + return -1; + } + } + + private Stream GetOutputStream() + { + if (DestinationFile is null) + { + return Console.OpenStandardOutput(); + } + return File.Open(Path.GetFullPath(DestinationFile), FileMode.Create, FileAccess.Write, FileShare.None); } private static IPackagesConfigReader GetPackagesConfigReader() @@ -157,7 +168,7 @@ private static IPackagesConfigReader GetPackagesConfigReader() #if NETFRAMEWORK return new WindowsPackagesConfigReader(); #else - return OperatingSystem.IsWindows() ? new WindowsPackagesConfigReader() : new FailingPackagesConfigReader(); + return OperatingSystem.IsWindows() ? new WindowsPackagesConfigReader() : new FailingPackagesConfigReader(); #endif } @@ -202,7 +213,7 @@ private IFileDownloader GetFileDownloader(HttpClient httpClient) return new FileDownloader(httpClient, DownloadLicenseInformation); } - private static async Task WriteValidationExceptions(List validationExceptions) + private static async Task WriteValidationExceptions(IReadOnlyCollection validationExceptions) { foreach (Exception exception in validationExceptions) { @@ -286,5 +297,27 @@ private string[] GetInputFiles() throw new FileNotFoundException("Please provide an input file"); } + + private IReadOnlyCollection GetPackagesPerProject(IEnumerable projects, ReferencedPackageReader reader, out IReadOnlyCollection exceptions) + { + var encounteredExceptions = new List(); + var result = new List(); + exceptions = encounteredExceptions; + foreach (string project in projects) + { + + try + { + IEnumerable installedPackages = reader.GetInstalledPackages(project, IncludeTransitive, TargetFramework); + result.Add(new ProjectWithReferencedPackages(project, installedPackages)); + } + catch (Exception e) + { + encounteredExceptions.Add(e); + } + } + + return result; + } } } diff --git a/src/NuGetUtility/Wrapper/HttpClientWrapper/FileDownloader.cs b/src/NuGetUtility/Wrapper/HttpClientWrapper/FileDownloader.cs index 2c148009..1d7d9fa8 100644 --- a/src/NuGetUtility/Wrapper/HttpClientWrapper/FileDownloader.cs +++ b/src/NuGetUtility/Wrapper/HttpClientWrapper/FileDownloader.cs @@ -39,9 +39,9 @@ public async Task DownloadFile(Uri url, string fileNameStem, CancellationToken t } } -#pragma warning disable S1172 +#pragma warning disable S1172 // Unused parameter private async Task TryDownload(string fileNameStem, Uri url, CancellationToken token) -#pragma warning restore S1172 +#pragma warning restore S1172 // Unused parameter { var request = new HttpRequestMessage(HttpMethod.Get, url); diff --git a/tests/NuGetUtility.Test/NuGetUtility.Test.csproj b/tests/NuGetUtility.Test/NuGetUtility.Test.csproj index 8993d432..6045f48f 100644 --- a/tests/NuGetUtility.Test/NuGetUtility.Test.csproj +++ b/tests/NuGetUtility.Test/NuGetUtility.Test.csproj @@ -31,7 +31,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - +