Calling InstallPackageAsync with my custom FolderNuGetProject does not install files

Viewed 65

When using the v6.3 Nuget.Packaging and NuGet.PackageManagement libaries, I am unable to install packages like I can when using v.4.0 equivalent.

It seems to be releated to my PluginProject.InstallPackageAsync override, v6 throws a null refernce exception but not with v4. Not calling the base.InstallPackagesAsync fails to install any packages in my folder so im sure its key.

// Class is derived from FolderNuGetProject and method overidden
public override async Task<bool> InstallPackageAsync(PackageIdentity packageIdentity, DownloadResourceResult downloadResourceResult,
    INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
    var result = await base.InstallPackageAsync(packageIdentity, downloadResourceResult, nuGetProjectContext, token);
    await _packagesConfigProject.InstallPackageAsync(packageIdentity, downloadResourceResult, nuGetProjectContext, token);
    return result;
}

Even when using the FolderNuGetProject directly, it throws - so I have found the source of the error need to debug the source to understand why... nuGetProjectContext.PackageExtractionContext is null.

object null ref

Detail

I writing an app which has plugins downloadable from nuget, which can then be dynamically loaded into my application - similair to this interesting talk.

I derive my class from the NuGet.ProjectManagement.FolderNuGetProject, however files are not installed after calling NuGetPackageManager.InstallPackageAsync with my custom folder project, only packages.config is updated per below:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Unified.Mqtt.Pattern" version="1.0.3" targetFramework="netstandard2.0" />
</packages>

But no files in the directory other than that:

PS C:\ProgramData\unified.msp> dir

    Directory: C:\ProgramData\unified.msp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        07/08/2022     11:07            156 packages.config

The Project class is below:

public class PluginProject : FolderNuGetProject
{
    private readonly PackagesConfigNuGetProject packagesConfigProject;

    public PluginProject(string root) : base(root)
    {
        this.packagesConfigProject = new PackagesConfigNuGetProject(root, new Dictionary<string, object>()
        {
            { "TargetFramework", NuGetFramework.AnyFramework },
            { "Name", "My.Package.Installer" }
        });
    }

    public override Task<IEnumerable<PackageReference>> GetInstalledPackagesAsync(CancellationToken cancellationToken)
    {
        return this.packagesConfigProject.GetInstalledPackagesAsync(cancellationToken);
    }

    public override Task<bool> InstallPackageAsync(PackageIdentity packageIdentity, DownloadResourceResult downloadResourceResult,
        INuGetProjectContext nuGetProjectContext, CancellationToken token)
    {
        // THIS LINE THROWS NULL REFERENCE WITH v6, BUT NOT v4
        var result = await DeletePackage(packageIdentity, nuGetProjectContext, token);
        await 
        packagesConfigProject.UninstallPackageAsync(packageIdentity, nuGetProjectContext, token);
        return result;
    }
}

And the overall plugin manager is below (which scaffolds everything together into the NuGetPackangeManager method calls. It does list installed packages, and can install (ableit without the actual file contents):

public class NugetPluginManager
{
    private readonly IOptions<NugetPluginManagerSettings> settings;
    private readonly LoggerAdapter logger;
    public NugetPluginManager(IOptions<NugetPluginManagerSettings> settings, ILogger<NugetPluginManager> logger)
    {
        this.settings = settings;
        this.logger = new LoggerAdapter(logger);
    }

    private static (SourceRepository Repository, List<Lazy<INuGetResourceProvider>> Providers) GetRepository(string repositoryUrl)
    {
        var providers = new List<Lazy<INuGetResourceProvider>>();
        providers.AddRange(Repository.Provider.GetCoreV3());
        var packageSource = new PackageSource(repositoryUrl);
        var repository = new SourceRepository(packageSource, providers);

        if(packageSource.SourceUri.Scheme != Uri.UriSchemeHttps)
            throw new ArgumentException($"{repositoryUrl} is not https.", nameof(repositoryUrl));
        return (repository, providers);
    }

    public async Task<IEnumerable<PackageReference>> GetInstalledPackages(CancellationToken cancellationToken = default)
    {
        var commonApplicationDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
        var rootAppPath = Path.Combine(commonApplicationDataPath, "unified.msp");
        var packagesPath = Path.Combine(rootAppPath, "packages");
        var project = new PluginProject(rootAppPath);
        return await project.GetInstalledPackagesAsync(cancellationToken);
    }

    public async Task Install(string repositoryUrl, string packageName, string version, bool includeDependencies,
        bool includePreRelease, CancellationToken cancellationToken = default)
    {
        var (repository, providers) = GetRepository(repositoryUrl);

        var commonApplicationDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
        var rootAppPath = Path.Combine(commonApplicationDataPath, "unified.msp");
        var packagesPath = Path.Combine(rootAppPath, "packages");

        var project = new PluginProject(rootAppPath);

        var defaultSettings = Settings.LoadDefaultSettings(rootAppPath, null, new XPlatMachineWideSetting());

        // Create the package manager - TODO: https://github.com/NuGet/Home/issues/8479
        #pragma warning disable CS0618 // Type or member is obsolete
        var repositoryProvider = new SourceRepositoryProvider(defaultSettings, providers);
        #pragma warning restore CS0618 // Type or member is obsolete

        var packageManager = new NuGetPackageManager(repositoryProvider, defaultSettings, packagesPath)
        {
            PackagesFolderNuGetProject = project
        };

        var dependencyBehaviour = includeDependencies ? DependencyBehavior.Lowest : DependencyBehavior.Ignore;

        var resolutionContext =
            new ResolutionContext(dependencyBehaviour, includePreRelease, false, VersionConstraints.None);

        var projectContext = new EmptyNuGetProjectContext();

        await packageManager.InstallPackageAsync(
                                packageManager.PackagesFolderNuGetProject,
                                new PackageIdentity(packageName, NuGetVersion.Parse(version)),
                                resolutionContext,
                                projectContext,
                                repository,
                                Array.Empty<SourceRepository>(),
                                cancellationToken);
    }
}
2 Answers

Stepping into the source code with Resharper the error the PackageExtractionContext was missing! Adding below resolved this:

var settings = Settings.LoadDefaultSettings(root: null);

var projectContext = new EmptyNuGetProjectContext()
{
    PackageExtractionContext = new PackageExtractionContext(
        PackageSaveMode.Defaultv3,
        XmlDocFileSaveMode.Skip,
        ClientPolicyContext.GetClientPolicy(settings, this.logger),
        this.logger)
};

await packageManager.InstallPackageAsync(
    packageManager.PackagesFolderNuGetProject,
    new PackageIdentity(packageId, NuGetVersion.Parse(packageVersion)),
    new ResolutionContext(DependencyBehavior.Ignore, false, false, VersionConstraints.None),
    projectContext,
    _repository,
    new SourceRepository[] { },
    CancellationToken.None);

nuGetProjectContext.PackageExtractionContext is null.

This is obviously because the package is missing in the first place! :3

When using the v6.3 Nuget.Packaging and NuGet.PackageManagement libaries, I am unable to install packages like I can when using v.4.0 equivalent.

Of course, you're gonna have to install them manually in the code you provided in your answer!

Tip: Since you're using a newer version, it is highly possible the PackageExtractionContext package is no longer compatible with the new version, and have to be installed by you manually.

This applies for many libraries and extensions with its packages.

Related