Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,16 @@ Note: Depending on the size and number of packages you need to install, the `Res

If you are interested in the process NuGetForUnity follows or you are trying to debug an issue, you can force NuGetForUnity to use verbose logging to output an increased amount of data to the Unity console. Either check the `Use Verbose Logging` checkbox in the `NuGet For Unity` settings window or add the line `<add key="verbose" value="true" />` to the `<config>` element in the _NuGet.config_ file. You can disable verbose logging by either setting the value to false or completely deleting the line.

## Ignored Dependencies

If a dependency is already provided by another Unity plugin that is installed outside of NuGetForUnity, add its package ID to `Ignored Dependencies` in the `NuGet For Unity` settings window. The same setting can be configured manually in _NuGet.config_:

```xml
<add key="ignoredPackageDependencies" value="Package.Id;Another.Package.Id" />
```

NuGetForUnity skips these package IDs only when they are pulled transitively as dependencies. Existing non-manually-installed entries for ignored dependencies are removed from _packages.config_ during restore or package cleanup. Explicitly installing the same package still works.

## Caching

The _.nupkg_ files downloaded from the NuGet server are cached locally in the current user's Application Data folder `%localappdata%\NuGet\Cache` (Windows: `C:\Users\[username]\AppData\Local\NuGet\Cache`, Mac/Linux: `~/.local/share/NuGet/Cache`). The cache location can be overwritten by setting the `NuGetCachePath` environment variable. Packages previously installed are installed via the cache folder instead of downloading it from the server again.
Expand Down
57 changes: 57 additions & 0 deletions src/NuGetForUnity.Tests/Assets/Tests/Editor/NuGetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,38 @@ public void InstallStyleCopTest([Values] InstallMode installMode)
Assert.That(InstalledPackagesManager.InstalledPackages, Is.EquivalentTo(new[] { styleCopPlusId, styleCopId }));
}

[Test]
public void InstallStyleCopWithIgnoredDependencyTest([Values] InstallMode installMode)
{
ConfigureNugetConfig(installMode);

var oldIgnoredPackageDependencies = ConfigurationManager.NugetConfigFile.IgnoredPackageDependenciesText;
try
{
ConfigurationManager.NugetConfigFile.IgnoredPackageDependenciesText = "stylecop.msbuild";

var styleCopPlusId = new NugetPackageIdentifier("StyleCopPlus.MSBuild", "4.7.49.5") { IsManuallyInstalled = true };
var styleCopId = new NugetPackageIdentifier("StyleCop.MSBuild", "4.7.49.0");

NugetPackageInstaller.InstallIdentifier(styleCopPlusId);

Assert.That(InstalledPackagesManager.InstalledPackages, Does.Contain(styleCopPlusId));
Assert.That(InstalledPackagesManager.InstalledPackages, Does.Not.Contain(styleCopId));
Assert.That(InstalledPackagesManager.PackagesConfigFile.Packages, Does.Not.Contain(styleCopId));

InstalledPackagesManager.PackagesConfigFile.Packages.Add(new PackageConfig { Id = styleCopId.Id, Version = styleCopId.Version });
PackageRestorer.Restore(false);

Assert.That(InstalledPackagesManager.InstalledPackages, Does.Contain(styleCopPlusId));
Assert.That(InstalledPackagesManager.InstalledPackages, Does.Not.Contain(styleCopId));
Assert.That(InstalledPackagesManager.PackagesConfigFile.Packages, Does.Not.Contain(styleCopId));
}
finally
{
ConfigurationManager.NugetConfigFile.IgnoredPackageDependenciesText = oldIgnoredPackageDependencies;
}
}

[Test]
public void InstallStyleCopWithoutDependenciesTest()
{
Expand Down Expand Up @@ -722,6 +754,31 @@ public void PackageSourceCredentialsTest(string name)
Assert.That(parsedSource.SavedPassword, Is.EqualTo(password));
}

[Test]
public void IgnoredPackageDependenciesConfigRoundTrips()
{
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}-{NugetConfigFile.FileName}");
try
{
var file = NugetConfigFile.CreateDefaultFile(path);
file.IgnoredPackageDependenciesText = "StyleCop.MSBuild; stylecop.msbuild, Newtonsoft.Json";
file.Save(path);

var loaded = NugetConfigFile.Load(path);

Assert.That(loaded.IgnoredPackageDependencies, Is.EqualTo(new[] { "StyleCop.MSBuild", "Newtonsoft.Json" }));
Assert.That(loaded.IsPackageDependencyIgnored("stylecop.msbuild"), Is.True);
Assert.That(loaded.IsPackageDependencyIgnored("NUnit"), Is.False);
}
finally
{
if (File.Exists(path))
{
File.Delete(path);
}
}
}

[Test]
[TestCase("2018.4.30f1", false, false, false)]
[TestCase("2018.4.30f1", true, false, false)]
Expand Down
120 changes: 120 additions & 0 deletions src/NuGetForUnity/Editor/Configuration/NugetConfigFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public class NugetConfigFile

private const string KeepingPdbFilesConfigKey = "KeepingPdbFiles";

private const string IgnoredPackageDependenciesConfigKey = "ignoredPackageDependencies";

private const string ProtocolVersionAttributeName = "protocolVersion";

private const string EnableCredentialProviderAttributeName = "enableCredentialProvider";
Expand All @@ -66,6 +68,8 @@ public class NugetConfigFile

private const string SupportsPackageIdSearchFilterAttributeName = "supportsPackageIdSearchFilter";

private static readonly char[] IgnoredPackageDependencySeparators = { ';', ',', '\r', '\n' };

[NotNull]
private readonly string unityPackagesNugetInstallPath = Path.Combine(UnityPathHelper.AbsoluteUnityPackagesNugetPath, "InstalledPackages");

Expand Down Expand Up @@ -197,6 +201,12 @@ public string RelativePackagesConfigDirectoryPath
/// </summary>
public bool PreferNetStandardOverNetFramework { get; set; }

/// <summary>
/// Gets the package dependency IDs that should not be installed when pulled transitively by another package.
/// </summary>
[NotNull]
internal List<string> IgnoredPackageDependencies { get; } = new List<string>();

/// <summary>
/// Gets or sets a value indicating whether PDB files included in NuGet packages should not be deleted if they can be read by Unity.
/// </summary>
Expand All @@ -212,6 +222,17 @@ public string RelativePackagesConfigDirectoryPath
/// </summary>
internal List<NugetForUnityPluginId> EnabledPlugins { get; } = new List<NugetForUnityPluginId>();

/// <summary>
/// Gets or sets the ignored package dependency IDs as editable text.
/// </summary>
[NotNull]
internal string IgnoredPackageDependenciesText
{
get => string.Join("; ", IgnoredPackageDependencies);

set => SetIgnoredPackageDependencies(value);
}

/// <summary>
/// Loads a NuGet.config file at the given file-path.
/// </summary>
Expand Down Expand Up @@ -382,6 +403,10 @@ public static NugetConfigFile Load([NotNull] string filePath)
{
configFile.KeepingPdbFiles = bool.Parse(value);
}
else if (string.Equals(key, IgnoredPackageDependenciesConfigKey, StringComparison.OrdinalIgnoreCase))
{
configFile.SetIgnoredPackageDependencies(value);
}
}

return configFile;
Expand Down Expand Up @@ -426,6 +451,8 @@ public static NugetConfigFile CreateDefaultFile([NotNull] string filePath)
[SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "We intentionally use lower case.")]
public void Save([NotNull] string filePath)
{
NormalizeIgnoredPackageDependencies();

var configFile = new XDocument();

var packageSources = new XElement("packageSources");
Expand Down Expand Up @@ -587,6 +614,14 @@ public void Save([NotNull] string filePath)
config.Add(addElement);
}

if (IgnoredPackageDependencies.Count > 0)
{
addElement = new XElement("add");
addElement.Add(new XAttribute("key", IgnoredPackageDependenciesConfigKey));
addElement.Add(new XAttribute("value", string.Join(";", IgnoredPackageDependencies)));
config.Add(addElement);
}

var configuration = new XElement("configuration");
configuration.Add(packageSources);
configuration.Add(disabledPackageSources);
Expand Down Expand Up @@ -627,6 +662,46 @@ public void Save([NotNull] string filePath)
configFile.Save(filePath);
}

/// <summary>
/// Checks if the given package ID is configured to be ignored when it is pulled as a dependency.
/// </summary>
/// <param name="packageId">The dependency package ID.</param>
/// <returns>True if the dependency should be skipped.</returns>
internal bool IsPackageDependencyIgnored([NotNull] string packageId)
{
if (string.IsNullOrWhiteSpace(packageId))
{
return false;
}

var normalizedPackageId = packageId.Trim();
return IgnoredPackageDependencies.Exists(
ignoredPackageId => string.Equals(ignoredPackageId, normalizedPackageId, StringComparison.OrdinalIgnoreCase));
}

/// <summary>
/// Adds the given package ID to the dependency ignore list.
/// </summary>
/// <param name="packageId">The dependency package ID to ignore.</param>
/// <returns>True if the package ID was added, false if it was already present or empty.</returns>
internal bool AddIgnoredPackageDependency([NotNull] string packageId)
{
if (string.IsNullOrWhiteSpace(packageId))
{
return false;
}

var normalizedPackageId = packageId.Trim();
if (IgnoredPackageDependencies.Exists(
ignoredPackageId => string.Equals(ignoredPackageId, normalizedPackageId, StringComparison.OrdinalIgnoreCase)))
{
return false;
}

IgnoredPackageDependencies.Add(normalizedPackageId);
return true;
}

/// <summary>
/// Changes the package install location config and also moves the packages.config to the new location.
/// </summary>
Expand Down Expand Up @@ -673,6 +748,28 @@ internal void FillMissingPackageCredentialsFromExternalFile([NotNull] string fil
}
}

[NotNull]
private static List<string> ParseIgnoredPackageDependencies([CanBeNull] string value)
{
if (string.IsNullOrEmpty(value))
{
return new List<string>();
}

var packageIds = new List<string>();
foreach (var packageId in value.Split(IgnoredPackageDependencySeparators, StringSplitOptions.RemoveEmptyEntries))
{
var trimmedPackageId = packageId.Trim();
if (!string.IsNullOrEmpty(trimmedPackageId) &&
!packageIds.Exists(existingPackageId => string.Equals(existingPackageId, trimmedPackageId, StringComparison.OrdinalIgnoreCase)))
{
packageIds.Add(trimmedPackageId);
}
}

return packageIds;
}

private void FillPackageSourceCredentialsFromConfig(XDocument file, bool overwriteMissingFromExternal)
{
var packageSourceCredentials = file.Root?.Element("packageSourceCredentials");
Expand Down Expand Up @@ -715,5 +812,28 @@ private void FillPackageSourceCredentialsFromConfig(XDocument file, bool overwri
}
}
}

private void SetIgnoredPackageDependencies([CanBeNull] string value)
{
IgnoredPackageDependencies.Clear();
IgnoredPackageDependencies.AddRange(ParseIgnoredPackageDependencies(value));
}

private void NormalizeIgnoredPackageDependencies()
{
var uniquePackageIds = new List<string>();
foreach (var packageId in IgnoredPackageDependencies)
{
var trimmedPackageId = packageId?.Trim();
if (!string.IsNullOrEmpty(trimmedPackageId) &&
!uniquePackageIds.Exists(existingPackageId => string.Equals(existingPackageId, trimmedPackageId, StringComparison.OrdinalIgnoreCase)))
{
uniquePackageIds.Add(trimmedPackageId);
}
}

IgnoredPackageDependencies.Clear();
IgnoredPackageDependencies.AddRange(uniquePackageIds);
}
}
}
36 changes: 31 additions & 5 deletions src/NuGetForUnity/Editor/InstalledPackagesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,20 +246,25 @@ internal static void UpdateInstalledPackages()
}

/// <summary>
/// Finds and removes any installed packages detected that are not listed inside the packages.config.
/// Finds and removes any installed packages detected that are not listed inside the packages.config, and removes ignored implicit entries from
/// packages.config.
/// </summary>
/// <returns>True if some packages are deleted.</returns>
/// <returns>True if some packages or packages.config entries are deleted.</returns>
internal static bool RemoveUnnecessaryPackages()
{
var somethingDeleted = RemoveIgnoredImplicitPackagesFromConfig();

if (!Directory.Exists(ConfigurationManager.NugetConfigFile.RepositoryPath))
{
return false;
return somethingDeleted;
}

var somethingDeleted = false;
foreach (var installedPackage in InstalledPackages)
{
var shouldBeInstalled = PackagesConfigFile.Packages.Exists(packageId => packageId.Equals(installedPackage));
var packageConfig = PackagesConfigFile.Packages.Find(packageId => packageId.Equals(installedPackage));
var shouldBeInstalled = packageConfig != null &&
(packageConfig.IsManuallyInstalled ||
!ConfigurationManager.NugetConfigFile.IsPackageDependencyIgnored(packageConfig.Id));

if (!shouldBeInstalled)
{
Expand Down Expand Up @@ -341,6 +346,27 @@ internal static List<INugetPackage> GetInstalledRootPackages()
return roots;
}

private static bool RemoveIgnoredImplicitPackagesFromConfig()
{
var ignoredImplicitPackages = PackagesConfigFile.Packages
.Where(package => !package.IsManuallyInstalled && ConfigurationManager.NugetConfigFile.IsPackageDependencyIgnored(package.Id))
.ToList();

foreach (var ignoredImplicitPackage in ignoredImplicitPackages)
{
NugetLogger.LogVerbose("---REMOVE ignored dependency from packages.config {0}", ignoredImplicitPackage.Id);
PackagesConfigFile.RemovePackage(ignoredImplicitPackage);
}

if (ignoredImplicitPackages.Count <= 0)
{
return false;
}

PackagesConfigFile.Save();
return true;
}

private static void AddPackageToInstalledInternal([NotNull] NugetPackageLocal package, ref int manuallyInstalledPackagesNumber)
{
var packages = InstalledPackagesDictionary;
Expand Down
6 changes: 6 additions & 0 deletions src/NuGetForUnity/Editor/NugetPackageInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ private static PackageInstallOperationResult Install(
NugetLogger.LogVerbose("Installing dependencies for TargetFramework: {0}", frameworkGroup.TargetFramework);
foreach (var dependency in frameworkGroup.Dependencies)
{
if (ConfigurationManager.NugetConfigFile.IsPackageDependencyIgnored(dependency.Id))
{
NugetLogger.LogVerbose("Skipping ignored dependency: {0} {1}", dependency.Id, dependency.Version);
continue;
}

NugetLogger.LogVerbose("Installing Dependency: {0} {1}", dependency.Id, dependency.Version);
var dependencyResult = Install(dependency, false, false, false);
result.Combine(dependencyResult);
Expand Down
14 changes: 13 additions & 1 deletion src/NuGetForUnity/Editor/PackageRestorer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using NugetForUnity.Configuration;
using NugetForUnity.Helper;
using UnityEditor;
using Debug = UnityEngine.Debug;
Expand All @@ -26,7 +27,7 @@ public static void Restore(bool slimRestore)
{
var packagesToInstall =
InstalledPackagesManager.PackagesConfigFile.Packages.FindAll(
package => !InstalledPackagesManager.IsInstalled(package, !slimRestore));
package => ShouldRestorePackage(package, !slimRestore));
if (packagesToInstall.Count > 0)
{
var progressStep = 1.0f / packagesToInstall.Count;
Expand Down Expand Up @@ -69,5 +70,16 @@ public static void Restore(bool slimRestore)
EditorUtility.ClearProgressBar();
}
}

private static bool ShouldRestorePackage(PackageConfig package, bool checkIsAlreadyImportedInEngine)
{
if (!package.IsManuallyInstalled && ConfigurationManager.NugetConfigFile.IsPackageDependencyIgnored(package.Id))
{
NugetLogger.LogVerbose("Skipping ignored dependency from packages.config: {0} {1}", package.Id, package.Version);
return false;
}

return !InstalledPackagesManager.IsInstalled(package, checkIsAlreadyImportedInEngine);
}
}
}
Loading
Loading