in CodeAnalysis/Xamarin.Interactive.CodeAnalysis/NuGet/InteractivePackageManager.cs [169:307]
public FilePath GetPackageInstallPath (InteractivePackage package)
=> project.GetInstalledPath (package.Identity);
/// <summary>
/// Install a NuGet package. Returns all newly installed packages.
/// </summary>
public async Task<IReadOnlyCollection<InteractivePackage>> InstallPackageAsync (
InteractivePackage package,
SourceRepository sourceRepository,
CancellationToken cancellationToken)
{
if (package == null)
throw new ArgumentNullException (nameof (package));
if (!package.Identity.HasVersion)
throw new ArgumentException ("PackageIdentity.Version must be set");
// TODO: File upstream issue about exception if primary source repo is offline.
// Shouldn't secondary source repos kick in? Our current work around is to
// pass the source repo from search to install, but that's not perfect.
sourceRepository = sourceRepository ?? SourceRepositories [0];
project.ResetInstallationContext ();
// Just need to apply one fixup here
if (PackageIdComparer.Equals (package.Identity.Id, FixedXamarinFormsPackageIdentity.Id) &&
package.Identity.Version != FixedXamarinFormsPackageIdentity.Version) {
Log.Warning (
TAG,
$"Replacing requested Xamarin.Forms version {package.Identity.Version} with " +
$"required version {FixedXamarinFormsPackageIdentity.Version}.");
package = package.WithVersion (
FixedXamarinFormsPackageIdentity.Version,
overwriteRange: true);
}
if (PackageIdComparer.Equals (package.Identity.Id, IntegrationPackageId)) {
Log.Warning (TAG, $"Refusing to add integration NuGet package {IntegrationPackageId}.");
return Array.Empty<InteractivePackage> ();
}
var resolutionContext = new ResolutionContext (
DependencyBehavior.Lowest, // IDEs only use Highest if upgrading
includePrelease: true,
includeUnlisted: true,
versionConstraints: VersionConstraints.None);
// Although there is a single repo associated with the package being installed,
// dependency resolution will also look into the secondary sources. In some cases,
// this can greatly slow down installation. For the primary case of searching for
// packages in nuget.org, prevent the package manager from using secondary sources
// for resolution.
//
// It is important to pass an empty enumerable, because if we pass null, the package
// manager will determine secondary sources based on the NuGet configuration.
var secondarySources =
sourceRepository == SourceRepositories [0]
? Enumerable.Empty<SourceRepository> ()
: SourceRepositories.Where (r => r != sourceRepository).ToArray ();
// There does not appear to be a way to hook into or override functionality of the
// NuGetPackageManager or PackageResolver classes. In order to mess with package
// resolution, we need to either write a lot of code, proxy the sources, or intercede
// via preview installation actions.
//
// Here we do the latter, though it is not the best general-purpose approach. It works
// fine for replacing one single package that we know a LOT about. If that package's
// dependencies continually changed, we'd be better off with another approach.
var previewInstallActions = await packageManager.PreviewInstallPackageAsync (
project,
package.Identity,
resolutionContext,
projectContext,
sourceRepository,
secondarySources,
cancellationToken);
var installActions = new List<NuGetProjectAction> ();
foreach (var action in previewInstallActions) {
// If the installed package has a dependency on Xamarin.Forms, make sure the version
// that gets installed is our preferred version. Force it to install from the primary
// source repository, because we can't assume that version is available everywhere.
//
// TODO: Consider adding a search or something to see if we can use the specified source
// instead. Could be handy if nuget.org is down or the user is offline and using
// a local repo.
if (action.PackageIdentity.Id == FixedXamarinFormsPackageIdentity.Id)
installActions.Add (NuGetProjectAction.CreateInstallProjectAction (
FixedXamarinFormsPackageIdentity,
SourceRepositories [0],
action.Project));
else
installActions.Add (action);
}
// We follow the modern behavior of .NET Core and do not actually install packages anywhere.
// Instead, we ultimately reference them out of the user's global package cache (by default,
// ~/.nuget/packages). Our NuGetProject implementation simply collects package assembly
// references (and potentially other necessary files) and populates them back into the
// InteractiveInstallationContext.
using (var sourceCacheContext = new SourceCacheContext ())
await packageManager.ExecuteNuGetProjectActionsAsync (
project,
installActions,
projectContext,
sourceCacheContext,
cancellationToken);
// Identify which packages were not already noted as installed, or have been upgraded now
var newlyInstalledPackages = new List<InteractivePackage> ();
foreach (var newPackage in project.InstallationContext.InstalledPackages) {
InteractivePackage finalNewPackage;
var foundInstalledMatch = installedPackages.TryGetValue (
newPackage,
out finalNewPackage);
if (!foundInstalledMatch ||
newPackage.Identity.Version > finalNewPackage.Identity.Version) {
// Make sure we have a reference to a matching explicit InteractivePackage if it
// exists, so that we can persist the original SupportedVersionRange
if (!foundInstalledMatch)
finalNewPackage = PackageIdComparer.Equals (package, newPackage)
? package
: newPackage;
finalNewPackage = newPackage
.WithIsExplicit (finalNewPackage.IsExplicit)
.WithSupportedVersionRange (finalNewPackage.SupportedVersionRange);
newlyInstalledPackages.Add (finalNewPackage);
installedPackages = installedPackages
.Remove (finalNewPackage)
.Add (finalNewPackage);
UpdateInstalledPackages ();
}
}
return newlyInstalledPackages;
}