Skip to content

Commit

Permalink
Allow explicit offline mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-englert committed Sep 21, 2023
1 parent 275f6a4 commit 0411854
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Notice.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This product bundles the following components under the described licenses:
MSBuild Locator

Id: Microsoft.Build.Locator
Version: 1.6.1
Version: 1.6.10
Project: https://github.com/microsoft/MSBuildLocator
License: MIT

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ dotnet tool install TomsToolbox.LicenseGenerator -g
```
build-license [options]
```
#### Options
### Options
```
-i, --input <input> (REQUIRED) The path to the solution file to process.
-o, --output <output> The name of the license file that is created.
An existing file will be overwritten without confirmation.
Default is Notice.txt in the same folder as the solution.
-e, --exclude <exclude> A regular expression to specify package ids to exclude from output.
--recursive A flag to indicate that all dependencies should be scanned recursively.
--offline A flag to indicate that only the locally cached packages should be scanned (requires a restor beforehand).
--version Show version information
-?, -h, --help Show help and usage information
```
Expand Down
44 changes: 27 additions & 17 deletions src/LicenseGenerator/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;

Expand All @@ -27,14 +28,16 @@ internal sealed class Builder : IDisposable
private readonly Dictionary<string, ProjectInfo> _includedProjects = new(StringComparer.OrdinalIgnoreCase);
private readonly string _outputPath;
private readonly bool _recursive;
private readonly bool _offline;
private readonly string _solutionDirectory;
private readonly Regex? _excludeRegex;
private readonly Dictionary<NuGetFramework, TargetFrameworkCollection> _targetFrameworkCollections = new();

