Search code examples
c#.netnuget

Nuget Package downloaded to wrong directory


When downloading a Nuget Package using the following code, said package is added, as expected, to my MachineWideConfigDirectory.

However, the folder it is added to is something like:

.nuget/packages/Microsoft.Extensions.Logging.6.0.0

instead of:

.nuget/packages/microsoft.extensions.logging/6.0.0.

Any idea on how to get it working properly?

Code to programatically download the package:

var settings = Settings.LoadDefaultSettings(string.Empty, null, new MachineWideSettings());
var packageSourceProvider = new PackageSourceProvider(settings);
var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, Providers);
var framework = NuGetFramework.Parse(Assembly.GetEntryAssembly()!.GetCustomAttribute<TargetFrameworkAttribute>()!.FrameworkName!);

var package = await this.GetPackageAsync(this.PackageId, this.PackageVersion, this.IncludePreRelease, false, cancellationToken).ConfigureAwait(false) ?? throw new ArgumentNullException($"Failed to find the specified package with id '{this.PackageId}' and version '{this.PackageVersion}'");
var project = new PluginNugetProject(this.PackagesDirectory.FullName, this.PluginDirectory.FullName, package.Identity, framework);
var packageManager = new NuGetPackageManager(sourceRepositoryProvider, settings, this.PackagesDirectory.FullName) { PackagesFolderNuGetProject = project };
var clientPolicyContext = ClientPolicyContext.GetClientPolicy(settings, this.NugetLogger);
var projectContext = new PluginNugetProjectContext(this.LoggerFactory) { PackageExtractionContext = new PackageExtractionContext(PackageSaveMode.Defaultv3, PackageExtractionBehavior.XmlDocFileSaveMode, clientPolicyContext, this.NugetLogger) };
var resolutionContext = new ResolutionContext(DependencyBehavior.Lowest, true, false, VersionConstraints.None);
var downloadContext = new PackageDownloadContext(resolutionContext.SourceCacheContext, this.PackagesDirectory.FullName, resolutionContext.SourceCacheContext.DirectDownload);

await packageManager.InstallPackageAsync(project, package.Identity, resolutionContext, projectContext, downloadContext, package.SourceRepository, new List<SourceRepository>(), cancellationToken).ConfigureAwait(false);
await project.PostProcessAsync(projectContext, cancellationToken).ConfigureAwait(false);
await project.PreProcessAsync(projectContext, cancellationToken).ConfigureAwait(false);
await packageManager.RestorePackageAsync(package.Identity, projectContext, downloadContext, new[] { package.SourceRepository }, cancellationToken).ConfigureAwait(false);

MachineWideSettings:

class MachineWideSettings
    : IMachineWideSettings
{

    public ISettings Settings => NuGet.Configuration.Settings.LoadMachineWideSettings(NuGetEnvironment.GetFolderPath(NuGetFolderPath.MachineWideConfigDirectory));

}

Edit:

Here's my nuget.config, as requested by @jakub-podhaisky:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
    <add key="Microsoft Visual Studio Offline Packages" value="C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\" />
  </packageSources>
</configuration>

Solution

  • There are two distinct paths to consider here: the "project related" package directory and the global NuGet cache directory. It's essential to differentiate between them for a clearer understanding of the issue.

    It appears that your confusion stems from configuring your "project" directory to function similarly to the "global cache" directory. My assumption is based on the structure outlined in Dave Glick’s articles, and PluginNugetProject extends FolderNuGetProject.

    It seems you’re passing .nuget/packages/ as your project root directory when creating your instance of PluginNugetProject.

    Additionally, the FolderNuGetProject can be constructed by passing a PackagePathResolver, which in turn, accepts a useSideBySidePath argument. When set to true (which is the default behavior), the PackagePathResolver concatenates your project root folder with the package identifier and version you're installing.

    Consequently, the final installation directory for a specific package will be $"{projectRoot}/{packageIdentity.Id}.{packageIdentity.Version}", resulting in .nuget/packages/ + Microsoft.Extensions.Logging + . + 6.0.0.

    However, you might wonder why the global cache directory isn't created. There should ideally be a directory structure like .nuget/packages/microsoft.extensions.logging/6.0.0.

    Understanding this behavior requires further investigation into the NuGet client sources. When building your ResolutionContext, you don't pass any SourceCacheContext. Consequently, the NullSourceCacheContext is utilized, with its DirectDownload property set to true by default.

    As the name suggests, direct download bypasses the global NuGet directory and downloads the package directly to its destination.

    To enable the global caching mechanism and cache your package in .nuget/packages/microsoft.extensions.logging/6.0.0, set resolutionContext.SourceCacheContext.DirectDownload = false;.

    In conclusion, depending on your use case, you have two options:

    • Implement and provide your own PackagePathResolver.
    • Set DirectDownload to false.

    Each option has its implications and trade-offs, so consider your requirements carefully.