|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 3 | +// See the LICENSE file in the project root for more information. |
| 4 | + |
| 5 | +using System; |
| 6 | +using System.Collections.Generic; |
| 7 | +using System.Diagnostics; |
| 8 | +using System.IO; |
| 9 | +using System.Linq; |
| 10 | +using System.Text.RegularExpressions; |
| 11 | +using System.Threading.Tasks; |
| 12 | +using Microsoft.DotNet.Framework.UpdateDependencies.Models; |
| 13 | +using Microsoft.DotNet.VersionTools; |
| 14 | +using Microsoft.DotNet.VersionTools.Automation; |
| 15 | +using Microsoft.DotNet.VersionTools.Dependencies; |
| 16 | +using Microsoft.DotNet.VersionTools.Dependencies.BuildOutput; |
| 17 | +using Newtonsoft.Json; |
| 18 | + |
| 19 | +namespace Microsoft.DotNet.Framework.UpdateDependencies |
| 20 | +{ |
| 21 | + public class DependencyUpdater |
| 22 | + { |
| 23 | + private readonly Options options; |
| 24 | + private static readonly Lazy<IEnumerable<DockerfileInfo>> dockerfiles; |
| 25 | + |
| 26 | + public const string RuntimeImageVariant = "runtime"; |
| 27 | + public const string SdkImageVariant = "sdk"; |
| 28 | + public const string AspnetImageVariant = "aspnet"; |
| 29 | + public const string WcfImageVariant = "wcf"; |
| 30 | + |
| 31 | + public DependencyUpdater(Options options) |
| 32 | + { |
| 33 | + this.options = options; |
| 34 | + } |
| 35 | + |
| 36 | + static DependencyUpdater() |
| 37 | + { |
| 38 | + dockerfiles = new Lazy<IEnumerable<DockerfileInfo>>(() => |
| 39 | + new DirectoryInfo(Program.RepoRoot).GetDirectories("4.*") |
| 40 | + .Append(new DirectoryInfo(Path.Combine(Program.RepoRoot, "3.5"))) |
| 41 | + .SelectMany(dir => dir.GetFiles("Dockerfile", SearchOption.AllDirectories)) |
| 42 | + .Select(file => new DockerfileInfo(file.FullName)) |
| 43 | + .ToArray()); |
| 44 | + } |
| 45 | + |
| 46 | + public async Task ExecuteAsync() |
| 47 | + { |
| 48 | + IEnumerable<IDependencyInfo> dependencyInfos = new IDependencyInfo[] |
| 49 | + { |
| 50 | + CreateBuildInfo(RuntimeImageVariant, |
| 51 | + this.options.DateStampRuntime?? this.options.DateStampAll ?? String.Empty), |
| 52 | + CreateBuildInfo(SdkImageVariant, |
| 53 | + this.options.DateStampSdk ?? this.options.DateStampAll ?? String.Empty), |
| 54 | + CreateBuildInfo(AspnetImageVariant, |
| 55 | + this.options.DateStampAspnet ?? this.options.DateStampAll ?? String.Empty), |
| 56 | + CreateBuildInfo(WcfImageVariant, |
| 57 | + this.options.DateStampWcf ?? this.options.DateStampAll ?? String.Empty), |
| 58 | + }; |
| 59 | + |
| 60 | + DependencyUpdateResults updateResults = UpdateFiles(dependencyInfos); |
| 61 | + if (updateResults.ChangesDetected()) |
| 62 | + { |
| 63 | + if (this.options.UpdateOnly) |
| 64 | + { |
| 65 | + Trace.TraceInformation($"Changes made but no GitHub credentials specified, skipping PR creation"); |
| 66 | + } |
| 67 | + else |
| 68 | + { |
| 69 | + await CreatePullRequestAsync(dependencyInfos); |
| 70 | + } |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + private async Task CreatePullRequestAsync(IEnumerable<IDependencyInfo> buildInfos) |
| 75 | + { |
| 76 | + GitHubAuth gitHubAuth = new GitHubAuth(this.options.GitHubPassword, this.options.GitHubUser, this.options.GitHubEmail); |
| 77 | + PullRequestCreator prCreator = new PullRequestCreator(gitHubAuth, this.options.GitHubUser); |
| 78 | + PullRequestOptions prOptions = new PullRequestOptions() |
| 79 | + { |
| 80 | + BranchNamingStrategy = new SingleBranchNamingStrategy($"UpdateDependencies-{this.options.GitHubUpstreamBranch}") |
| 81 | + }; |
| 82 | + |
| 83 | + string commitMessage = $"[{this.options.GitHubUpstreamBranch}] Update image dependencies"; |
| 84 | + |
| 85 | + await prCreator.CreateOrUpdateAsync( |
| 86 | + commitMessage, |
| 87 | + commitMessage, |
| 88 | + string.Empty, |
| 89 | + new GitHubBranch(this.options.GitHubUpstreamBranch, new GitHubProject(this.options.GitHubProject, this.options.GitHubUpstreamOwner)), |
| 90 | + new GitHubProject(this.options.GitHubProject, gitHubAuth.User), |
| 91 | + prOptions); |
| 92 | + } |
| 93 | + |
| 94 | + private static BuildDependencyInfo CreateBuildInfo(string name, string version) |
| 95 | + { |
| 96 | + return new BuildDependencyInfo( |
| 97 | + new BuildInfo |
| 98 | + { |
| 99 | + Name = name, |
| 100 | + LatestPackages = new Dictionary<string, string> { }, |
| 101 | + LatestReleaseVersion = version |
| 102 | + }, |
| 103 | + false, |
| 104 | + Enumerable.Empty<string>()); |
| 105 | + } |
| 106 | + |
| 107 | + private DependencyUpdateResults UpdateFiles(IEnumerable<IDependencyInfo> buildInfos) |
| 108 | + { |
| 109 | + List<IDependencyUpdater> updaters = new List<IDependencyUpdater>(); |
| 110 | + |
| 111 | + updaters.AddRange(CreateManifestUpdaters()); |
| 112 | + updaters.AddRange(CreateLcuUpdaters()); |
| 113 | + updaters.AddRange(CreateVsUpdaters()); |
| 114 | + updaters.AddRange(CreateNuGetUpdaters()); |
| 115 | + updaters.Add(new ReadmeUpdater()); |
| 116 | + |
| 117 | + return DependencyUpdateUtils.Update(updaters, buildInfos); |
| 118 | + } |
| 119 | + |
| 120 | + private IEnumerable<IDependencyUpdater> CreateLcuUpdaters() |
| 121 | + { |
| 122 | + LcuInfo[] lcuConfigs = JsonConvert.DeserializeObject<LcuInfo[]>( |
| 123 | + File.ReadAllText(this.options.LcuInfoPath)); |
| 124 | + |
| 125 | + const string UrlVersionGroupName = "Url"; |
| 126 | + const string CabFileVersionGroupName = "CabFile"; |
| 127 | + |
| 128 | + return dockerfiles.Value |
| 129 | + .Where(dockerfile => dockerfile.ImageVariant == RuntimeImageVariant) |
| 130 | + .Select(dockerfile => new |
| 131 | + { |
| 132 | + Dockerfile = dockerfile, |
| 133 | + LcuConfigInfo = GetLcuConfigInfo(dockerfile, lcuConfigs) |
| 134 | + }) |
| 135 | + .Where(val => val.LcuConfigInfo != null) |
| 136 | + .SelectMany(val => |
| 137 | + new IDependencyUpdater[] |
| 138 | + { |
| 139 | + new CustomFileRegexUpdater(val.LcuConfigInfo!.DownloadUrl, RuntimeImageVariant) |
| 140 | + { |
| 141 | + Path = val.Dockerfile.Path, |
| 142 | + VersionGroupName = UrlVersionGroupName, |
| 143 | + Regex = new Regex(@$"# Apply latest patch(.|\n)+(?<{UrlVersionGroupName}>http:\/\/[^\s""]+)") |
| 144 | + }, |
| 145 | + new CustomFileRegexUpdater(ParseCabFileName(val.LcuConfigInfo!.DownloadUrl), RuntimeImageVariant) |
| 146 | + { |
| 147 | + Path = val.Dockerfile.Path, |
| 148 | + VersionGroupName = CabFileVersionGroupName, |
| 149 | + Regex = new Regex(@$"# Apply latest patch(.|\n)+dism.+C:\\patch\\(?<{CabFileVersionGroupName}>\S+)", RegexOptions.IgnoreCase) |
| 150 | + } |
| 151 | + } |
| 152 | + ); |
| 153 | + } |
| 154 | + |
| 155 | + private IEnumerable<IDependencyUpdater> CreateVsUpdaters() |
| 156 | + { |
| 157 | + VsInfo vsConfig = JsonConvert.DeserializeObject<VsInfo>( |
| 158 | + File.ReadAllText(this.options.VsInfoPath)); |
| 159 | + |
| 160 | + const string TestAgentGroupName = "TestAgent"; |
| 161 | + const string BuildToolsGroupName = "BuildTools"; |
| 162 | + const string WebTargetsGroupName = "WebTargets"; |
| 163 | + |
| 164 | + return dockerfiles.Value |
| 165 | + .Where(dockerfile => dockerfile.ImageVariant == SdkImageVariant) |
| 166 | + .SelectMany(dockerfile => |
| 167 | + new IDependencyUpdater[] |
| 168 | + { |
| 169 | + new CustomFileRegexUpdater(vsConfig.TestAgentUrl, dockerfile.ImageVariant) |
| 170 | + { |
| 171 | + VersionGroupName = TestAgentGroupName, |
| 172 | + Path = dockerfile.Path, |
| 173 | + Regex = new Regex(@$"(?<{TestAgentGroupName}>https:\/\/\S+vs_TestAgent\.exe)"), |
| 174 | + }, |
| 175 | + new CustomFileRegexUpdater(vsConfig.BuildToolsUrl, dockerfile.ImageVariant) |
| 176 | + { |
| 177 | + VersionGroupName = BuildToolsGroupName, |
| 178 | + Path = dockerfile.Path, |
| 179 | + Regex = new Regex(@$"(?<{BuildToolsGroupName}>https:\/\/\S+vs_BuildTools\.exe)"), |
| 180 | + }, |
| 181 | + new CustomFileRegexUpdater(vsConfig.WebTargetsUrl, dockerfile.ImageVariant) |
| 182 | + { |
| 183 | + VersionGroupName = WebTargetsGroupName, |
| 184 | + Path = dockerfile.Path, |
| 185 | + Regex = new Regex(@$"# Install web targets(.|\n)+?(?<{WebTargetsGroupName}>https:\/\/\S+)"), |
| 186 | + } |
| 187 | + }); |
| 188 | + } |
| 189 | + |
| 190 | + private static string ParseCabFileName(string lcuDownloadUrl) |
| 191 | + { |
| 192 | + string msuFilename = lcuDownloadUrl.Substring(lcuDownloadUrl.LastIndexOf("/") + 1); |
| 193 | + return msuFilename.Substring(0, msuFilename.IndexOf("_")) + ".cab"; |
| 194 | + } |
| 195 | + |
| 196 | + private static LcuInfo? GetLcuConfigInfo(DockerfileInfo dockerfile, LcuInfo[] lcuConfigs) |
| 197 | + { |
| 198 | + return lcuConfigs |
| 199 | + .FirstOrDefault(config => config.OsVersion == dockerfile.OsVersion && |
| 200 | + config.RuntimeVersions.Any(runtime => runtime == dockerfile.FrameworkVersion)); |
| 201 | + } |
| 202 | + |
| 203 | + private IEnumerable<IDependencyUpdater> CreateNuGetUpdaters() |
| 204 | + { |
| 205 | + NuGetInfo[] nuGetVersions = JsonConvert.DeserializeObject<NuGetInfo[]>( |
| 206 | + File.ReadAllText(this.options.NuGetInfoPath)); |
| 207 | + |
| 208 | + const string NuGetVersionGroupName = "version"; |
| 209 | + |
| 210 | + return dockerfiles.Value |
| 211 | + .Where(dockerfile => dockerfile.ImageVariant == SdkImageVariant) |
| 212 | + .Select(dockerfile => |
| 213 | + { |
| 214 | + // Find the NuGetInfo that matches the OS version of this Dockerfile |
| 215 | + NuGetInfo nuGetInfo = nuGetVersions.FirstOrDefault(ver => ver.OsVersions.Contains(dockerfile.OsVersion)); |
| 216 | + if (nuGetInfo is null) |
| 217 | + { |
| 218 | + throw new InvalidOperationException($"No NuGet info is specified in '{this.options.NuGetInfoPath}' for OS version '{dockerfile.OsVersion}'."); |
| 219 | + } |
| 220 | + |
| 221 | + return new CustomFileRegexUpdater(nuGetInfo.NuGetClientVersion, dockerfile.ImageVariant) |
| 222 | + { |
| 223 | + Path = dockerfile.Path, |
| 224 | + VersionGroupName = NuGetVersionGroupName, |
| 225 | + Regex = new Regex(@$"ENV NUGET_VERSION (?<{NuGetVersionGroupName}>\d+\.\d+\.\d+)") |
| 226 | + }; |
| 227 | + }); |
| 228 | + } |
| 229 | + |
| 230 | + private IEnumerable<IDependencyUpdater> CreateManifestUpdaters() |
| 231 | + { |
| 232 | + const string RuntimePrefix = "Runtime"; |
| 233 | + const string SdkPrefix = "Sdk"; |
| 234 | + const string AspnetPrefix = "Aspnet"; |
| 235 | + const string WcfPrefix = "Wcf"; |
| 236 | + |
| 237 | + if (this.options.DateStampAll != null) |
| 238 | + { |
| 239 | + yield return CreateManifestUpdater(RuntimePrefix, RuntimeImageVariant); |
| 240 | + yield return CreateManifestUpdater(SdkPrefix, SdkImageVariant); |
| 241 | + yield return CreateManifestUpdater(AspnetPrefix, AspnetImageVariant); |
| 242 | + yield return CreateManifestUpdater(WcfPrefix, WcfImageVariant); |
| 243 | + } |
| 244 | + else |
| 245 | + { |
| 246 | + if (this.options.DateStampRuntime != null) |
| 247 | + { |
| 248 | + yield return CreateManifestUpdater(RuntimePrefix, RuntimeImageVariant); |
| 249 | + } |
| 250 | + |
| 251 | + if (this.options.DateStampSdk != null) |
| 252 | + { |
| 253 | + yield return CreateManifestUpdater(SdkPrefix, SdkImageVariant); |
| 254 | + } |
| 255 | + |
| 256 | + if (this.options.DateStampAspnet != null) |
| 257 | + { |
| 258 | + yield return CreateManifestUpdater(AspnetPrefix, AspnetImageVariant); |
| 259 | + } |
| 260 | + |
| 261 | + if (this.options.DateStampWcf != null) |
| 262 | + { |
| 263 | + yield return CreateManifestUpdater(WcfPrefix, WcfImageVariant); |
| 264 | + } |
| 265 | + } |
| 266 | + } |
| 267 | + |
| 268 | + private static IDependencyUpdater CreateManifestUpdater(string dateStampVariablePrefix, string buildInfoName) |
| 269 | + { |
| 270 | + const string TagDateStampGroupName = "tagDateStampValue"; |
| 271 | + |
| 272 | + return new FileRegexReleaseUpdater |
| 273 | + { |
| 274 | + Path = Path.Combine(Program.RepoRoot, "manifest.json"), |
| 275 | + BuildInfoName = buildInfoName, |
| 276 | + Regex = new Regex($"\"{dateStampVariablePrefix}ReleaseDateStamp\": \"(?<{TagDateStampGroupName}>\\d{{8}})\""), |
| 277 | + VersionGroupName = TagDateStampGroupName |
| 278 | + }; |
| 279 | + } |
| 280 | + |
| 281 | + private class DockerfileInfo |
| 282 | + { |
| 283 | + public DockerfileInfo(string path) |
| 284 | + { |
| 285 | + this.Path = path; |
| 286 | + |
| 287 | + string[] pathParts = path.Substring(Program.RepoRoot.Length + 1) |
| 288 | + .Replace(@"\", "/") |
| 289 | + .Split("/"); |
| 290 | + |
| 291 | + this.FrameworkVersion = pathParts[0]; |
| 292 | + this.ImageVariant = pathParts[1]; |
| 293 | + this.OsVersion = pathParts[2]; |
| 294 | + } |
| 295 | + |
| 296 | + public string Path { get; } |
| 297 | + public string FrameworkVersion { get; } |
| 298 | + public string OsVersion { get; } |
| 299 | + public string ImageVariant { get; } |
| 300 | + } |
| 301 | + } |
| 302 | +} |
0 commit comments