Skip to content

Commit

Permalink
Merge pull request #539 from GlitchEnzo/smarter-dependencies
Browse files Browse the repository at this point in the history
Added support for better handling of transitive dependencies
  • Loading branch information
popara96 authored Jul 20, 2023
2 parents 780abdc + c2c4b00 commit 6a2fa48
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 140 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/[Bb]in/

*.log
**/.idea
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,20 @@ Click the **View License** to open the license in a web browser.
Click the **Install** to install the package.
Note: If the package is already installed an **Uninstall** button will be displayed which lets you uninstall the package.
If the **Install** button is disabled, it means the package is already imported by Unity.
The **Installed** tabs shows the packages already installed in the current Unity project.
<img alt="Installed Packages Tap" src="docs/screenshots/installed.png" height="500px" />
The **Installed packages** part of the list shows packages directly installed as project dependencies.
The **Implicitly installed packages** part shows packages that are installed as transitive dependencies.
Click the **Uninstall** button to uninstall the package.
When uninstalling an **explicitely** installed package, all of its dependencies that are not a dependency of any other package or the project itself will also be uninstalled.
If **Add as explicit** is clicked on an **implicitly** installed package, it will be moved to the first part of the list and will **not** be automatically uninstalled in a scenario described above.
The **Updates** tab shows the packages currently installed that have updates available on the server.
Expand Down
Binary file modified docs/screenshots/installed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 10 additions & 10 deletions src/NuGetForUnity.Tests/Assets/Tests/Editor/NuGetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void LoadConfigFileTest()
public void InstallJsonTest()
{
// install a specific version
var json608 = new NugetPackageIdentifier("Newtonsoft.Json", "6.0.8");
var json608 = new NugetPackageIdentifier("Newtonsoft.Json", "6.0.8") { IsManuallyInstalled = true };
NugetHelper.InstallIdentifier(json608);
Assert.IsTrue(NugetHelper.IsInstalled(json608), "The package was NOT installed: {0} {1}", json608.Id, json608.Version);

Expand All @@ -52,7 +52,7 @@ public void InstallJsonTest()
[Test]
public void InstallRoslynAnalyzerTest()
{
var analyzer = new NugetPackageIdentifier("ErrorProne.NET.CoreAnalyzers", "0.1.2");
var analyzer = new NugetPackageIdentifier("ErrorProne.NET.CoreAnalyzers", "0.1.2") {IsManuallyInstalled = true};
if (NugetHelper.NugetConfigFile == null)
{
NugetHelper.LoadNugetConfigFile();
Expand Down Expand Up @@ -95,7 +95,7 @@ public void InstallRoslynAnalyzerTest()
[Test]
public void InstallProtobufTest()
{
var protobuf = new NugetPackageIdentifier("protobuf-net", "2.0.0.668");
var protobuf = new NugetPackageIdentifier("protobuf-net", "2.0.0.668") { IsManuallyInstalled = true };

// install the package
NugetHelper.InstallIdentifier(protobuf);
Expand All @@ -117,7 +117,7 @@ public void InstallBootstrapCSSTest()
// disable the cache for now to force getting the lowest version of the dependency
NugetHelper.NugetConfigFile.InstallFromCache = false;

var bootstrap337 = new NugetPackageIdentifier("bootstrap", "3.3.7");
var bootstrap337 = new NugetPackageIdentifier("bootstrap", "3.3.7") { IsManuallyInstalled = true };

NugetHelper.InstallIdentifier(bootstrap337);
Assert.IsTrue(NugetHelper.IsInstalled(bootstrap337), "The package was NOT installed: {0} {1}", bootstrap337.Id, bootstrap337.Version);
Expand All @@ -128,7 +128,7 @@ public void InstallBootstrapCSSTest()
Assert.IsTrue(NugetHelper.IsInstalled(jQuery191), "The package was NOT installed: {0} {1}", jQuery191.Id, jQuery191.Version);

// now upgrade jQuery to 3.1.1
var jQuery311 = new NugetPackageIdentifier("jQuery", "3.1.1");
var jQuery311 = new NugetPackageIdentifier("jQuery", "3.1.1") { IsManuallyInstalled = true };
NugetHelper.InstallIdentifier(jQuery311);
Assert.IsTrue(NugetHelper.IsInstalled(jQuery311), "The package was NOT installed: {0} {1}", jQuery311.Id, jQuery311.Version);

Expand All @@ -154,7 +154,7 @@ public void InstallBootstrapCSSTest()
[Test]
public void InstallStyleCopTest()
{
var styleCopPlusId = new NugetPackageIdentifier("StyleCopPlus.MSBuild", "4.7.49.5");
var styleCopPlusId = new NugetPackageIdentifier("StyleCopPlus.MSBuild", "4.7.49.5") { IsManuallyInstalled = true };
var styleCopId = new NugetPackageIdentifier("StyleCop.MSBuild", "4.7.49.0");

NugetHelper.InstallIdentifier(styleCopPlusId);
Expand All @@ -174,7 +174,7 @@ public void InstallStyleCopTest()
[Test]
public void InstallSignalRClientTest()
{
var signalRClient = new NugetPackageIdentifier("Microsoft.AspNet.SignalR.Client", "2.2.2");
var signalRClient = new NugetPackageIdentifier("Microsoft.AspNet.SignalR.Client", "2.2.2") { IsManuallyInstalled = true };

NugetHelper.InstallIdentifier(signalRClient);
Assert.IsTrue(NugetHelper.IsInstalled(signalRClient), "The package was NOT installed: {0} {1}", signalRClient.Id, signalRClient.Version);
Expand All @@ -201,7 +201,7 @@ public void InstallSignalRClientTest()
[Test]
public void InstallMicrosoftMlProbabilisticCompilerTest()
{
var probabilisticCompiler = new NugetPackageIdentifier("Microsoft.ML.Probabilistic.Compiler", "0.4.2301.301");
var probabilisticCompiler = new NugetPackageIdentifier("Microsoft.ML.Probabilistic.Compiler", "0.4.2301.301") { IsManuallyInstalled = true };

NugetHelper.InstallIdentifier(probabilisticCompiler);
Assert.IsTrue(
Expand Down Expand Up @@ -415,8 +415,8 @@ public void TestUpgrading()
{
NugetHelper.LoadNugetConfigFile();

var componentModelAnnotation47 = new NugetPackageIdentifier("System.ComponentModel.Annotations", "4.7.0");
var componentModelAnnotation5 = new NugetPackageIdentifier("System.ComponentModel.Annotations", "5.0.0");
var componentModelAnnotation47 = new NugetPackageIdentifier("System.ComponentModel.Annotations", "4.7.0") { IsManuallyInstalled = true };
var componentModelAnnotation5 = new NugetPackageIdentifier("System.ComponentModel.Annotations", "5.0.0") { IsManuallyInstalled = true };

NugetHelper.InstallIdentifier(componentModelAnnotation47);
Assert.IsTrue(
Expand Down
13 changes: 1 addition & 12 deletions src/NuGetForUnity/Editor/DependencyTreeViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,7 @@ private void OnEnable()

private void BuildTree()
{
// default all packages to being roots
roots = new List<NugetPackage>(installedPackages);

// remove a package as a root if another package is dependent on it
foreach (var package in installedPackages)
{
var frameworkGroup = NugetHelper.GetBestDependencyFrameworkGroupForCurrentSettings(package);
foreach (var dependency in frameworkGroup.Dependencies)
{
roots.RemoveAll(p => p.Id == dependency.Id);
}
}
roots = NugetHelper.GetInstalledRootPackages();
}

/// <summary>
Expand Down
110 changes: 90 additions & 20 deletions src/NuGetForUnity/Editor/NugetHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ private static void CleanInstallationDirectory(NugetPackageIdentifier package)
}
}

private static bool IsAlreadyImportedInEngine(NugetPackageIdentifier package, bool log = true)
internal static bool IsAlreadyImportedInEngine(NugetPackageIdentifier package, bool log = true)
{
var alreadyImportedLibs = UnityPreImportedLibraryResolver.GetAlreadyImportedLibs();
var isAlreadyImported = alreadyImportedLibs.Contains(package.Id);
Expand Down Expand Up @@ -722,33 +722,63 @@ internal static void UninstallAll(List<NugetPackage> packagesToUninstall)
{
foreach (var package in packagesToUninstall)
{
Uninstall(package);
Uninstall(package, false);
}

AssetDatabase.Refresh();
}

/// <summary>
/// "Uninstalls" the given package by simply deleting its folder.
/// </summary>
/// <param name="package">The NugetPackage to uninstall.</param>
/// <param name="refreshAssets">True to force Unity to refesh its Assets folder. False to temporarily ignore the change. Defaults to true.</param>
/// <param name="refreshAssets">True to force Unity to refresh its Assets folder. False to temporarily ignore the change. Defaults to true.</param>
public static void Uninstall(NugetPackageIdentifier package, bool refreshAssets = true)
{
LogVerbose("Uninstalling: {0} {1}", package.Id, package.Version);

var foundPackage = package as NugetPackage ?? GetSpecificPackage(package);

// update the package.config file
PackagesConfigFile.RemovePackage(package);
if (!PackagesConfigFile.RemovePackage(foundPackage))
{
return;
}

PackagesConfigFile.Save(PackagesConfigFilePath);

var packageInstallDirectory = Path.Combine(NugetConfigFile.RepositoryPath, $"{package.Id}.{package.Version}");
var packageInstallDirectory = Path.Combine(NugetConfigFile.RepositoryPath, $"{foundPackage.Id}.{foundPackage.Version}");
DeleteDirectory(packageInstallDirectory);

var metaFile = Path.Combine(NugetConfigFile.RepositoryPath, $"{package.Id}.{package.Version}.meta");
var metaFile = Path.Combine(NugetConfigFile.RepositoryPath, $"{foundPackage.Id}.{foundPackage.Version}.meta");
DeleteFile(metaFile);

var toolsInstallDirectory = Path.Combine(AbsoluteProjectPath, "Packages", $"{package.Id}.{package.Version}");
var toolsInstallDirectory = Path.Combine(AbsoluteProjectPath, "Packages", $"{foundPackage.Id}.{foundPackage.Version}");
DeleteDirectory(toolsInstallDirectory);

installedPackages?.Remove(package.Id);
if (installedPackages != null && installedPackages.Count > 0)
{
installedPackages.Remove(foundPackage.Id);

// uninstall all non manually installed dependencies that are not a dependency of another installed package
var frameworkGroup = GetBestDependencyFrameworkGroupForCurrentSettings(foundPackage);
foreach (var dependency in frameworkGroup.Dependencies)
{
var packageConfiguration = PackagesConfigFile.Packages.Find(pkg => pkg.Id == dependency.Id);
if (packageConfiguration == null || packageConfiguration.IsManuallyInstalled)
{
continue;
}

var hasMoreParents = installedPackages.Values.Select(GetBestDependencyFrameworkGroupForCurrentSettings)
.Any(frameworkGrp => frameworkGrp.Dependencies.Any(dep => dep.Id == dependency.Id));

if (!hasMoreParents)
{
Uninstall(dependency, false);
}
}
}

if (refreshAssets)
{
Expand All @@ -766,6 +796,7 @@ public static bool Update(NugetPackageIdentifier currentVersion, NugetPackage ne
{
LogVerbose("Updating {0} {1} to {2}", currentVersion.Id, currentVersion.Version, newVersion.Version);
Uninstall(currentVersion, false);
newVersion.IsManuallyInstalled = newVersion.IsManuallyInstalled || currentVersion.IsManuallyInstalled;
return InstallIdentifier(newVersion, refreshAssets);
}

Expand Down Expand Up @@ -804,6 +835,12 @@ public static void UpdateAll(IEnumerable<NugetPackage> updates, IEnumerable<Nuge
EditorUtility.ClearProgressBar();
}

public static void SetManuallyInstalledFlag(NugetPackageIdentifier package)
{
PackagesConfigFile.SetManuallyInstalledFlag(package);
PackagesConfigFile.Save(PackagesConfigFilePath);
}

/// <summary>
/// Updates the dictionary of packages that are actually installed in the project based on the files that are currently installed.
/// </summary>
Expand All @@ -826,13 +863,14 @@ public static void UpdateInstalledPackages()
// loops through the packages that are actually installed in the project
if (Directory.Exists(NugetConfigFile.RepositoryPath))
{
// a package that was installed via NuGet will have the .nupkg it came from inside the folder
var nupkgFiles = Directory.GetFiles(NugetConfigFile.RepositoryPath, "*.nupkg", SearchOption.AllDirectories);
foreach (var nupkgFile in nupkgFiles)
var manuallyInstalledPackagesNumber = 0;
void AddPackageToInstalled(NugetPackage package)
{
var package = NugetPackage.FromNupkgFile(nupkgFile);
if (!installedPackages.ContainsKey(package.Id))
{
package.IsManuallyInstalled =
PackagesConfigFile.Packages.Find(pkg => pkg.Id == package.Id)?.IsManuallyInstalled ?? false;
if (package.IsManuallyInstalled) manuallyInstalledPackagesNumber++;
installedPackages.Add(package.Id, package);
}
else
Expand All @@ -841,26 +879,55 @@ public static void UpdateInstalledPackages()
}
}

// a package that was installed via NuGet will have the .nupkg it came from inside the folder
var nupkgFiles = Directory.GetFiles(NugetConfigFile.RepositoryPath, "*.nupkg", SearchOption.AllDirectories);
foreach (var nupkgFile in nupkgFiles)
{
var package = NugetPackage.FromNupkgFile(nupkgFile);
AddPackageToInstalled(package);
}

// if the source code & assets for a package are pulled directly into the project (ex: via a symlink/junction) it should have a .nuspec defining the package
var nuspecFiles = Directory.GetFiles(NugetConfigFile.RepositoryPath, "*.nuspec", SearchOption.AllDirectories);
foreach (var nuspecFile in nuspecFiles)
{
var package = NugetPackage.FromNuspec(NuspecFile.Load(nuspecFile));
if (!installedPackages.ContainsKey(package.Id))
{
installedPackages.Add(package.Id, package);
}
else
AddPackageToInstalled(package);
}

if (manuallyInstalledPackagesNumber == 0)
{
// set root packages as manually installed if none are marked as such
foreach (var rootPackage in GetInstalledRootPackages())
{
Debug.LogErrorFormat("Package is already in installed list: {0}", package.Id);
PackagesConfigFile.SetManuallyInstalledFlag(rootPackage);
}
PackagesConfigFile.Save(PackagesConfigFilePath);
}
}

stopwatch.Stop();
LogVerbose("Getting installed packages took {0} ms", stopwatch.ElapsedMilliseconds);
}

internal static List<NugetPackage> GetInstalledRootPackages()
{
// default all packages to being roots
var roots = new List<NugetPackage>(installedPackages.Values);

// remove a package as a root if another package is dependent on it
foreach (var package in installedPackages.Values)
{
var frameworkGroup = GetBestDependencyFrameworkGroupForCurrentSettings(package);
foreach (var dependency in frameworkGroup.Dependencies)
{
roots.RemoveAll(p => p.Id == dependency.Id);
}
}

return roots;
}

/// <summary>
/// Gets a list of NuGetPackages via the HTTP Search() function defined by NuGet.Server and NuGet Gallery.
/// This allows searching for partial IDs or even the empty string (the default) to list ALL packages.
Expand Down Expand Up @@ -1087,6 +1154,7 @@ internal static bool InstallIdentifier(NugetPackageIdentifier package, bool refr

if (foundPackage != null)
{
foundPackage.IsManuallyInstalled = package.IsManuallyInstalled;
return Install(foundPackage, refreshAssets);
}

Expand Down Expand Up @@ -1178,7 +1246,7 @@ public static bool Install(NugetPackage package, bool refreshAssets = true)
"Can't find a matching dependency group for the NuGet Package {0} {1} that has a TargetFramework supported by the current Unity Scripting Backend. The NuGet Package supports the following TargetFramework's: {2}",
package.Id,
package.Version,
string.Join(", ", package.Dependencies.Select(dependeny => dependeny.TargetFramework)));
string.Join(", ", package.Dependencies.Select(dependency => dependency.TargetFramework)));
}
else if (frameworkGroup != null)
{
Expand Down Expand Up @@ -1251,10 +1319,12 @@ public static bool Install(NugetPackage package, bool refreshAssets = true)
foreach (var entry in zip.Entries)
{
var filePath = Path.Combine(baseDirectory, entry.FullName);

var directory = Path.GetDirectoryName(filePath);
Directory.CreateDirectory(directory);
if (Directory.Exists(filePath))
{
Debug.LogWarning($"The path {filePath} refers to an existing directory. Overwriting it may lead to data loss.");
continue;
}

Expand All @@ -1269,7 +1339,7 @@ public static bool Install(NugetPackage package, bool refreshAssets = true)
}

// copy the .nupkg inside the Unity project
File.Copy(cachedPackagePath, Path.Combine(baseDirectory, string.Format("{0}.{1}.nupkg", package.Id, package.Version)), true);
File.Copy(cachedPackagePath, Path.Combine(baseDirectory, $"{package.Id}.{package.Version}.nupkg"), true);
}
else
{
Expand Down
Loading

0 comments on commit 6a2fa48

Please sign in to comment.