public Builder(FileInfo input, string output, string? exclude, bool recursive)
public Builder(FileInfo input, string output, string? exclude, bool recursive, bool offline)
{
_outputPath = output;
_recursive = recursive;
_offline = offline;
_solutionDirectory = input.DirectoryName ?? ".";
_excludeRegex = string.IsNullOrEmpty(exclude) ? null : new Regex(exclude, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

Expand Down Expand Up @@ -187,7 +190,7 @@ private static NuGetFramework ToPlatformVersionIndependent(NuGetFramework framew

private async Task LoadPackage(FrameworkSpecificProject project, PackageIdentity packageIdentity, ICollection<SourceRepository> repositories, PackageDownloadContext context, IDictionary<string, PackageArchiveReader> resolvedPackages, string globalPackagesFolder)
{
List<string> lastExceptions = new List<string>();
var lastExceptions = new List<string>();

if (resolvedPackages.TryGetValue(packageIdentity.Id, out var existingPackage) && existingPackage.GetIdentity().Version >= packageIdentity.Version)
{
Expand All @@ -198,19 +201,7 @@ private async Task LoadPackage(FrameworkSpecificProject project, PackageIdentity

foreach (var repository in repositories)
{
DownloadResourceResult downloadResult;

try
{
var downloadResource = await repository.GetResourceAsync<DownloadResource>();

downloadResult = await downloadResource.GetDownloadResourceResultAsync(packageIdentity, context, globalPackagesFolder, NullLogger.Instance, CancellationToken.None);
}
catch (NuGetProtocolException ex)
{
lastExceptions.Add(ex.Message);
continue; // Try next repo
}
var downloadResult = await GetDownloadResultAsync(packageIdentity, context, globalPackagesFolder, repository, lastExceptions);

if (downloadResult.Status != DownloadResourceResultStatus.Available)
continue; // Try next repo
Expand All @@ -224,7 +215,7 @@ private async Task LoadPackage(FrameworkSpecificProject project, PackageIdentity

await using var nuspec = package.GetNuspec();

bool ScanDependencies()
bool ShouldScanDependencies()
{
// Don't scan packages with pseudo-references, they don't get physically included.
if (string.Equals(packageIdentity.Id, "NETStandard.Library", StringComparison.OrdinalIgnoreCase))
Expand All @@ -241,7 +232,7 @@ bool ScanDependencies()

}

if (!ScanDependencies())
if (!ShouldScanDependencies())
return;

var packageDependencies = package.GetPackageDependencies()?.ToArray();
Expand Down Expand Up @@ -270,6 +261,25 @@ bool ScanDependencies()
throw new InvalidOperationException($"Package {packageIdentity} not found in any of the configured repositories: {string.Join(", ", lastExceptions)}");
}

private async Task<DownloadResourceResult> GetDownloadResultAsync(PackageIdentity packageIdentity, PackageDownloadContext context, string globalPackagesFolder, SourceRepository repository, ICollection<string> lastExceptions)
{
try
{
if (_offline)
{
return GlobalPackagesFolderUtility.GetPackage(packageIdentity, globalPackagesFolder);
}

var downloadResource = await repository.GetResourceAsync<DownloadResource>();
return await downloadResource.GetDownloadResourceResultAsync(packageIdentity, context, globalPackagesFolder, NullLogger.Instance, CancellationToken.None);
}
catch (NuGetProtocolException ex)
{
lastExceptions.Add(ex.Message);
return new DownloadResourceResult(DownloadResourceResultStatus.NotFound);
}
}

private async Task<string> CreateLicenseFile(IEnumerable<PackageArchiveReader> packages)
{
var content = new StringBuilder("This product bundles the following components under the described licenses:\r\n\r\n");
Expand Down
4 changes: 2 additions & 2 deletions src/LicenseGenerator/LicenseGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="[17.4.0]" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.6.1" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.6.10" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="[17.4.0]" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
<PackageReference Include="NuGet.ProjectModel" Version="6.7.0" />
Expand All @@ -65,6 +65,6 @@
</ItemGroup>

<Target Name="CreateNotice" AfterTargets="Build">
<Exec Command="$(RunCommand) -i ../LicenseGenerator.sln -o ..\..\Notice.txt" />
<Exec Command='"$(RunCommand)" -i ../LicenseGenerator.sln -o ..\..\Notice.txt' />
</Target>
</Project>
13 changes: 9 additions & 4 deletions src/LicenseGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,32 @@
""") { IsRequired = false };

var recursiveOption = new Option<bool>(new[] { "--recursive" }, """
A flag to indicate that all dependencies should be scanned recursively.
A flag to indicate that all dependencies should be scanned recursively.
""") { IsRequired = false };

var offlineOption = new Option<bool>(new[] { "--offline" }, """
A flag to indicate that only the locally cached packages should be scanned (requires a restor beforehand).
""") { IsRequired = false };

rootCommand.AddOption(inputOption);
rootCommand.AddOption(outputOption);
rootCommand.AddOption(excludeOption);
rootCommand.AddOption(recursiveOption);
rootCommand.SetHandler(async (input, output, exclude, recursive) => returnValue = await Run(input, output, exclude, recursive), inputOption, outputOption, excludeOption, recursiveOption);
rootCommand.AddOption(offlineOption);
rootCommand.SetHandler(async (input, output, exclude, recursive, offline) => returnValue = await Run(input, output, exclude, recursive, offline), inputOption, outputOption, excludeOption, recursiveOption, offlineOption);

await rootCommand.InvokeAsync(args);

return returnValue;

static async Task<int> Run(FileInfo input, string? output, string? exclude, bool recursive)
static async Task<int> Run(FileInfo input, string? output, string? exclude, bool recursive, bool offline)
{
var visualStudioInstance = MSBuildLocator.QueryVisualStudioInstances().MaxBy(instance => instance.Version);
MSBuildLocator.RegisterInstance(visualStudioInstance);

try
{
using var builder = new Builder(input, output ?? "Notice.txt", exclude, recursive);
using var builder = new Builder(input, output ?? "Notice.txt", exclude, recursive, offline);
return await builder.Build();
}
catch (Exception ex)
Expand Down

0 comments on commit 0411854

Please sign in to comment.