diff --git a/Microsoft.DotNet.Framework.Docker.sln b/Microsoft.DotNet.Framework.Docker.sln new file mode 100644 index 000000000..f6109c6ed --- /dev/null +++ b/Microsoft.DotNet.Framework.Docker.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.Framework.UpdateDependencies", "eng\update-dependencies\Microsoft.DotNet.Framework.UpdateDependencies.csproj", "{41061718-0920-4131-A70C-F52E46C89E31}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.Framework.Docker.Tests", "tests\Microsoft.DotNet.Framework.Docker.Tests\Microsoft.DotNet.Framework.Docker.Tests.csproj", "{ACB9C5B2-6D27-4487-94A1-6F397AC4D733}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41061718-0920-4131-A70C-F52E46C89E31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Debug|x64.ActiveCfg = Debug|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Debug|x64.Build.0 = Debug|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Debug|x86.ActiveCfg = Debug|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Debug|x86.Build.0 = Debug|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Release|Any CPU.Build.0 = Release|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Release|x64.ActiveCfg = Release|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Release|x64.Build.0 = Release|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Release|x86.ActiveCfg = Release|Any CPU + {41061718-0920-4131-A70C-F52E46C89E31}.Release|x86.Build.0 = Release|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Debug|x64.ActiveCfg = Debug|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Debug|x64.Build.0 = Debug|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Debug|x86.ActiveCfg = Debug|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Debug|x86.Build.0 = Debug|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Release|Any CPU.Build.0 = Release|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Release|x64.ActiveCfg = Release|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Release|x64.Build.0 = Release|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Release|x86.ActiveCfg = Release|Any CPU + {ACB9C5B2-6D27-4487-94A1-6F397AC4D733}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/eng/update-dependencies/CustomFileRegexUpdater.cs b/eng/update-dependencies/CustomFileRegexUpdater.cs deleted file mode 100644 index ef729f104..000000000 --- a/eng/update-dependencies/CustomFileRegexUpdater.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Linq; -using Microsoft.DotNet.VersionTools.Dependencies; - -namespace Microsoft.DotNet.Framework.UpdateDependencies -{ - public class CustomFileRegexUpdater : FileRegexUpdater - { - private readonly string replacementValue; - private readonly string buildInfoName; - - public CustomFileRegexUpdater(string replacementValue, string buildInfoName) - { - this.replacementValue = replacementValue; - this.buildInfoName = buildInfoName; - } - - protected override string TryGetDesiredValue(IEnumerable dependencyInfos, out IEnumerable usedDependencyInfos) - { - usedDependencyInfos = dependencyInfos.Where(info => info.SimpleName == this.buildInfoName); - - return this.replacementValue; - } - } -} diff --git a/eng/update-dependencies/DependencyUpdater.cs b/eng/update-dependencies/DependencyUpdater.cs deleted file mode 100644 index 29807acc9..000000000 --- a/eng/update-dependencies/DependencyUpdater.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Microsoft.DotNet.VersionTools; -using Microsoft.DotNet.VersionTools.Automation; -using Microsoft.DotNet.VersionTools.Dependencies; -using Microsoft.DotNet.VersionTools.Dependencies.BuildOutput; - -namespace Microsoft.DotNet.Framework.UpdateDependencies -{ - public class DependencyUpdater - { - private readonly Options options; - private static readonly Lazy> dockerfiles; - - public const string RuntimeImageVariant = "runtime"; - public const string SdkImageVariant = "sdk"; - public const string AspnetImageVariant = "aspnet"; - public const string WcfImageVariant = "wcf"; - - public DependencyUpdater(Options options) - { - this.options = options; - } - - static DependencyUpdater() - { - dockerfiles = new Lazy>(() => - Directory.GetFiles( - Path.Combine(Program.RepoRoot, "src"), - "Dockerfile", - SearchOption.AllDirectories) - .Select(file => new DockerfileInfo(file)) - .ToArray()); - } - - public async Task ExecuteAsync() - { - IEnumerable dependencyInfos = new IDependencyInfo[] - { - CreateBuildInfo(RuntimeImageVariant, - this.options.DateStampRuntime?? this.options.DateStampAll ?? String.Empty), - CreateBuildInfo(SdkImageVariant, - this.options.DateStampSdk ?? this.options.DateStampAll ?? String.Empty), - CreateBuildInfo(AspnetImageVariant, - this.options.DateStampAspnet ?? this.options.DateStampAll ?? String.Empty), - CreateBuildInfo(WcfImageVariant, - this.options.DateStampWcf ?? this.options.DateStampAll ?? String.Empty), - }; - - DependencyUpdateResults updateResults = UpdateFiles(dependencyInfos); - if (updateResults.ChangesDetected()) - { - if (this.options.UpdateOnly) - { - Trace.TraceInformation($"Changes made but no GitHub credentials specified, skipping PR creation"); - } - else - { - await CreatePullRequestAsync(dependencyInfos); - } - } - } - - private async Task CreatePullRequestAsync(IEnumerable buildInfos) - { - GitHubAuth gitHubAuth = new GitHubAuth(this.options.GitHubPassword, this.options.GitHubUser, this.options.GitHubEmail); - PullRequestCreator prCreator = new PullRequestCreator(gitHubAuth, this.options.GitHubUser); - PullRequestOptions prOptions = new PullRequestOptions() - { - BranchNamingStrategy = new SingleBranchNamingStrategy($"UpdateDependencies-{this.options.GitHubUpstreamBranch}") - }; - - string commitMessage = $"[{this.options.GitHubUpstreamBranch}] Update image dependencies"; - - await prCreator.CreateOrUpdateAsync( - commitMessage, - commitMessage, - string.Empty, - new GitHubBranch(this.options.GitHubUpstreamBranch, new GitHubProject(this.options.GitHubProject, this.options.GitHubUpstreamOwner)), - new GitHubProject(this.options.GitHubProject, gitHubAuth.User), - prOptions); - } - - private static BuildDependencyInfo CreateBuildInfo(string name, string version) - { - return new BuildDependencyInfo( - new BuildInfo - { - Name = name, - LatestPackages = new Dictionary { }, - LatestReleaseVersion = version - }, - false, - Enumerable.Empty()); - } - - private DependencyUpdateResults UpdateFiles(IEnumerable buildInfos) - { - List updaters = new List(); - - updaters.AddRange(CreateManifestUpdaters()); - updaters.Add(ScriptRunnerUpdater.GetDockerfileUpdater(Program.RepoRoot)); - updaters.Add(ScriptRunnerUpdater.GetReadMeUpdater(Program.RepoRoot)); - - return DependencyUpdateUtils.Update(updaters, buildInfos); - } - - private IEnumerable CreateManifestUpdaters() - { - const string RuntimePrefix = "Runtime"; - const string SdkPrefix = "Sdk"; - const string AspnetPrefix = "Aspnet"; - const string WcfPrefix = "Wcf"; - - if (this.options.DateStampAll != null) - { - yield return CreateManifestUpdater(RuntimePrefix, RuntimeImageVariant); - yield return CreateManifestUpdater(SdkPrefix, SdkImageVariant); - yield return CreateManifestUpdater(AspnetPrefix, AspnetImageVariant); - yield return CreateManifestUpdater(WcfPrefix, WcfImageVariant); - } - else - { - if (this.options.DateStampRuntime != null) - { - yield return CreateManifestUpdater(RuntimePrefix, RuntimeImageVariant); - } - - if (this.options.DateStampSdk != null) - { - yield return CreateManifestUpdater(SdkPrefix, SdkImageVariant); - } - - if (this.options.DateStampAspnet != null) - { - yield return CreateManifestUpdater(AspnetPrefix, AspnetImageVariant); - } - - if (this.options.DateStampWcf != null) - { - yield return CreateManifestUpdater(WcfPrefix, WcfImageVariant); - } - } - } - - private static IDependencyUpdater CreateManifestUpdater(string dateStampVariablePrefix, string buildInfoName) - { - const string TagDateStampGroupName = "tagDateStampValue"; - - return new FileRegexReleaseUpdater - { - Path = Path.Combine(Program.RepoRoot, "manifest.datestamps.json"), - BuildInfoName = buildInfoName, - Regex = new Regex($"\"{dateStampVariablePrefix}ReleaseDateStamp\": \"(?<{TagDateStampGroupName}>\\d{{8}})\""), - VersionGroupName = TagDateStampGroupName - }; - } - - private class DockerfileInfo - { - public DockerfileInfo(string path) - { - this.Path = path; - - string[] pathParts = path.Substring(Program.RepoRoot.Length + 1) - .Replace(@"\", "/") - .Split("/"); - - this.FrameworkVersion = pathParts[2]; - this.ImageVariant = pathParts[1]; - this.OsVersion = pathParts[3]; - } - - public string Path { get; } - public string FrameworkVersion { get; } - public string OsVersion { get; } - public string ImageVariant { get; } - } - } -} diff --git a/eng/update-dependencies/IVariableContext.cs b/eng/update-dependencies/IVariableContext.cs new file mode 100644 index 000000000..93777b741 --- /dev/null +++ b/eng/update-dependencies/IVariableContext.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.DotNet.Framework.UpdateDependencies; + +/// +/// Provides access to a collection of named variables that can be retrieved +/// and modified. Variables support recursive resolution, allowing one variable +/// to reference another using the $(variableName) syntax. +/// +internal interface IVariableContext +{ + /// + /// Retrieves or updates a variable by name. When retrieving, any embedded + /// variable references are resolved recursively. When setting, the + /// underlying content is updated to reflect the change. + /// + /// + /// The name of the variable to access. + /// + /// + /// The resolved value of the variable, or an empty string if not found. + /// + string this[string key] { get; set; } + + /// + /// Enumerates all available variable names in this context. + /// + IEnumerable AllVariables { get; } +} diff --git a/eng/update-dependencies/IVariableUpdater.cs b/eng/update-dependencies/IVariableUpdater.cs new file mode 100644 index 000000000..7d521fbaf --- /dev/null +++ b/eng/update-dependencies/IVariableUpdater.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.DotNet.Framework.UpdateDependencies; + +internal interface IVariableUpdater +{ + /// + /// Determines whether a variable should be updated based on its name. + /// + /// + /// The name of the variable being checked for update eligibility. + /// + /// + /// All variables in the current context, in case the updater needs to + /// reference other variables when determining if an update is needed. + /// + /// + /// True if the variable should be updated. + /// + bool ShouldUpdate(string variableKey, IVariableContext variables); + + /// + /// Computes a new value for a variable based on its current name and the + /// surrounding context. This method is only called for variables whose + /// names match the . + /// + /// + /// The name of the variable being updated. + /// + /// + /// All variables in the current context. Used to reference other variables + /// when generating a new value. + /// + /// + /// The new value that should be assigned to the variable. + /// + Task GetNewValueAsync(string variableKey, IVariableContext variables); +} diff --git a/eng/update-dependencies/LcuVariableUpdater.cs b/eng/update-dependencies/LcuVariableUpdater.cs new file mode 100644 index 000000000..cf0f58891 --- /dev/null +++ b/eng/update-dependencies/LcuVariableUpdater.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.RegularExpressions; +using Microsoft.Playwright; + +namespace Microsoft.DotNet.Framework.UpdateDependencies; + +/// +/// Updates Latest Cumulative Update (LCU) download URLs variables by fetching +/// the most recent update information from the Microsoft Update Catalog. +/// +internal sealed partial class LcuVariableUpdater : IVariableUpdater, IAsyncDisposable +{ + private static readonly BrowserNewContextOptions s_newBrowserOptions = new() { Locale = "en-US" }; + + private readonly Lazy> _playwright; + private readonly Lazy> _browser; + + public LcuVariableUpdater() + { + _playwright = new Lazy>(Playwright.Playwright.CreateAsync); + _browser = new( + async () => + { + var playwright = await _playwright.Value; + return await playwright.Chromium.LaunchAsync( + new BrowserTypeLaunchOptions { Headless = true } + ); + } + ); + } + + /// + /// Matches LCU variable names in the format "lcu|{version}|{framework}" + /// where version contains digits and periods. Examples: + /// "lcu|ltsc2019|4.8", "lcu|ltsc2022|3.5". + /// + [GeneratedRegex(@"lcu\|[^|]+\|\d+(\.\d+)+?")] + private partial Regex LcuVariablePattern { get; } + + /// + public bool ShouldUpdate(string variableKey, IVariableContext variables) + { + return LcuVariablePattern.IsMatch(variableKey); + } + + /// + public async Task GetNewValueAsync(string variableKey, IVariableContext variables) + { + // Assuming that variableKey is in a format like "lcu|ltsc2019|4.8", we + // want to look at the variable "kb|ltsc2019|4.8". + string[] variableNameParts = variableKey.Split('|'); + variableNameParts[0] = "kb"; + string kbVariableName = string.Join('|', variableNameParts); + string kbNumber = variables[kbVariableName]; + + // By convention, the second/middle part of the variable name contains + // the Windows version. + var windowsVersion = variableNameParts[1]; + + string kbDownloadUrl = await GetKbDownloadUrlAsync(kbNumber, windowsVersion); + return kbDownloadUrl; + } + + /// + /// Fetches the download URL for a given KB number from the Microsoft + /// Update Catalog. + /// + /// The KB article to get the URL for. + /// + /// The Windows version to get the download URL for, since KB articles can + /// have versions for different Windows versions. It should be a string + /// like "ltsc2016", "ltsc2019", "ltsc2022" etc. + /// + /// KB article download URL + private async Task GetKbDownloadUrlAsync(string kb, string windowsVersion) + { + var browser = await _browser.Value; + var context = await browser.NewContextAsync(s_newBrowserOptions); + var page = await context.NewPageAsync(); + + await page.GotoAsync($"https://catalog.update.microsoft.com/Search.aspx?q={kb}"); + + // Some windows versions require a more precise regex to match the + // correct LCU in on the update catalog page. By convention, the + // Windows version is the second part of the version name. + var tableRowRegex = windowsVersion switch + { + "ltsc2022" => Server2022TableRowRegex, + _ => WindowsServerTableRowRegex + }; + + var downloadPopUpPage = await page.RunAndWaitForPopupAsync( + async () => + { + await page + .GetByRole(AriaRole.Row, new PageGetByRoleOptions() { NameRegex = tableRowRegex, Exact = true }) + .GetByRole(AriaRole.Button) + .ClickAsync(); + } + ); + + var url = await downloadPopUpPage + .GetByRole(AriaRole.Link, s_getDownloadLinkOptions) + .GetAttributeAsync("href"); + + await context.CloseAsync(); + + Console.WriteLine($"{kb} download URL: {url}"); + + return url ?? ""; + } + + public async ValueTask DisposeAsync() + { + if (_browser.IsValueCreated) + { + var browser = await _browser.Value; + await browser.DisposeAsync(); + } + + if (_playwright.IsValueCreated) + { + var playwright = await _playwright.Value; + playwright.Dispose(); + } + } + + private static readonly PageGetByRoleOptions s_getDownloadLinkOptions = new() + { + NameRegex = DownloadLinkRegex, + }; + + [GeneratedRegex(@"^windows.*\.msu$")] + private static partial Regex DownloadLinkRegex { get; } + + [GeneratedRegex(@"server.*x64", RegexOptions.IgnoreCase, "en-US")] + private static partial Regex WindowsServerTableRowRegex { get; } + + [GeneratedRegex(@"server.*21H2.*x64", RegexOptions.IgnoreCase, "en-US")] + private static partial Regex Server2022TableRowRegex { get; } +} diff --git a/eng/update-dependencies/ManifestVariableContext.cs b/eng/update-dependencies/ManifestVariableContext.cs new file mode 100644 index 000000000..74bb8eed9 --- /dev/null +++ b/eng/update-dependencies/ManifestVariableContext.cs @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; + +namespace Microsoft.DotNet.Framework.UpdateDependencies; + +/// +/// Manages variables stored in a manifest JSON file. Variables can reference +/// other variables using the $(name) syntax. Changes are automatically +/// synchronized back to the original JSON text data via . +/// +internal partial class ManifestVariableContext : IVariableContext +{ + private static readonly JsonDocumentOptions s_jsonDocumentOptions = new() + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + }; + + private readonly JsonObject _variables; + + /// + /// Initializes a new variable context from manifest JSON content. The JSON + /// must contain a root object with a "variables" property whose value is + /// an object mapping variable names to their string values. + /// + /// + /// Complete JSON content of the manifest file, which will be parsed and + /// modified as variables change. + /// + public ManifestVariableContext(string manifestJsonContent) + { + Content = manifestJsonContent; + + JsonNode manifest = JsonNode.Parse(Content, documentOptions: s_jsonDocumentOptions) ?? + throw new InvalidOperationException($""" + Failed to serialize variables manifest from content: + + {Content} + """ + ); + + _variables = (JsonObject?)manifest["variables"] ?? + throw new InvalidOperationException($""" + Manifest JSON does not contain 'variables' section: + + {Content} + """ + ); + } + + /// + /// The current JSON content of the manifest file, automatically updated + /// whenever variables are modified through the indexer. This content + /// preserves the original formatting and structure while reflecting any + /// variable changes. + /// + public string Content { get; private set; } + + /// + /// Enumerates all variable names defined in the manifest's variables + /// section. + /// + public IEnumerable AllVariables => _variables.Select(kvp => kvp.Key); + + /// + /// Retrieves or updates a variable by name. Embedded variable references + /// using $(variableName) syntax are resolved recursively. When modifying, + /// is updated to reflect the changes. + /// + public string this[string key] + { + get => GetVariable(key); + set => SetVariable(key, value); + } + + /// + /// Updates all variables that match 's + /// rules. + /// + /// + /// The updater that defines which variables to modify and calculates their + /// new values. + /// + public async Task ApplyAsync(IVariableUpdater variableUpdater) + { + foreach (string variableName in AllVariables) + { + if (variableUpdater.ShouldUpdate(variableName, this) && !ReferencesOtherVariable(variableName)) + { + string newValue = await variableUpdater.GetNewValueAsync(variableName, this); + this[variableName] = newValue; + } + } + } + + /// + /// Resolves a variable's value, including any nested variable references. + /// Variable references use the $(variableName) syntax and are resolved + /// recursively. + /// + /// + /// The name of the variable to resolve. + /// + /// + /// The fully resolved value, or an empty string if the variable is not + /// found. + /// + private string GetVariable(string key) + { + string value = _variables[key]?.ToString() ?? ""; + + // Look through any variables in this variable's value. If there are + // any, resolve them recursively. + var matchedSubVariables = VariableRegex.Matches(value); + foreach (Match match in matchedSubVariables) + { + string subVariableName = match.Groups["name"].Value; + string subVariableValue = GetVariable(subVariableName); + value = value.Replace(match.Value, subVariableValue); + } + + return value; + } + + private void SetVariable(string key, string value) + { + // Update the variable in the json object representation + _variables[key] = value; + + // Update the variable in the file contents. Use regex to preserve formatting. + string jsonPropertyPattern = $@" + ""{Regex.Escape(key)}"" # property name in quotes + \s*:\s* # colon with optional whitespace + ""[^""]*"" # value in quotes (no inner quotes) + "; + + var regex = new Regex(jsonPropertyPattern, RegexOptions.IgnorePatternWhitespace); + Content = regex.Replace(Content, $"\"{key}\": \"{value}\""); + } + + /// + /// Tells whether or not the specified variable is a direct reference to + /// another variable. + /// + /// Name of the variable to check. + /// + /// True if the variable references another variable, false if it doesn't + /// exist or has its own value. + /// + private bool ReferencesOtherVariable(string key) + { + string rawVariableValue = _variables[key]?.ToString() ?? ""; + return VariableRegex.IsMatch(rawVariableValue); + } + + /// + /// Matches variable reference patterns in the format $(variableName) for + /// recursive resolution. Has one named group, "name" which captures the + /// variable name inside the parentheses. + /// + [GeneratedRegex(@"\$\((?.*?)\)")] + private static partial Regex VariableRegex { get; } +} diff --git a/eng/update-dependencies/Microsoft.DotNet.Framework.UpdateDependencies.csproj b/eng/update-dependencies/Microsoft.DotNet.Framework.UpdateDependencies.csproj index c6e47d25a..8ff60a5fa 100644 --- a/eng/update-dependencies/Microsoft.DotNet.Framework.UpdateDependencies.csproj +++ b/eng/update-dependencies/Microsoft.DotNet.Framework.UpdateDependencies.csproj @@ -5,13 +5,14 @@ net9.0 latest enable + enable Microsoft.DotNet.Framework.UpdateDependencies update-dependencies - - - + + + diff --git a/eng/update-dependencies/Options.cs b/eng/update-dependencies/Options.cs deleted file mode 100644 index ce4a9c047..000000000 --- a/eng/update-dependencies/Options.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.CommandLine; - -namespace Microsoft.DotNet.Framework.UpdateDependencies -{ - public class Options - { - public string? DateStampAll { get; private set; } - - public string? DateStampRuntime { get; private set; } - - public string? DateStampSdk { get; private set; } - - public string? DateStampAspnet { get; private set; } - - public string? DateStampWcf { get; private set; } - - public string? GitHubEmail { get; private set; } - - public string? GitHubPassword { get; private set; } - - public string GitHubProject => "dotnet-framework-docker"; - - public string GitHubUpstreamBranch => "main"; - - public string GitHubUpstreamOwner => "Microsoft"; - - public string? GitHubUser { get; private set; } - - public bool UpdateOnly => GitHubEmail == null || GitHubPassword == null || GitHubUser == null; - - public Options(string? datestampAll, string? datestampRuntime, string? datestampSdk, string? datestampAspnet, string? datestampWcf, string? email, - string? password, string? user) - { - DateStampAll = datestampAll; - DateStampRuntime = datestampRuntime; - DateStampSdk = datestampSdk; - DateStampAspnet = datestampAspnet; - DateStampWcf = datestampWcf; - GitHubEmail = email; - GitHubPassword = password; - GitHubUser = user; - } - - public static IEnumerable GetCliSymbols() => - new Symbol[] - { - new Option("--datestamp-all", "Tag date stamp to assign to all image types"), - new Option("--datestamp-runtime", "Tag date stamp to assign to runtime image types (overrides datestamp-all)"), - new Option("--datestamp-sdk", "Tag date stamp to assign to SDK image types (overrides datestamp-all)"), - new Option("--datestamp-aspnet", "Tag date stamp to assign to ASP.NET image types (overrides datestamp-all)"), - new Option("--datestamp-wcf", "Tag date stamp to assign to WCF image types (overrides datestamp-all)"), - new Option("--email", "GitHub email used to make PR (if not specified, a PR will not be created)"), - new Option("--password", "GitHub password used to make PR (if not specified, a PR will not be created)"), - new Option("--user", "GitHub user used to make PR (if not specified, a PR will not be created)") - }; - } -} diff --git a/eng/update-dependencies/Program.cs b/eng/update-dependencies/Program.cs index fb7e72d49..dc083370b 100644 --- a/eng/update-dependencies/Program.cs +++ b/eng/update-dependencies/Program.cs @@ -2,47 +2,47 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +using Microsoft.DotNet.Framework.UpdateDependencies; using System.CommandLine; -using System.CommandLine.Invocation; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -namespace Microsoft.DotNet.Framework.UpdateDependencies +// Command-line tool for updating dependency variables in .NET Framework Docker +// manifest files. +// +// Before running, you may need to build the app and then run: +// pwsh bin/Debug/net*/playwright.ps1 install +// +// Usage: dotnet run -- --help + +var manifestFileOption = new Argument("manifest file path") { - public static class Program - { - public static string RepoRoot { get; } = Directory.GetCurrentDirectory(); + DefaultValueFactory = _ => "manifest.versions.json", +}; - public static Task Main(string[] args) - { - RootCommand command = new RootCommand(); - foreach (Symbol symbol in Options.GetCliSymbols()) - { - command.Add(symbol); - }; +var updateLcusCommand = new Command( + name: "update-lcus", + description: "Update all LCU variables in the specified manifest file.") +{ + manifestFileOption +}; - command.Handler = CommandHandler.Create(ExecuteAsync); +var rootCommand = new RootCommand() { updateLcusCommand }; - return command.InvokeAsync(args); - } +updateLcusCommand.SetAction( + async parseResult => + { + string manifestFilePath = parseResult.GetValue(manifestFileOption) ?? + throw new ArgumentException("Manifest file path is required."); + + var manifestVersionsContent = await File.ReadAllTextAsync(manifestFilePath); + var manifestVersionsContext = new ManifestVariableContext(manifestVersionsContent); - private static async Task ExecuteAsync(Options options) + await using (var lcuUpdater = new LcuVariableUpdater()) { - try - { - Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); - - await new DependencyUpdater(options).ExecuteAsync(); - } - catch (Exception e) - { - Console.Error.WriteLine($"Failed to update dependencies:{Environment.NewLine}{e.ToString()}"); - Environment.Exit(1); - } - - Environment.Exit(0); + await manifestVersionsContext.ApplyAsync(lcuUpdater); } + + await File.WriteAllTextAsync(manifestFilePath, manifestVersionsContext.Content); } -} +); + +return rootCommand.Parse(args).Invoke(); diff --git a/eng/update-dependencies/ScriptRunnerUpdater.cs b/eng/update-dependencies/ScriptRunnerUpdater.cs deleted file mode 100644 index b9ea3a3ff..000000000 --- a/eng/update-dependencies/ScriptRunnerUpdater.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using Microsoft.DotNet.VersionTools.Dependencies; - -namespace Microsoft.DotNet.Framework.UpdateDependencies -{ - /// - /// An IDependencyUpdater that executes a PowerShell script to perform the update. - /// - public class ScriptRunnerUpdater : IDependencyUpdater - { - private readonly string _scriptPath; - - private ScriptRunnerUpdater(string scriptPath) - { - _scriptPath = scriptPath; - } - - public static IDependencyUpdater GetDockerfileUpdater(string repoRoot) => - new ScriptRunnerUpdater(Path.Combine(repoRoot, "eng", "dockerfile-templates", "Get-GeneratedDockerfiles.ps1")); - - public static IDependencyUpdater GetReadMeUpdater(string repoRoot) => - new ScriptRunnerUpdater(Path.Combine(repoRoot, "eng", "readme-templates", "Get-GeneratedReadmes.ps1")); - - public IEnumerable GetUpdateTasks(IEnumerable dependencyInfos) - { - return new DependencyUpdateTask[] { - new DependencyUpdateTask( - () => ExecuteScript(), - Enumerable.Empty(), - Enumerable.Empty() - ) - }; - } - - private void ExecuteScript() - { - Trace.TraceInformation($"Executing '{_scriptPath}'"); - - // Support both execution within Windows 10, Nano Server and Linux environments. - Process process; - try - { - process = Process.Start("pwsh", _scriptPath); - process.WaitForExit(); - } - catch (Win32Exception) - { - process = Process.Start("powershell", _scriptPath); - process.WaitForExit(); - } - - if (process.ExitCode != 0) - { - throw new InvalidOperationException($"Unable to successfully execute '{_scriptPath}'"); - } - } - } -}