#! /usr/bin/env dotnet #:property Nullable=enable #:property PublishAOT=false #:package ArcaneLibs@1.0.1-preview.2026* using System.Diagnostics; using ArcaneLibs; using System.Text.Json; #region Sync package versions for CDN worker { Console.WriteLine("==> Ensuring CDN worker dependencies are in sync..."); var origContent = await File.ReadAllTextAsync("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj"); var depToReplace = "Magick.NET-Q16-HDRI-OpenMP-x64"; (string Project, string Dependency)[] replaceTargets = [ // ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj", "Magick.NET-Q16-HDRI-OpenMP-x64"), // source ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj", "Magick.NET-Q16-OpenMP-x64"), ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj", "Magick.NET-Q8-OpenMP-x64"), ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj", "Magick.NET-Q16-HDRI-OpenMP-arm64"), ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj", "Magick.NET-Q16-OpenMP-arm64"), ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj", "Magick.NET-Q8-OpenMP-arm64"), ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj", "Magick.NET-Q16-HDRI-AnyCPU"), ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj", "Magick.NET-Q16-AnyCPU"), ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj", "Magick.NET-Q8-AnyCPU"), ]; foreach (var target in replaceTargets) { Console.WriteLine($" ==> {target.Project} -> {target.Dependency}"); await File.WriteAllTextAsync(target.Project, origContent.Replace(depToReplace, target.Dependency)); } } #endregion Console.WriteLine("==> Getting outputs..."); var outNames = JsonSerializer .Deserialize(Util.GetCommandOutputSync("nix", $"eval --json .#packages.x86_64-linux --apply builtins.attrNames", silent: true, stderr: false))! .Where(o => args.Length == 0 || args.Select(x => x.Replace('.', '-')).Any(o.Contains)).ToArray(); Console.WriteLine($"==> Updating dependencies for {outNames.Length} projects..."); #region Dependency resolution var sortSw = Stopwatch.StartNew(); Console.WriteLine($"==> Sorting projects by dependencies..."); Console.WriteLine($" ==> Getting project references..."); (string Name, string[] References)[] byName = await Task.WhenAll(outNames .Select(async x => { await Task.Delay(Random.Shared.Next(outNames.Length)); var refJson = Util.GetCommandOutputSync("nix", $"eval --json .#packages.x86_64-linux.{x}.passthru.__sbDmProjectRefs.names", silent: true, stderr: false); var references = string.IsNullOrEmpty(refJson) ? [] : JsonSerializer.Deserialize(refJson)!.Select(x => x.Replace('.', '-')).ToArray(); if (references.Length > 0) Console.WriteLine($" ==> Got {references.Length} project references for {x}"); // Console.WriteLine($" ==> Got {references.Length} project references for {x}: {string.Join(", ", references)}"); return (x, references); }).ToList()); Console.WriteLine($" ==> Mapping to tree nodes..."); // create nodes first var deps = byName.Select(x => new ProjectDependencyNode() { Name = x.Name, References = [] }).ToArray(); // actually fill in references foreach (var d in deps) { d.References = deps.Where(x => byName.First(bn => bn.Name == d.Name).References.Contains(x.Name)).ToArray(); Console.WriteLine($" ==> {d.Name} => {string.Join(", ", d.References.Select(x => x.GetShortName()))}"); } Console.WriteLine($" ==> Sorting..."); deps = deps.OrderBy(x => x.GetDepth()).ThenBy(x => x.GetWeight()).ThenBy(x => x.Name).ToArray(); foreach (var d in deps) { // just a nice thing when debugging d.References = d.References.OrderBy(x => x.GetDepth()).ThenBy(x => x.GetWeight()).ThenBy(x => x.Name).ToArray(); Console.WriteLine($" ==> {d.GetNameWithWeights()} => {string.Join(", ", d.References.Select(x => $"{x.GetNameWithWeights(true)}"))}"); } Console.WriteLine($" ==> Sorted dependency tree with {deps.Length} projects in {sortSw.Elapsed}"); #endregion var maxNameLength = deps.Max(x => x.Name.Length); foreach (var depthGroup in deps.GroupBy(x => x.GetDepth())) { var tasks = depthGroup.Index().Select(indexedOutpEnt => Task.Run(async () => { var (idx, outpEnt) = indexedOutpEnt; var outp = outpEnt.Name; var prefix = ConsoleUtils.ColoredString( $"{outpEnt.GetDepth():00}.{idx:00}({outpEnt.GetWeight():000}) {outpEnt.Name.PadRight(maxNameLength)}>", (byte)((outp.GetHashCode() >> 16) & 0xff), (byte)((outp.GetHashCode() >> 8) & 0xff), (byte)(outp.GetHashCode() & 0xff) ); Console.WriteLine(prefix + ConsoleUtils.ColoredString($" ==> Updating {outp}...", 0x80, 0x80, 0xff)); var rootDir = outpEnt.GetRelativeRootDir(); var depsFile = outpEnt.GetDepsFile(); if (depsFile is null) { Console.WriteLine(prefix + ConsoleUtils.ColoredString($" ==> No __nugetDeps attribute, skipping!", 0xff, 0x80, 0x80)); return; } Console.WriteLine( prefix + ConsoleUtils.ColoredString($" ==> Got project root directory: ", 0x80, 0xff, 0xff) + ConsoleUtils.ColoredString($"{rootDir}", 0x80, 0xff, 0xff) + " - " + ConsoleUtils.ColoredString($"{depsFile.Value.StorePath.Replace(depsFile.Value.Name, "")}", 0x80, 0x80, 0xff) + ConsoleUtils.ColoredString($"{depsFile.Value.Name}", 0x80, 0xff, 0x80) + (File.Exists(depsFile.Value.LocalPath) ? ConsoleUtils.ColoredString($" (exists)", 0x80, 0xff, 0x80) : ConsoleUtils.ColoredString($" (does not exist)", 0xf, 0x80, 0x80)) ); if (!File.Exists(depsFile.Value.LocalPath)) { Console.WriteLine(prefix + ConsoleUtils.ColoredString($" ==> No NuGet deps file, skipping!", 0xff, 0x80, 0x80)); return; } Console.WriteLine(prefix + ConsoleUtils.ColoredString($" ==> Building fetch-deps script...", 0x80, 0xff, 0x80)); var fname = outpEnt.GetDepsUpdateScript(); Console.WriteLine(prefix + ConsoleUtils.ColoredString($" ==> Running fetch-deps script, writing into {depsFile.Value.LocalPath}...", 0x80, 0xff, 0x80)); RunCommandSync(fname, depsFile.Value.LocalPath); var resolvedDeps = JsonSerializer.Deserialize(await File.ReadAllTextAsync(depsFile.Value.LocalPath)); Console.WriteLine(prefix + ConsoleUtils.ColoredString($" ==> Locked {resolvedDeps.Length} dependencies!", (byte)(resolvedDeps.Length == 0 ? 0xff : 0x80), (byte)(resolvedDeps.Length == 0 ? 0x80 : 0xff), 0x80 )); RunCommandSync("nix", $"run nixpkgs#git -- add {depsFile.Value.LocalPath}"); })).ToList(); await Task.WhenAll(tasks); } static void RunCommandSync(string command, string args = "", bool silent = false) { // Console.WriteLine($"Executing command (silent: {silent}): {command} {args}"); Util.RunCommandSync(command, args, silent); } public class ProjectDependencyNode : IComparable { public required string Name; public required ProjectDependencyNode[] References; public int GetDepth() { if (References.Length == 0) return 0; return References.Max(x => x.GetDepth()) + 1; } public int GetWeight() { if (References.Length == 0) return 1; return References.Sum(x => x.GetWeight()) + 1; } public string GetNameWithWeights(bool compact = false) => compact ? $"{GetShortName()}:{GetDepth()}w{GetWeight()}" : $"{Name} @ {GetDepth()} | {GetWeight()}"; public string GetShortName() => Name.Replace("Spacebar-", "Sb-") .Replace("-Models-", "-Mdl-") .Replace("-Interop-", "-Intp-") .Replace("-Replication-", "-Rpl-") .Replace("-Authentication-", "-Auth-"); public string GetRelativeRootDir() => JsonSerializer .Deserialize(Util.GetCommandOutputSync("nix", $"eval --json .#packages.x86_64-linux.{Name}.srcRoot", silent: true, stderr: false))! .Split("/extra/admin-api/", 2)[1]; public string GetDepsUpdateScript() => Util.GetCommandOutputSync("nix", $"build .#{Name}.passthru.fetch-deps --no-link --print-out-paths", stderr: false); public (string Name, string StorePath, string LocalPath)? GetDepsFile() { var storePath = JsonSerializer.Deserialize(Util.GetCommandOutputSync("nix", $"eval --json .#packages.x86_64-linux.{Name}.passthru.__nugetDeps", silent: true, stderr: false)); return storePath == null ? null : ( new FileInfo(storePath).Name, storePath, Path.Combine(GetRelativeRootDir(), new FileInfo(storePath).Name) ); } public int CompareTo(object? obj) => References.Any(x => x.Name == Name) ? 1 : 0; }