Skip to content

Commit

Permalink
Allow setting the path to registry.json file
Browse files Browse the repository at this point in the history
  • Loading branch information
bdovaz committed Dec 10, 2024
1 parent 18fe0c3 commit 0521dc0
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 57 deletions.
5 changes: 3 additions & 2 deletions examples/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ services:
- Registry:UnityScope=org.custom # Packages prefix, default is "org.nuget" but it can be modified to be able to have several containers with different prefixes and to be able to add several scope registries.
- Registry:MinimumUnityVersion=2020.1 # Minimum version of Unity required to install packages, default is "2019.1".
- Registry:PackageNameNuGetPostFix= (Custom NuGet) # Suffix of the package title, useful in case of having several containers and several scope registries, default is " (NuGet)".
- Registry:RootPersistentFolder=custom_unity_packages # Path to the folder where the packages cache will be stored, default is "unity_packages".
- Registry:RegistryFilePath=custom_registry.json # Path to the file (relative or absolute) where the packages registry file will be stored, default is "registry.json".
- Registry:RootPersistentFolder=custom_unity_packages # Path to the folder (relative or absolute) where the packages cache will be stored, default is "unity_packages".
- Registry:UpdateInterval=00:01:00 # Packages update interval, default is "00:10:00" (10 minutes).
- Logging:LogLevel:Default=Information
ports:
- 5000:8080
volumes:
- ./registry.json:/app/custom_registry.json # Override the package registry to be able to add or remove packages.
- ./unity_packages:/app/custom_unity_packages # Map the folder with the packages cache.
- ./registry.json:/app/registry.json # Override the package registry to be able to add or remove packages.
- ./NuGet.Config:/root/.nuget/NuGet/NuGet.Config # Override Nuget.config file with repository information. This file can be used to configure a custom NuGet repository: https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file
2 changes: 2 additions & 0 deletions src/UnityNuGet.Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

var builder = WebApplication.CreateBuilder(args);

