Program.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net.Http;
  7. using System.Text;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using System.Xml;
  11. using System.Xml.Linq;
  12. using Microsoft.Extensions.CommandLineUtils;
  13. using NuGet.Common;
  14. using NuGet.Configuration;
  15. using NuGet.Packaging;
  16. using NuGet.Protocol;
  17. using NuGet.Protocol.Core.Types;
  18. using NuGet.Versioning;
  19. namespace PackageBaselineGenerator
  20. {
  21. /// <summary>
  22. /// This generates Baseline.props with information about the last RTM release.
  23. /// </summary>
  24. class Program : CommandLineApplication
  25. {
  26. static void Main(string[] args)
  27. {
  28. new Program().Execute(args);
  29. }
  30. private readonly CommandOption _source;
  31. private readonly CommandOption _output;
  32. private readonly CommandOption _update;
  33. public Program()
  34. {
  35. _source = Option(
  36. "-s|--package-source <SOURCE>",
  37. "The NuGet source of packages to fetch",
  38. CommandOptionType.SingleValue);
  39. _output = Option("-o|--output <OUT>", "The generated file output path", CommandOptionType.SingleValue);
  40. _update = Option("-u|--update", "Regenerate the input (Baseline.xml) file.", CommandOptionType.NoValue);
  41. Invoke = () => Run().GetAwaiter().GetResult();
  42. }
  43. private async Task<int> Run()
  44. {
  45. if (_output.HasValue() && _update.HasValue())
  46. {
  47. await Error.WriteLineAsync("'--output' and '--update' options must not be used together.");
  48. return 1;
  49. }
  50. var inputPath = Path.Combine(Directory.GetCurrentDirectory(), "Baseline.xml");
  51. var input = XDocument.Load(inputPath);
  52. var source = _source.HasValue() ? _source.Value().TrimEnd('/') : "https://api.nuget.org/v3/index.json";
  53. var packageSource = new PackageSource(source);
  54. var providers = Repository.Provider.GetCoreV3(); // Get v2 and v3 API support
  55. var sourceRepository = new SourceRepository(packageSource, providers);
  56. if (_update.HasValue())
  57. {
  58. var updateResult = await RunUpdateAsync(inputPath, input, sourceRepository);
  59. if (updateResult != 0)
  60. {
  61. return updateResult;
  62. }
  63. }
  64. var feedType = await sourceRepository.GetFeedType(CancellationToken.None);
  65. var feedV3 = feedType == FeedType.HttpV3;
  66. var packageBase = source + "/package";
  67. if (feedV3)
  68. {
  69. var resources = await sourceRepository.GetResourceAsync<ServiceIndexResourceV3>();
  70. packageBase = resources.GetServiceEntryUri(ServiceTypes.PackageBaseAddress).ToString().TrimEnd('/');
  71. }
  72. var output = _output.HasValue()
  73. ? _output.Value()
  74. : Path.Combine(Directory.GetCurrentDirectory(), "Baseline.Designer.props");
  75. var packageCache = Environment.GetEnvironmentVariable("NUGET_PACKAGES") ??
  76. Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
  77. var tempDir = Path.Combine(Directory.GetCurrentDirectory(), "obj", "tmp");
  78. Directory.CreateDirectory(tempDir);
  79. var baselineVersion = input.Root.Attribute("Version").Value;
  80. // Baseline and .NET Core versions always align in non-preview releases.
  81. var parsedVersion = Version.Parse(baselineVersion);
  82. var defaultTarget = ((parsedVersion.Major < 5) ? "netcoreapp" : "net") +
  83. $"{parsedVersion.Major}.{parsedVersion.Minor}";
  84. var doc = new XDocument(
  85. new XComment(" Auto generated. Do not edit manually, use eng/tools/BaselineGenerator/ to recreate. "),
  86. new XElement("Project",
  87. new XElement("PropertyGroup",
  88. new XElement("MSBuildAllProjects", "$(MSBuildAllProjects);$(MSBuildThisFileFullPath)"),
  89. new XElement("AspNetCoreBaselineVersion", baselineVersion))));
  90. var client = new HttpClient();
  91. foreach (var pkg in input.Root.Descendants("Package"))
  92. {
  93. var id = pkg.Attribute("Id").Value;
  94. var version = pkg.Attribute("Version").Value;
  95. var packageFileName = $"{id}.{version}.nupkg";
  96. var nupkgPath = Path.Combine(packageCache, id.ToLowerInvariant(), version, packageFileName);
  97. if (!File.Exists(nupkgPath))
  98. {
  99. nupkgPath = Path.Combine(tempDir, packageFileName);
  100. }
  101. if (!File.Exists(nupkgPath))
  102. {
  103. var url = feedV3 ?
  104. $"{packageBase}/{id.ToLowerInvariant()}/{version}/{id.ToLowerInvariant()}.{version}.nupkg" :
  105. $"{packageBase}/{id}/{version}";
  106. Console.WriteLine($"Downloading {url}");
  107. using (var response = await client.GetStreamAsync(url))
  108. {
  109. using (var file = File.Create(nupkgPath))
  110. {
  111. await response.CopyToAsync(file);
  112. }
  113. }
  114. }
  115. using (var reader = new PackageArchiveReader(nupkgPath))
  116. {
  117. doc.Root.Add(new XComment($" Package: {id}"));
  118. var propertyGroup = new XElement(
  119. "PropertyGroup",
  120. new XAttribute("Condition", $" '$(PackageId)' == '{id}' "),
  121. new XElement("BaselinePackageVersion", version));
  122. doc.Root.Add(propertyGroup);
  123. foreach (var group in reader.NuspecReader.GetDependencyGroups())
  124. {
  125. // Don't bother generating empty ItemGroup elements.
  126. if (group.Packages.Count() == 0)
  127. {
  128. continue;
  129. }
  130. // Handle changes to $(DefaultNetCoreTargetFramework) even if some projects are held back.
  131. var targetCondition = $"'$(TargetFramework)' == '{group.TargetFramework.GetShortFolderName()}'";
  132. if (string.Equals(
  133. group.TargetFramework.GetShortFolderName(),
  134. defaultTarget,
  135. StringComparison.OrdinalIgnoreCase))
  136. {
  137. targetCondition =
  138. $"('$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)' OR {targetCondition})";
  139. }
  140. var itemGroup = new XElement(
  141. "ItemGroup",
  142. new XAttribute("Condition", $" '$(PackageId)' == '{id}' AND {targetCondition} "));
  143. doc.Root.Add(itemGroup);
  144. foreach (var dependency in group.Packages)
  145. {
  146. itemGroup.Add(
  147. new XElement("BaselinePackageReference",
  148. new XAttribute("Include", dependency.Id),
  149. new XAttribute("Version", dependency.VersionRange.ToString())));
  150. }
  151. }
  152. }
  153. }
  154. var settings = new XmlWriterSettings
  155. {
  156. OmitXmlDeclaration = true,
  157. Encoding = Encoding.UTF8,
  158. Indent = true,
  159. };
  160. using (var writer = XmlWriter.Create(output, settings))
  161. {
  162. doc.Save(writer);
  163. }
  164. Console.WriteLine($"Generated file in {output}");
  165. return 0;
  166. }
  167. private async Task<int> RunUpdateAsync(
  168. string documentPath,
  169. XDocument document,
  170. SourceRepository sourceRepository)
  171. {
  172. var packageMetadataResource = await sourceRepository.GetResourceAsync<PackageMetadataResource>();
  173. var logger = new Logger(Error, Out);
  174. var hasChanged = false;
  175. using (var cacheContext = new SourceCacheContext { NoCache = true })
  176. {
  177. var versionAttribute = document.Root.Attribute("Version");
  178. hasChanged = await TryUpdateVersionAsync(
  179. versionAttribute,
  180. "Microsoft.AspNetCore.App.Runtime.win-x64",
  181. packageMetadataResource,
  182. logger,
  183. cacheContext);
  184. foreach (var package in document.Root.Descendants("Package"))
  185. {
  186. var id = package.Attribute("Id").Value;
  187. versionAttribute = package.Attribute("Version");
  188. var attributeChanged = await TryUpdateVersionAsync(
  189. versionAttribute,
  190. id,
  191. packageMetadataResource,
  192. logger,
  193. cacheContext);
  194. hasChanged |= attributeChanged;
  195. }
  196. }
  197. if (hasChanged)
  198. {
  199. await Out.WriteLineAsync($"Updating {documentPath}.");
  200. var settings = new XmlWriterSettings
  201. {
  202. Async = true,
  203. CheckCharacters = true,
  204. CloseOutput = false,
  205. Encoding = Encoding.UTF8,
  206. Indent = true,
  207. IndentChars = " ",
  208. NewLineOnAttributes = false,
  209. OmitXmlDeclaration = true,
  210. WriteEndDocumentOnClose = true,
  211. };
  212. using (var stream = File.OpenWrite(documentPath))
  213. {
  214. using (var writer = XmlWriter.Create(stream, settings))
  215. {
  216. await document.SaveAsync(writer, CancellationToken.None);
  217. }
  218. }
  219. }
  220. else
  221. {
  222. await Out.WriteLineAsync("No new versions found");
  223. }
  224. return 0;
  225. }
  226. private static async Task<bool> TryUpdateVersionAsync(
  227. XAttribute versionAttribute,
  228. string packageId,
  229. PackageMetadataResource packageMetadataResource,
  230. ILogger logger,
  231. SourceCacheContext cacheContext)
  232. {
  233. var searchMetadata = await packageMetadataResource.GetMetadataAsync(
  234. packageId,
  235. includePrerelease: false,
  236. includeUnlisted: true, // Microsoft.AspNetCore.DataOrotection.Redis package is not listed.
  237. sourceCacheContext: cacheContext,
  238. log: logger,
  239. token: CancellationToken.None);
  240. var currentVersion = NuGetVersion.Parse(versionAttribute.Value);
  241. var versionRange = new VersionRange(
  242. currentVersion,
  243. new FloatRange(NuGetVersionFloatBehavior.Patch, currentVersion));
  244. var latestVersion = versionRange.FindBestMatch(
  245. searchMetadata.Select(metadata => metadata.Identity.Version));
  246. if (latestVersion == null)
  247. {
  248. logger.LogWarning($"Unable to find latest version of '{packageId}'.");
  249. return false;
  250. }
  251. var hasChanged = false;
  252. if (latestVersion != currentVersion)
  253. {
  254. hasChanged = true;
  255. versionAttribute.Value = latestVersion.ToNormalizedString();
  256. }
  257. return hasChanged;
  258. }
  259. private class Logger : ILogger
  260. {
  261. private readonly TextWriter _error;
  262. private readonly TextWriter _out;
  263. public Logger(TextWriter error, TextWriter @out)
  264. {
  265. _error = error;
  266. _out = @out;
  267. }
  268. public void Log(LogLevel level, string data)
  269. {
  270. switch (level)
  271. {
  272. case LogLevel.Debug:
  273. LogDebug(data);
  274. break;
  275. case LogLevel.Error:
  276. LogError(data);
  277. break;
  278. case LogLevel.Information:
  279. LogInformation(data);
  280. break;
  281. case LogLevel.Minimal:
  282. LogMinimal(data);
  283. break;
  284. case LogLevel.Verbose:
  285. LogVerbose(data);
  286. break;
  287. case LogLevel.Warning:
  288. LogWarning(data);
  289. break;
  290. }
  291. }
  292. public void Log(ILogMessage message) => Log(message.Level, message.Message);
  293. public Task LogAsync(LogLevel level, string data)
  294. {
  295. Log(level, data);
  296. return Task.CompletedTask;
  297. }
  298. public Task LogAsync(ILogMessage message) => LogAsync(message.Level, message.Message);
  299. public void LogDebug(string data) => _out.WriteLine($"Debug: {data}");
  300. public void LogError(string data) => _error.WriteLine($"Error: {data}");
  301. public void LogInformation(string data) => _out.WriteLine($"Information: {data}");
  302. public void LogInformationSummary(string data) => _out.WriteLine($"Summary: {data}");
  303. public void LogMinimal(string data) => _out.WriteLine($"Minimal: {data}");
  304. public void LogVerbose(string data) => _out.WriteLine($"Verbose: {data}");
  305. public void LogWarning(string data) => _out.WriteLine($"Warning: {data}");
  306. }
  307. }
  308. }