From fa2899c2dcc993542f0e0dd23512c7a5bb42737d Mon Sep 17 00:00:00 2001 From: agneszitte Date: Thu, 28 Aug 2025 19:28:35 -0400 Subject: [PATCH 1/3] feat(updater): Add support for excluding package groups via JSON file (cherry picked from commit b6fa3b6753c89928f0eb4a6366442d4f0a571b33) --- .github/actions/sdk-update/action.yml | 8 +++- .github/sdk-update-exclude.json | 6 +++ tools/Uno.Sdk.Updater/Program.cs | 65 +++++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 .github/sdk-update-exclude.json diff --git a/.github/actions/sdk-update/action.yml b/.github/actions/sdk-update/action.yml index b51fc0fcd..c173f7ffc 100644 --- a/.github/actions/sdk-update/action.yml +++ b/.github/actions/sdk-update/action.yml @@ -6,6 +6,10 @@ inputs: description: 'base branch' required: true default: 'main' + exclude-file: # packages to exclude for the update + description: 'Path to the JSON exclude file' + required: false + default: '.github/sdk-update-exclude.json' runs: using: "composite" @@ -22,5 +26,7 @@ runs: setAllVars: true - name: Run Uno Sdk Updater - run: dotnet run -c Release --project tools/Uno.Sdk.Updater shell: pwsh + env: + UNO_SDK_EXCLUDE_FILE: ${{ inputs['exclude-file'] }} + run: dotnet run -c Release --project tools/Uno.Sdk.Updater diff --git a/.github/sdk-update-exclude.json b/.github/sdk-update-exclude.json new file mode 100644 index 000000000..a5d9981fb --- /dev/null +++ b/.github/sdk-update-exclude.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "SvgSkia" + ] +} + diff --git a/tools/Uno.Sdk.Updater/Program.cs b/tools/Uno.Sdk.Updater/Program.cs index bc084bd4d..e85d37308 100644 --- a/tools/Uno.Sdk.Updater/Program.cs +++ b/tools/Uno.Sdk.Updater/Program.cs @@ -279,22 +279,28 @@ static string GetManifestGroupVersionOverride(IEnumerable manifes return group.VersionOverride[overrideKey]; } - throw new InvalidOperationException($"No Version Overrides were fround for {groupId} or the key {overrideKey}."); +throw new InvalidOperationException($"No Version Overrides were found for {groupId} or the key {overrideKey}."); } static async Task UpdateGroup(ManifestGroup group, NuGetVersion unoVersion, NuGetApiClient client) { + if (ExcludeConfig.Excluded.Contains(group.Group)) + { + Console.WriteLine($"Skipping '{group.Group}' due to exclusion list."); + return group; + } + if (group.Group == "Core") { Console.WriteLine($"Setting Core group to: {unoVersion.OriginalVersion}"); return group with { Version = unoVersion }; } // Skip AndroidX packages to avoid Java misalignment - else if (group.Packages.Any(x => x.StartsWith("Xamarin")) + else if (group.Packages.Any(x => x.StartsWith("Xamarin")) // Skip Maui on Release branch to avoid AndroidX package misalignment || (!unoVersion.IsPreview && group.Group == "Maui")) { - Console.WriteLine("Skipping " + group.Group + " to avoid Java misalignment."); + Console.WriteLine($"Skipping '{group.Group}' to avoid Java misalignment."); return group; } @@ -387,4 +393,55 @@ public string ToXml() var attributes = string.Join(" ", attributeList); return $"<{ItemType} Include=\"{Include}\" {attributes} />"; } -} \ No newline at end of file +} + +static class ExcludeConfig +{ + private static readonly Lazy> _excluded = new(() => Load()); + public static HashSet Excluded => _excluded.Value; + + private static HashSet Load() + { + var set = new HashSet(StringComparer.OrdinalIgnoreCase); + + var path = Environment.GetEnvironmentVariable("UNO_SDK_EXCLUDE_FILE"); + if (string.IsNullOrWhiteSpace(path)) + { + // Nothing passed -> no exclusions + return set; + } + + if (!File.Exists(path)) + { + Console.WriteLine($"Exclude file not found: {path}"); + return set; + } + + try + { + using var fs = File.OpenRead(path); + using var doc = System.Text.Json.JsonDocument.Parse(fs); + if (doc.RootElement.TryGetProperty("exclude", out var arr) && + arr.ValueKind == System.Text.Json.JsonValueKind.Array) + { + foreach (var el in arr.EnumerateArray()) + { + if (el.ValueKind == System.Text.Json.JsonValueKind.String) + { + var s = el.GetString(); + if (!string.IsNullOrWhiteSpace(s)) + set.Add(s!); + } + } + } + + Console.WriteLine($"Loaded {set.Count} exclusion(s) from {path}"); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to read exclude file: {ex.Message}"); + } + + return set; + } +} From de1e650b720a1760878dae9f7dbd67cac4ca59ff Mon Sep 17 00:00:00 2001 From: agneszitte Date: Wed, 3 Sep 2025 16:51:29 -0400 Subject: [PATCH 2/3] feat(updater): Add exclude CLI arg and prevent unwanted downgrades during the update (cherry picked from commit f07b1d3a9a87719d2d9f220c37641561f2161b85) --- .github/actions/sdk-update/action.yml | 4 +- tools/Uno.Sdk.Updater/Config/ExcludeConfig.cs | 57 ++++++++ tools/Uno.Sdk.Updater/Program.cs | 133 +++++++++--------- tools/Uno.Sdk.Updater/ReadMe.md | 2 +- tools/Uno.Sdk.Updater/Utils/Cli.cs | 17 +++ 5 files changed, 146 insertions(+), 67 deletions(-) create mode 100644 tools/Uno.Sdk.Updater/Config/ExcludeConfig.cs create mode 100644 tools/Uno.Sdk.Updater/Utils/Cli.cs diff --git a/.github/actions/sdk-update/action.yml b/.github/actions/sdk-update/action.yml index c173f7ffc..3e69c9e4c 100644 --- a/.github/actions/sdk-update/action.yml +++ b/.github/actions/sdk-update/action.yml @@ -27,6 +27,4 @@ runs: - name: Run Uno Sdk Updater shell: pwsh - env: - UNO_SDK_EXCLUDE_FILE: ${{ inputs['exclude-file'] }} - run: dotnet run -c Release --project tools/Uno.Sdk.Updater + run: dotnet run -c Release --project tools/Uno.Sdk.Updater -- --exclude-file "${{ inputs['exclude-file'] }}" diff --git a/tools/Uno.Sdk.Updater/Config/ExcludeConfig.cs b/tools/Uno.Sdk.Updater/Config/ExcludeConfig.cs new file mode 100644 index 000000000..c21b2400b --- /dev/null +++ b/tools/Uno.Sdk.Updater/Config/ExcludeConfig.cs @@ -0,0 +1,57 @@ +namespace Uno.Sdk.Updater.Config +{ + // Exclusions (JSON via --exclude-file) + internal static class ExcludeConfig + { + public static string? ExcludeFilePath { get; set; } + + private static readonly Lazy> _excluded = new(() => Load()); + public static HashSet Excluded => _excluded.Value; + + private static HashSet Load() + { + var set = new HashSet(StringComparer.OrdinalIgnoreCase); + + var path = ExcludeFilePath; + if (string.IsNullOrWhiteSpace(path)) + return set; // no exclusions when not provided + + // Normalize relative path + if (!Path.IsPathRooted(path)) + path = Path.GetFullPath(path); + + if (!File.Exists(path)) + { + Console.WriteLine($"Exclude file not found: {path}"); + return set; + } + + try + { + using var fs = File.OpenRead(path); + using var doc = System.Text.Json.JsonDocument.Parse(fs); + if (doc.RootElement.TryGetProperty("exclude", out var arr) && + arr.ValueKind == System.Text.Json.JsonValueKind.Array) + { + foreach (var el in arr.EnumerateArray()) + { + if (el.ValueKind == System.Text.Json.JsonValueKind.String) + { + var s = el.GetString(); + if (!string.IsNullOrWhiteSpace(s)) + set.Add(s); + } + } + } + + Console.WriteLine($"Loaded {set.Count} exclusion(s) from {path}"); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to read exclude file: {ex.Message}"); + } + + return set; + } + } +} diff --git a/tools/Uno.Sdk.Updater/Program.cs b/tools/Uno.Sdk.Updater/Program.cs index e85d37308..3f6803855 100644 --- a/tools/Uno.Sdk.Updater/Program.cs +++ b/tools/Uno.Sdk.Updater/Program.cs @@ -6,6 +6,7 @@ using Uno.Sdk.Models; using Uno.Sdk.Services; using Uno.Sdk.Updater; +using Uno.Sdk.Updater.Config; using Uno.Sdk.Updater.Utils; const string UnoSdkPackageId = "Uno.Sdk.Private"; @@ -16,6 +17,9 @@ Console.WriteLine($"Maximum Search Version: {UpdaterBuildContext.MaxVersion}"); WriteBreak(); +// Minimal CLI parsing for --exclude-file +ExcludeConfig.ExcludeFilePath = Cli.GetArgValue("--exclude-file"); + var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web) { // We want to keep the output Human Readable @@ -53,7 +57,7 @@ string? tags = null; bool wroteChanges = false; -foreach(var entry in sdkZip.Entries) +foreach (var entry in sdkZip.Entries) { var extension = Path.GetExtension(entry.FullName); string[] allowedExtensions = [".md", ".json", ".nuspec"]; @@ -88,13 +92,12 @@ var inputManifest = await JsonSerializer.DeserializeAsync>(packageStream) ?? throw new InvalidOperationException("Unable to parse the packages.json from the Sdk."); - if (!unoVersion.IsPreview) - { - inputManifest = MergeLocalManifest(inputManifest, outputPath); - } + inputManifest = unoVersion.IsPreview + ? MergeLocalOverridesOnly(inputManifest, outputPath) // keep only local VersionOverride entries + : MergeLocalManifest(inputManifest, outputPath); // full merge as before var manifest = new List(); - foreach(var group in inputManifest) + foreach (var group in inputManifest) { var updated = await UpdateGroup(group, unoVersion, client); manifest.Add(updated); @@ -135,7 +138,7 @@ var manifestJson = File.ReadAllText(packagesJsonPath); var manifest = JsonSerializer.Deserialize>(manifestJson) ?? []; - foreach(var group in manifest) + foreach (var group in manifest) { readMe = Regex.Replace(readMe, Regex.Escape($"${group.Group}$"), group.Version); } @@ -177,6 +180,49 @@ static IEnumerable MergeLocalManifest(IEnumerable return mergedManifest; } +// Keep local versions as baseline. Only ensure local VersionOverride are present, +// and bring in any brand-new groups that exist in the SDK manifest. +static IEnumerable MergeLocalOverridesOnly(IEnumerable sdkManifest, string packagesJsonPath) +{ + var local = JsonSerializer.Deserialize>(File.ReadAllText(packagesJsonPath)) + ?? throw new InvalidOperationException($"Unable to parse local packages.json at '{packagesJsonPath}'."); + + // Start from LOCAL manifest -> prevents downgrades of existing groups + var map = local.ToDictionary(g => g.Group, g => g, StringComparer.OrdinalIgnoreCase); + + // Keep local versions as the baseline to prevent downgrades. + // For groups that have local VersionOverride entries, merge them over the + // existing overrides in the map (local wins). This preserves any SDK overrides + // that local did not specify, and ensures a case-insensitive dictionary. + foreach (var lg in local) + { + if (lg.VersionOverride is { Count: > 0 } && map.TryGetValue(lg.Group, out var existing)) + { + // Start from existing overrides so we don't drop SDK-provided keys + var merged = existing.VersionOverride is null + ? new Dictionary(StringComparer.OrdinalIgnoreCase) + : new Dictionary(existing.VersionOverride, StringComparer.OrdinalIgnoreCase); + + foreach (var kv in lg.VersionOverride) + { + // Local overrides win (overwrite) + merged[kv.Key] = kv.Value; + } + + map[lg.Group] = existing with { VersionOverride = merged }; + } + } + + // Add brand-new groups that exist in SDK but not locally + foreach (var sg in sdkManifest) + { + if (!map.ContainsKey(sg.Group)) + map[sg.Group] = sg; + } + + return map.Values; +} + static void WriteIfDifferent(string filePath, string content, ref bool didWriteChanges) { if (!File.Exists(filePath) || !File.ReadAllText(filePath).Equals(content)) @@ -279,7 +325,7 @@ static string GetManifestGroupVersionOverride(IEnumerable manifes return group.VersionOverride[overrideKey]; } -throw new InvalidOperationException($"No Version Overrides were found for {groupId} or the key {overrideKey}."); + throw new InvalidOperationException($"No Version Overrides were found for {groupId} or the key {overrideKey}."); } static async Task UpdateGroup(ManifestGroup group, NuGetVersion unoVersion, NuGetApiClient client) @@ -355,21 +401,33 @@ static async Task UpdateGroup(ManifestGroup group, NuGetVersion u if (group.VersionOverride is not null && group.VersionOverride.Count != 0) { var updatedOverrides = new Dictionary(); - foreach((var key, var versionOverrideString) in group.VersionOverride) + foreach ((var key, var versionOverrideString) in group.VersionOverride) { if (!NuGetVersion.TryParse(versionOverrideString, out var versionOverride)) { - Console.WriteLine($"Could not parse version '{versionOverrideString} for {group.Group}."); + Console.WriteLine($"Could not parse version '{versionOverrideString}' for group '{group.Group}', package '{packageId}'."); continue; } - version = await client.GetVersionAsync(packageId, versionOverride.IsPreview, noMajorUpgrade, versionOverride.OriginalVersion); + // Explicit VersionOverrides should always allow major upgrades + version = await client.GetVersionAsync(packageId, versionOverride.IsPreview, false, versionOverride.OriginalVersion); if (version != versionOverrideString) { Console.WriteLine($"Updated Version Override for '{group.Group}' - '{key}' to '{version}'."); } - version = NuGetVersion.Parse(version) < versionOverride ? versionOverrideString : version; + if (!NuGetVersion.TryParse(version, out var parsedVersion)) + { + Console.WriteLine($"Could not parse version '{version}' for {group.Group}."); + // Fall back to the local override as a safe default + version = versionOverride.OriginalVersion; + } + else if (parsedVersion < versionOverride) + { + // Keep the local override (e.g., 10.0.0-preview) if the feed returns a lower version (e.g., 9.x) + version = versionOverride.OriginalVersion; + } + updatedOverrides.Add(key, version); } @@ -394,54 +452,3 @@ public string ToXml() return $"<{ItemType} Include=\"{Include}\" {attributes} />"; } } - -static class ExcludeConfig -{ - private static readonly Lazy> _excluded = new(() => Load()); - public static HashSet Excluded => _excluded.Value; - - private static HashSet Load() - { - var set = new HashSet(StringComparer.OrdinalIgnoreCase); - - var path = Environment.GetEnvironmentVariable("UNO_SDK_EXCLUDE_FILE"); - if (string.IsNullOrWhiteSpace(path)) - { - // Nothing passed -> no exclusions - return set; - } - - if (!File.Exists(path)) - { - Console.WriteLine($"Exclude file not found: {path}"); - return set; - } - - try - { - using var fs = File.OpenRead(path); - using var doc = System.Text.Json.JsonDocument.Parse(fs); - if (doc.RootElement.TryGetProperty("exclude", out var arr) && - arr.ValueKind == System.Text.Json.JsonValueKind.Array) - { - foreach (var el in arr.EnumerateArray()) - { - if (el.ValueKind == System.Text.Json.JsonValueKind.String) - { - var s = el.GetString(); - if (!string.IsNullOrWhiteSpace(s)) - set.Add(s!); - } - } - } - - Console.WriteLine($"Loaded {set.Count} exclusion(s) from {path}"); - } - catch (Exception ex) - { - Console.WriteLine($"Failed to read exclude file: {ex.Message}"); - } - - return set; - } -} diff --git a/tools/Uno.Sdk.Updater/ReadMe.md b/tools/Uno.Sdk.Updater/ReadMe.md index cc5d37186..e115aba32 100644 --- a/tools/Uno.Sdk.Updater/ReadMe.md +++ b/tools/Uno.Sdk.Updater/ReadMe.md @@ -37,7 +37,7 @@ The Uno.Sdk powers the Uno Platform Single Project, including the ability to imp | MauiVersion** | $Maui$ | \* UnoVersion cannot be changed via MSBuild. You must change the SDK Version to change the UnoVersion. -\*\* This version may have a different version for .NET 9.0. +\*\* This version may have a different version for .NET 10.0. ```json $PackagesJson$ diff --git a/tools/Uno.Sdk.Updater/Utils/Cli.cs b/tools/Uno.Sdk.Updater/Utils/Cli.cs new file mode 100644 index 000000000..01bf115ad --- /dev/null +++ b/tools/Uno.Sdk.Updater/Utils/Cli.cs @@ -0,0 +1,17 @@ +namespace Uno.Sdk.Updater.Utils +{ + /// Minimal helper to retrieve a CLI argument value (e.g. --exclude-file). + internal static class Cli + { + public static string? GetArgValue(string name) + { + var av = Environment.GetCommandLineArgs(); + for (int i = 0; i < av.Length; i++) + { + if (string.Equals(av[i], name, StringComparison.OrdinalIgnoreCase) && i + 1 < av.Length) + return av[i + 1]; + } + return null; + } + } +} From b527d3548dbd6d79be7d28a85d5854cc0441d408 Mon Sep 17 00:00:00 2001 From: agneszitte Date: Wed, 3 Sep 2025 18:48:39 -0400 Subject: [PATCH 3/3] chore: Adjust back .NET version for 6.2 branch --- tools/Uno.Sdk.Updater/ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Uno.Sdk.Updater/ReadMe.md b/tools/Uno.Sdk.Updater/ReadMe.md index e115aba32..cc5d37186 100644 --- a/tools/Uno.Sdk.Updater/ReadMe.md +++ b/tools/Uno.Sdk.Updater/ReadMe.md @@ -37,7 +37,7 @@ The Uno.Sdk powers the Uno Platform Single Project, including the ability to imp | MauiVersion** | $Maui$ | \* UnoVersion cannot be changed via MSBuild. You must change the SDK Version to change the UnoVersion. -\*\* This version may have a different version for .NET 10.0. +\*\* This version may have a different version for .NET 9.0. ```json $PackagesJson$