// Add the registry
builder.Services.AddHostedService<Registry>();
// Add the registry cache initializer
builder.Services.AddHostedService<RegistryCacheInitializer>();
// Add the registry cache updater
Expand Down
7 changes: 4 additions & 3 deletions src/UnityNuGet.Server/RegistryCacheUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ namespace UnityNuGet.Server
/// <summary>
/// Update the RegistryCache at a regular interval
/// </summary>
internal sealed class RegistryCacheUpdater(RegistryCacheReport registryCacheReport, RegistryCacheSingleton currentRegistryCache, ILogger<RegistryCacheUpdater> logger, IOptions<RegistryOptions> registryOptionsAccessor) : BackgroundService
{
internal sealed class RegistryCacheUpdater(Registry registry, RegistryCacheReport registryCacheReport, RegistryCacheSingleton currentRegistryCache, ILogger<RegistryCacheUpdater> logger, IOptions<RegistryOptions> registryOptionsAccessor) : BackgroundService
{
private readonly Registry registry = registry;
private readonly RegistryCacheReport _registryCacheReport = registryCacheReport;
private readonly RegistryCacheSingleton _currentRegistryCache = currentRegistryCache;
private readonly ILogger _logger = logger;
Expand All @@ -28,7 +29,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)

_registryCacheReport.Start();

var newRegistryCache = new RegistryCache(_currentRegistryCache.UnityPackageFolder!, _currentRegistryCache.ServerUri!, _registryOptions.UnityScope!, _registryOptions.MinimumUnityVersion!, _registryOptions.PackageNameNuGetPostFix!, _registryOptions.TargetFrameworks!, _currentRegistryCache.NuGetRedirectLogger!)
var newRegistryCache = new RegistryCache(registry, _currentRegistryCache.UnityPackageFolder!, _currentRegistryCache.ServerUri!, _registryOptions.UnityScope!, _registryOptions.MinimumUnityVersion!, _registryOptions.PackageNameNuGetPostFix!, _registryOptions.TargetFrameworks!, _currentRegistryCache.NuGetRedirectLogger!)
{
// Update progress
OnProgress = (current, total) =>
Expand Down
17 changes: 9 additions & 8 deletions src/UnityNuGet.Server/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
"UnityScope": "org.nuget",
"MinimumUnityVersion": "2019.1",
"PackageNameNuGetPostFix": " (NuGet)",
"RegistryFilePath": "registry.json",
"RootPersistentFolder": "unity_packages",
"UpdateInterval": "00:10:00",
"TargetFrameworks": [
{
"Name": "netstandard2.1",
"DefineConstraints": [ "UNITY_2021_2_OR_NEWER" ]
},
{
"Name": "netstandard2.0",
"DefineConstraints": [ "!UNITY_2021_2_OR_NEWER" ]
}
{
"Name": "netstandard2.1",
"DefineConstraints": [ "UNITY_2021_2_OR_NEWER" ]
},
{
"Name": "netstandard2.0",
"DefineConstraints": [ "!UNITY_2021_2_OR_NEWER" ]
}
]
},
"Logging": {
Expand Down
9 changes: 8 additions & 1 deletion src/UnityNuGet.Tests/NativeTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using NUnit.Framework;

namespace UnityNuGet.Tests
Expand All @@ -15,8 +17,13 @@ public async Task TestBuild()
Directory.Delete(unityPackages, true);

var errorsTriggered = false;

var registry = new Registry(Options.Create(new RegistryOptions { RegistryFilePath = "registry.json" }));

await registry.StartAsync(CancellationToken.None);

var registryCache = new RegistryCache(
var registryCache = new RegistryCache(
registry,
unityPackages,
new Uri("http://localhost/"),
"org.nuget",
Expand Down
7 changes: 7 additions & 0 deletions src/UnityNuGet.Tests/RegistryCacheTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using NUnit.Framework;

namespace UnityNuGet.Tests
Expand All @@ -13,7 +15,12 @@ public async Task TestBuild()
var errorsTriggered = false;

var unityPackages = Path.Combine(Path.GetDirectoryName(typeof(RegistryCacheTests).Assembly.Location)!, "unity_packages");
var registry = new Registry(Options.Create(new RegistryOptions { RegistryFilePath = "registry.json" }));

await registry.StartAsync(CancellationToken.None);

var registryCache = new RegistryCache(
registry,
unityPackages,
new Uri("http://localhost/"),
"org.nuget",
Expand Down
34 changes: 23 additions & 11 deletions src/UnityNuGet.Tests/RegistryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using NuGet.Configuration;
using NuGet.PackageManagement;
using NuGet.Packaging.Core;
Expand All @@ -17,20 +18,28 @@ namespace UnityNuGet.Tests
{
public class RegistryTests
{
private readonly RegistryOptions registryOptions = new RegistryOptions { RegistryFilePath = "registry.json" };

[Test]
public void Make_Sure_That_The_Order_In_The_Registry_Is_Respected()
public async Task Make_Sure_That_The_Order_In_The_Registry_Is_Respected()
{
var registry = Registry.GetInstance();
var registry = new Registry(Options.Create(registryOptions));

await registry.StartAsync(CancellationToken.None);

var originalPackageNames = registry.Select(r => r.Key).ToArray();
var sortedPackageNames = originalPackageNames.OrderBy(p => p).ToArray();

Assert.That(originalPackageNames, Is.EqualTo(sortedPackageNames));
}

[Test]
public void Ensure_That_Packages_Already_Included_In_Net_Standard_Are_not_Included_In_The_Registry()
public async Task Ensure_That_Packages_Already_Included_In_Net_Standard_Are_not_Included_In_The_Registry()
{
var registry = Registry.GetInstance();
var registry = new Registry(Options.Create(registryOptions));

await registry.StartAsync(CancellationToken.None);

var packageNames = registry.Select(r => r.Key).Where(DotNetHelper.IsNetStandard20Assembly).ToArray();

Assert.That(packageNames, Is.Empty);
Expand All @@ -48,7 +57,7 @@ public async Task CanParse_PackageWithRuntimes()

// Fetch a package that has runtime overrides as described here: https://learn.microsoft.com/en-us/nuget/create-packages/supporting-multiple-target-frameworks
var downloadResult = await PackageDownloader.GetDownloadResourceResultAsync(
new SourceRepository[] { repository },
[repository],
new PackageIdentity("System.Security.Cryptography.ProtectedData", new NuGet.Versioning.NuGetVersion(6, 0, 0)),
new PackageDownloadContext(cache),
SettingsUtility.GetGlobalPackagesFolder(settings),
Expand All @@ -72,13 +81,12 @@ public async Task CanParse_PackageWithRuntimes()
var versions = await downloadResult.PackageReader.GetLibItemsAsync(cancellationToken);
var closestVersions = NuGetHelper.GetClosestFrameworkSpecificGroups(
versions,
new RegistryTargetFramework[]
{
[
new()
{
Framework = CommonFrameworks.NetStandard20,
},
});
]);
var libFiles = closestVersions
.Single()
.Item1.Items
Expand All @@ -95,11 +103,13 @@ public async Task CanParse_PackageWithRuntimes()
[Test]
public async Task Ensure_Min_Version_Is_Correct_Ignoring_Analyzers_And_Native_Libs()
{
var registry = Registry.GetInstance();
var registry = new Registry(Options.Create(registryOptions));

var logger = new NuGetConsoleTestLogger();
var cancellationToken = CancellationToken.None;

await registry.StartAsync(cancellationToken);

var cache = new SourceCacheContext();
var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
var resource = await repository.GetResourceAsync<PackageMetadataResource>();
Expand Down Expand Up @@ -185,7 +195,7 @@ public async Task Ensure_Min_Version_Is_Correct_Ignoring_Analyzers_And_Native_Li
var settings = Settings.LoadDefaultSettings(root: null);

var downloadResult = await PackageDownloader.GetDownloadResourceResultAsync(
new SourceRepository[] { repository },
[repository],
new PackageIdentity(registryKvp.Key, registryKvp.Value.Version!.MinVersion),
new PackageDownloadContext(cache),
SettingsUtility.GetGlobalPackagesFolder(settings),
Expand All @@ -210,11 +220,13 @@ public async Task Ensure_Do_Not_Exceed_The_Maximum_Number_Of_Allowed_Versions()
{
const int maxAllowedVersions = 100;

var registry = Registry.GetInstance();
var registry = new Registry(Options.Create(registryOptions));

var logger = new NuGetConsoleTestLogger();
var cancellationToken = CancellationToken.None;

await registry.StartAsync(cancellationToken);

var cache = new SourceCacheContext();
var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
var resource = await repository.GetResourceAsync<PackageMetadataResource>();
Expand Down
65 changes: 37 additions & 28 deletions src/UnityNuGet/Registry.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;

namespace UnityNuGet
{
/// <summary>
/// Loads the `registry.json` file at startup
/// </summary>
[Serializable]
public sealed class Registry : Dictionary<string, RegistryEntry>
{
private const string RegistryFileName = "registry.json";
private static readonly Lock LockRead = new();
private static Registry? _registry = null;

// A comparer is established for cases where the dependency name is not set to the correct case.
// Example: https://www.nuget.org/packages/NeoSmart.Caching.Sqlite/0.1.0#dependencies-body-tab
public Registry() : base(StringComparer.OrdinalIgnoreCase)
{
}

public static Registry Parse(string json)
{
ArgumentNullException.ThrowIfNull(json);
return JsonConvert.DeserializeObject<Registry>(json, JsonCommonExtensions.Settings)!;
}

public static Registry GetInstance()
{
lock (LockRead)
{
_registry ??= Parse(File.ReadAllText(Path.Combine(Path.GetDirectoryName(AppContext.BaseDirectory)!, RegistryFileName)));
}
return _registry;
}
public sealed class Registry(IOptions<RegistryOptions> registryOptionsAccessor) : IHostedService, IReadOnlyCollection<KeyValuePair<string, RegistryEntry>>, IEnumerable<KeyValuePair<string, RegistryEntry>>
{
private IDictionary<string, RegistryEntry>? _data;

private readonly RegistryOptions registryOptions = registryOptionsAccessor.Value;

public int Count => _data!.Count;

IEnumerator IEnumerable.GetEnumerator() => _data!.GetEnumerator();

public IEnumerator<KeyValuePair<string, RegistryEntry>> GetEnumerator() => _data!.GetEnumerator();

public bool TryGetValue(string key, out RegistryEntry value) => _data!.TryGetValue(key, out value!);

public async Task StartAsync(CancellationToken cancellationToken)
{
string registryFilePath;

if (Path.IsPathRooted(registryOptions.RegistryFilePath))
{
registryFilePath = registryOptions.RegistryFilePath;
}
else
{
registryFilePath = Path.Combine(Directory.GetCurrentDirectory(), registryOptions.RegistryFilePath!);
}

string json = await File.ReadAllTextAsync(registryFilePath, cancellationToken);

_data = JsonConvert.DeserializeObject<IDictionary<string, RegistryEntry>>(json, JsonCommonExtensions.Settings)!;
}

public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}
8 changes: 4 additions & 4 deletions src/UnityNuGet/RegistryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class RegistryCache
private const string CurrentRegistryVersion = "1.8.0";

private static readonly Encoding Utf8EncodingNoBom = new UTF8Encoding(false, false);
private readonly Registry _registry;
private readonly string _rootPersistentFolder;
private readonly Uri _rootHttpUri;
private readonly string _unityScope;
Expand All @@ -46,16 +47,16 @@ public class RegistryCache
private readonly ISettings _settings;
private readonly IEnumerable<SourceRepository> _sourceRepositories;
private readonly SourceCacheContext _sourceCacheContext;
private readonly Registry _registry;
private readonly NpmPackageRegistry _npmPackageRegistry;

public RegistryCache(RegistryCache registryCache) : this(registryCache._rootPersistentFolder, registryCache._rootHttpUri, registryCache._unityScope,
public RegistryCache(Registry registry, RegistryCache registryCache) : this(registry, registryCache._rootPersistentFolder, registryCache._rootHttpUri, registryCache._unityScope,
registryCache._minimumUnityVersion, registryCache._packageNameNuGetPostFix, registryCache._targetFrameworks, registryCache._logger)
{ }

public RegistryCache(string rootPersistentFolder, Uri rootHttpUri, string unityScope, string minimumUnityVersion,
public RegistryCache(Registry registry, string rootPersistentFolder, Uri rootHttpUri, string unityScope, string minimumUnityVersion,
string packageNameNuGetPostFix, RegistryTargetFramework[] targetFrameworks, ILogger logger)
{
_registry = registry;
_rootPersistentFolder = rootPersistentFolder ?? throw new ArgumentNullException(nameof(rootPersistentFolder));
_rootHttpUri = rootHttpUri ?? throw new ArgumentNullException(nameof(rootHttpUri));
_unityScope = unityScope ?? throw new ArgumentNullException(nameof(unityScope));
Expand All @@ -79,7 +80,6 @@ public RegistryCache(string rootPersistentFolder, Uri rootHttpUri, string unityS
var sourceRepositoryProvider = new SourceRepositoryProvider(new PackageSourceProvider(_settings), Repository.Provider.GetCoreV3());
_sourceRepositories = sourceRepositoryProvider.GetRepositories();
_logger = logger;
_registry = Registry.GetInstance();

// Initialize target framework
foreach (var registryTargetFramework in _targetFrameworks)
Expand Down
3 changes: 3 additions & 0 deletions src/UnityNuGet/RegistryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public class RegistryOptions
[Required]
public string? PackageNameNuGetPostFix { get; set; }

[Required]
public string? RegistryFilePath { get; set; }

[Required]
public string? RootPersistentFolder { get; set; }

Expand Down
12 changes: 12 additions & 0 deletions src/nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>

0 comments on commit 0521dc0

Please sign in to comment.