diff --git a/Doki.sln b/Doki.sln
index de5371a..32a18f1 100644
--- a/Doki.sln
+++ b/Doki.sln
@@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.TestAssembly", "tests\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.Tests.Common", "tests\Doki.Tests.Common\Doki.Tests.Common.csproj", "{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.CommandLine.Tests", "tests\Doki.CommandLine.Tests\Doki.CommandLine.Tests.csproj", "{67B12AF1-2696-411F-ADFD-B5C32A43BA71}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -52,6 +54,7 @@ Global
{6CCD9EE6-B3FC-485F-9155-553165141B20} = {8C7B5305-B599-4F08-B28B-DD9F1715DD51}
{0293D689-DFDC-4A78-80D8-BFC11DB0A175} = {08041208-BE3D-4BE8-9AF7-806B73985275}
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC} = {8C7B5305-B599-4F08-B28B-DD9F1715DD51}
+ {67B12AF1-2696-411F-ADFD-B5C32A43BA71} = {8C7B5305-B599-4F08-B28B-DD9F1715DD51}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6F31B87A-2BD3-4FB4-8C08-7E059A338D4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -110,5 +113,9 @@ Global
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {67B12AF1-2696-411F-ADFD-B5C32A43BA71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {67B12AF1-2696-411F-ADFD-B5C32A43BA71}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {67B12AF1-2696-411F-ADFD-B5C32A43BA71}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {67B12AF1-2696-411F-ADFD-B5C32A43BA71}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/Doki.CommandLine/Commands/GenerateCommand.Outputs.cs b/src/Doki.CommandLine/Commands/GenerateCommand.Outputs.cs
index 50aa0c0..2cfd0ed 100644
--- a/src/Doki.CommandLine/Commands/GenerateCommand.Outputs.cs
+++ b/src/Doki.CommandLine/Commands/GenerateCommand.Outputs.cs
@@ -34,7 +34,7 @@ internal partial class GenerateCommand
var nugetFolder = Path.Combine(workingDirectory.FullName, ".doki", "nuget");
- using var nugetLoader = new NuGetLoader(output.From);
+ using var nugetLoader = new NuGetLoader(_logger, output.From);
var assemblyPath =
await nugetLoader.LoadPackageAsync(output.Type, nugetFolder, allowPreview, cancellationToken);
diff --git a/src/Doki.CommandLine/Doki.CommandLine.csproj b/src/Doki.CommandLine/Doki.CommandLine.csproj
index 2b84899..0787513 100644
--- a/src/Doki.CommandLine/Doki.CommandLine.csproj
+++ b/src/Doki.CommandLine/Doki.CommandLine.csproj
@@ -21,18 +21,22 @@
..\..\nuget
+
+
+
+
-
+
-
+
-
+
diff --git a/src/Doki.CommandLine/NuGet/NuGetLoader.cs b/src/Doki.CommandLine/NuGet/NuGetLoader.cs
index c05e1c0..48f03e0 100644
--- a/src/Doki.CommandLine/NuGet/NuGetLoader.cs
+++ b/src/Doki.CommandLine/NuGet/NuGetLoader.cs
@@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyModel;
-using NuGet.Common;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.Packaging;
@@ -16,12 +15,13 @@ internal class NuGetLoader : IDisposable
{
private readonly SourceCacheContext _cacheContext = new();
- //TODO use console logger (ASCII)
- private readonly ILogger _logger = NullLogger.Instance;
+ private readonly NuGetLogger _logger;
private readonly SourceRepositoryProvider _sourceRepositoryProvider;
- public NuGetLoader(string? source = null)
+ public NuGetLoader(Microsoft.Extensions.Logging.ILogger logger, string? source = null)
{
+ _logger = new NuGetLogger(logger);
+
var sources = new List
{
new("https://api.nuget.org/v3/index.json")
@@ -39,19 +39,27 @@ public async Task LoadPackageAsync(string packageId, string destinationD
{
ArgumentNullException.ThrowIfNull(packageId, nameof(packageId));
- var packageIdentity = await GetPackageIdentityAsync(packageId, allowPreview, cancellationToken);
+ var currentVersion = NuGetVersion.Parse(typeof(DocumentationObject).Assembly.GetName().Version!.ToString());
+
+ var packageIdentities = await GetPackageIdentitiesAsync(packageId, allowPreview, cancellationToken);
+ foreach (var packageIdentity in packageIdentities)
+ {
+ var dependencyInfos = new HashSet();
+ var result = await ScanPackagesAsync(currentVersion, packageIdentity, dependencyInfos, cancellationToken);
+ if (!result) continue;
- var dependencyInfos = new HashSet();
- await CollectPackagesAsync(packageIdentity, dependencyInfos, cancellationToken);
+ var packagesToInstall = GetPackagesToInstall(packageId, dependencyInfos.ToArray());
- var packagesToInstall = GetPackagesToInstall(packageId, dependencyInfos.ToArray());
+ var settings = Settings.LoadDefaultSettings(destinationDirectory);
- var settings = Settings.LoadDefaultSettings(destinationDirectory);
+ await InstallPackagesAsync(packagesToInstall, destinationDirectory, settings, cancellationToken);
- await InstallPackagesAsync(packagesToInstall, destinationDirectory, settings, cancellationToken);
+ return Path.Combine(destinationDirectory, $"{packageIdentity.Id}.{packageIdentity.Version}", "lib",
+ "net8.0",
+ $"{packageIdentity.Id}.dll");
+ }
- return Path.Combine(destinationDirectory, $"{packageIdentity.Id}.{packageIdentity.Version}", "lib", "net8.0",
- $"{packageIdentity.Id}.dll");
+ throw new InvalidOperationException($"No applicable package '{packageId}' found for version: {currentVersion}");
}
private async Task InstallPackagesAsync(IEnumerable packagesToInstall,
@@ -90,9 +98,9 @@ private IEnumerable GetPackagesToInstall(string pac
var resolverContext = new PackageResolverContext(
DependencyBehavior.Lowest,
new[] { packageId },
- Enumerable.Empty(),
- Enumerable.Empty(),
- Enumerable.Empty(),
+ [],
+ [],
+ [],
dependencyInfos,
_sourceRepositoryProvider.GetRepositories().Select(s => s.PackageSource),
_logger);
@@ -103,10 +111,10 @@ private IEnumerable GetPackagesToInstall(string pac
.Select(p => dependencyInfos.Single(x => PackageIdentityComparer.Default.Equals(x, p)));
}
- private async Task CollectPackagesAsync(PackageIdentity package, ICollection packages,
- CancellationToken cancellationToken)
+ private async Task ScanPackagesAsync(NuGetVersion currentVersion, PackageIdentity package,
+ ICollection packages, CancellationToken cancellationToken)
{
- if (packages.Contains(package)) return;
+ if (packages.Contains(package)) return true;
foreach (var repository in _sourceRepositoryProvider.GetRepositories())
{
@@ -120,24 +128,45 @@ private async Task CollectPackagesAsync(PackageIdentity package, ICollection();
+ foreach (var dependency in dependencyInfo.Dependencies)
+ {
+ if (dependency.Id is "Doki" or "Doki.Abstractions" or "Doki.Output.Extensions"
+ or "Doki.Output.Abstractions")
+ {
+ if (dependency.VersionRange.Satisfies(currentVersion)) continue;
+
+ _logger.LogDebug(
+ $"Doki dependency '{dependency.Id}' version '{dependency.VersionRange}' does not satisfy current version '{currentVersion}'");
+
+ return false;
+ }
+
+ if (!IsDependencyProvided(dependency)) dependencies.Add(dependency);
+ }
+
var filteredSourceDependency = new SourcePackageDependencyInfo(
dependencyInfo.Id,
dependencyInfo.Version,
- dependencyInfo.Dependencies.Where(d => !IsDependencyProvided(d)),
+ dependencies,
dependencyInfo.Listed,
dependencyInfo.Source);
packages.Add(filteredSourceDependency);
- foreach (var dependency in filteredSourceDependency.Dependencies)
+ foreach (var dependency in dependencies)
{
- await CollectPackagesAsync(new PackageIdentity(dependency.Id, dependency.VersionRange.MinVersion),
- packages, cancellationToken);
+ var result = await ScanPackagesAsync(currentVersion,
+ new PackageIdentity(dependency.Id, dependency.VersionRange.MinVersion), packages,
+ cancellationToken);
+ if (!result) return false;
}
}
+
+ return true;
}
- private async Task GetPackageIdentityAsync(string packageId, bool allowPreview,
+ private async Task> GetPackageIdentitiesAsync(string packageId, bool allowPreview,
CancellationToken cancellationToken)
{
foreach (var repository in _sourceRepositoryProvider.GetRepositories())
@@ -147,10 +176,12 @@ private async Task GetPackageIdentityAsync(string packageId, bo
var versions = await packages.GetAllVersionsAsync(packageId, _cacheContext, _logger,
cancellationToken);
- var latestVersion = versions.Where(v => allowPreview || !v.IsPrerelease).MaxBy(v => v);
- if (latestVersion == null) continue;
+ var allowedVersions = versions.Where(v => allowPreview || !v.IsPrerelease).ToArray();
- return new PackageIdentity(packageId, latestVersion);
+ if (allowedVersions.Length != 0)
+ return allowedVersions
+ .OrderByDescending(v => v)
+ .Select(v => new PackageIdentity(packageId, v));
}
throw new InvalidOperationException($"Package '{packageId}' not found.");
@@ -158,15 +189,13 @@ private async Task GetPackageIdentityAsync(string packageId, bo
private static bool IsDependencyProvided(PackageDependency dependency)
{
- if (dependency.Id is "Doki" or "Doki.Abstractions" or "Doki.Output.Abstractions") return true;
-
var runtimeLibrary = DependencyContext.Default!.RuntimeLibraries.FirstOrDefault(r => r.Name == dependency.Id);
if (runtimeLibrary == null) return false;
var parsedLibVersion = NuGetVersion.Parse(runtimeLibrary.Version);
- return parsedLibVersion.IsPrerelease || dependency.VersionRange.Satisfies(parsedLibVersion);
+ return dependency.VersionRange.Satisfies(parsedLibVersion);
}
public void Dispose()
diff --git a/src/Doki.CommandLine/NuGet/NuGetLogger.cs b/src/Doki.CommandLine/NuGet/NuGetLogger.cs
new file mode 100644
index 0000000..bed22be
--- /dev/null
+++ b/src/Doki.CommandLine/NuGet/NuGetLogger.cs
@@ -0,0 +1,85 @@
+using Microsoft.Extensions.Logging;
+using NuGet.Common;
+using ILogger = NuGet.Common.ILogger;
+using LogLevel = NuGet.Common.LogLevel;
+
+namespace Doki.CommandLine.NuGet;
+
+internal class NuGetLogger(Microsoft.Extensions.Logging.ILogger logger) : ILogger
+{
+ public void LogDebug(string data)
+ {
+ logger.LogDebug(data);
+ }
+
+ public void LogVerbose(string data)
+ {
+ logger.LogTrace(data);
+ }
+
+ public void LogInformation(string data)
+ {
+ logger.LogInformation(data);
+ }
+
+ public void LogMinimal(string data)
+ {
+ logger.LogInformation(data);
+ }
+
+ public void LogWarning(string data)
+ {
+ logger.LogWarning(data);
+ }
+
+ public void LogError(string data)
+ {
+ logger.LogError(data);
+ }
+
+ public void LogInformationSummary(string data)
+ {
+ logger.LogInformation(data);
+ }
+
+ public void Log(LogLevel level, string data)
+ {
+ switch (level)
+ {
+ case LogLevel.Debug:
+ LogDebug(data);
+ break;
+ case LogLevel.Verbose:
+ LogVerbose(data);
+ break;
+ case LogLevel.Information:
+ LogInformation(data);
+ break;
+ case LogLevel.Minimal:
+ LogMinimal(data);
+ break;
+ case LogLevel.Warning:
+ LogWarning(data);
+ break;
+ case LogLevel.Error:
+ LogError(data);
+ break;
+ }
+ }
+
+ public Task LogAsync(LogLevel level, string data)
+ {
+ Log(level, data);
+ return Task.CompletedTask;
+ }
+
+ public void Log(ILogMessage message)
+ {
+ Log(message.Level, message.Message);
+ }
+
+ public Task LogAsync(ILogMessage message)
+ {
+ return LogAsync(message.Level, message.Message);
+ }
+}
\ No newline at end of file
diff --git a/tests/Doki.CommandLine.Tests/Doki.CommandLine.Tests.csproj b/tests/Doki.CommandLine.Tests/Doki.CommandLine.Tests.csproj
new file mode 100644
index 0000000..39c484e
--- /dev/null
+++ b/tests/Doki.CommandLine.Tests/Doki.CommandLine.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/tests/Doki.CommandLine.Tests/GlobalUsings.cs b/tests/Doki.CommandLine.Tests/GlobalUsings.cs
new file mode 100644
index 0000000..8c927eb
--- /dev/null
+++ b/tests/Doki.CommandLine.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/tests/Doki.CommandLine.Tests/NuGetTests.cs b/tests/Doki.CommandLine.Tests/NuGetTests.cs
new file mode 100644
index 0000000..f8b452c
--- /dev/null
+++ b/tests/Doki.CommandLine.Tests/NuGetTests.cs
@@ -0,0 +1,27 @@
+using Doki.CommandLine.NuGet;
+using Doki.Tests.Common;
+using Xunit.Abstractions;
+
+namespace Doki.CommandLine.Tests;
+
+public class NuGetTests(ITestOutputHelper testOutputHelper)
+{
+ [Fact]
+ public async Task NuGetLoader_TestAsync()
+ {
+ const string packageId = "Doki.Output.Json";
+ var tmpDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+
+ var logger = new TestOutputLogger(testOutputHelper);
+
+ using var loader = new NuGetLoader(logger);
+
+ await loader.LoadPackageAsync(packageId, tmpDirectory);
+
+ Assert.False(logger.HadError);
+
+ var dllPath = Path.Combine(tmpDirectory, $"{packageId}.1.0.0", "lib", "net8.0", $"{packageId}.dll");
+
+ Assert.True(File.Exists(dllPath));
+ }
+}
\ No newline at end of file