| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Net.Http;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Xml;
- using System.Xml.Linq;
- using Microsoft.Extensions.CommandLineUtils;
- using NuGet.Common;
- using NuGet.Configuration;
- using NuGet.Packaging;
- using NuGet.Protocol;
- using NuGet.Protocol.Core.Types;
- using NuGet.Versioning;
- namespace PackageBaselineGenerator;
- /// <summary>
- /// This generates Baseline.props with information about the last RTM release.
- /// </summary>
- class Program : CommandLineApplication
- {
- static void Main(string[] args)
- {
- new Program().Execute(args);
- }
- private readonly CommandOption _sources;
- private readonly CommandOption _output;
- private readonly CommandOption _update;
- private static readonly string[] _defaultSources = new string[] { "https://api.nuget.org/v3/index.json" };
- public Program()
- {
- _sources = Option(
- "-s|--package-sources <Sources>",
- "The NuGet source(s) of packages to fetch",
- CommandOptionType.MultipleValue);
- _output = Option("-o|--output <OUT>", "The generated file output path", CommandOptionType.SingleValue);
- _update = Option("-u|--update", "Regenerate the input (Baseline.xml) file.", CommandOptionType.NoValue);
- Invoke = () => Run().GetAwaiter().GetResult();
- }
- private async Task<int> Run()
- {
- if (_output.HasValue() && _update.HasValue())
- {
- await Error.WriteLineAsync("'--output' and '--update' options must not be used together.");
- return 1;
- }
- var inputPath = Path.Combine(Directory.GetCurrentDirectory(), "Baseline.xml");
- var input = XDocument.Load(inputPath);
- var sources = _sources.HasValue() ? _sources.Values.Select(s => s.TrimEnd('/')) : _defaultSources;
- var packageSources = sources.Select(s => new PackageSource(s));
- var providers = Repository.Provider.GetCoreV3(); // Get v2 and v3 API support
- var sourceRepositories = packageSources.Select(ps => new SourceRepository(ps, providers));
- if (_update.HasValue())
- {
- var updateResult = await RunUpdateAsync(inputPath, input, sourceRepositories);
- if (updateResult != 0)
- {
- return updateResult;
- }
- }
- List<(string packageBase, bool feedV3)> packageBases = new List<(string, bool)>();
- foreach (var sourceRepository in sourceRepositories)
- {
- var feedType = await sourceRepository.GetFeedType(CancellationToken.None);
- var feedV3 = feedType == FeedType.HttpV3;
- var packageBase = sourceRepository.PackageSource + "/package";
- if (feedV3)
- {
- var resources = await sourceRepository.GetResourceAsync<ServiceIndexResourceV3>();
- packageBase = resources.GetServiceEntryUri(ServiceTypes.PackageBaseAddress).ToString().TrimEnd('/');
- }
- packageBases.Add((packageBase, feedV3));
- }
- var output = _output.HasValue()
- ? _output.Value()
- : Path.Combine(Directory.GetCurrentDirectory(), "Baseline.Designer.props");
- var packageCache = Environment.GetEnvironmentVariable("NUGET_PACKAGES") ??
- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
- var tempDir = Path.Combine(Directory.GetCurrentDirectory(), "obj", "tmp");
- Directory.CreateDirectory(tempDir);
- var baselineVersion = input.Root.Attribute("Version").Value;
- // Baseline and .NET Core versions always align in non-preview releases.
- var parsedVersion = Version.Parse(baselineVersion);
- var defaultTarget = ((parsedVersion.Major < 5) ? "netcoreapp" : "net") +
- $"{parsedVersion.Major}.{parsedVersion.Minor}";
- var doc = new XDocument(
- new XComment(" Auto generated. Do not edit manually, use eng/tools/BaselineGenerator/ to recreate. "),
- new XElement("Project",
- new XElement("PropertyGroup",
- new XElement("MSBuildAllProjects", "$(MSBuildAllProjects);$(MSBuildThisFileFullPath)"),
- new XElement("AspNetCoreBaselineVersion", baselineVersion))));
- var client = new HttpClient();
- foreach (var pkg in input.Root.Descendants("Package"))
- {
- var id = pkg.Attribute("Id").Value;
- var version = pkg.Attribute("Version").Value;
- var packageFileName = $"{id}.{version}.nupkg";
- var nupkgPath = Path.Combine(packageCache, id.ToLowerInvariant(), version, packageFileName);
- if (!File.Exists(nupkgPath))
- {
- nupkgPath = Path.Combine(tempDir, packageFileName);
- }
- if (!File.Exists(nupkgPath))
- {
- foreach ((string packageBase, bool feedV3) in packageBases)
- {
- var url = feedV3 ?
- $"{packageBase}/{id.ToLowerInvariant()}/{version}/{id.ToLowerInvariant()}.{version}.nupkg" :
- $"{packageBase}/{id}/{version}";
- Console.WriteLine($"Downloading {url}");
- try
- {
- using (var response = await client.GetStreamAsync(url))
- {
- using (var file = File.Create(nupkgPath))
- {
- await response.CopyToAsync(file);
- }
- }
- }
- catch (HttpRequestException e) when (e.StatusCode == System.Net.HttpStatusCode.NotFound)
- {
- // If it's not found, continue onto the next one.
- continue;
- }
- }
- if (!File.Exists(nupkgPath))
- {
- throw new Exception($"Could not download package {id} @ {version} using any input feed");
- }
- }
- using (var reader = new PackageArchiveReader(nupkgPath))
- {
- doc.Root.Add(new XComment($" Package: {id}"));
- var propertyGroup = new XElement(
- "PropertyGroup",
- new XAttribute("Condition", $" '$(PackageId)' == '{id}' "),
- new XElement("BaselinePackageVersion", version));
- doc.Root.Add(propertyGroup);
- foreach (var group in reader.NuspecReader.GetDependencyGroups())
- {
- // Don't bother generating empty ItemGroup elements.
- if (!group.Packages.Any())
- {
- continue;
- }
- // Handle changes to $(DefaultNetCoreTargetFramework) even if some projects are held back.
- var targetCondition = $"'$(TargetFramework)' == '{group.TargetFramework.GetShortFolderName()}'";
- if (string.Equals(
- group.TargetFramework.GetShortFolderName(),
- defaultTarget,
- StringComparison.OrdinalIgnoreCase))
- {
- targetCondition =
- $"('$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)' OR '$(TargetFramework)' == '{defaultTarget}')";
- }
- var itemGroup = new XElement(
- "ItemGroup",
- new XAttribute("Condition", $" '$(PackageId)' == '{id}' AND {targetCondition} "));
- doc.Root.Add(itemGroup);
- foreach (var dependency in group.Packages)
- {
- itemGroup.Add(
- new XElement("BaselinePackageReference",
- new XAttribute("Include", dependency.Id),
- new XAttribute("Version", dependency.VersionRange.ToString())));
- }
- }
- }
- }
- var settings = new XmlWriterSettings
- {
- OmitXmlDeclaration = true,
- Encoding = Encoding.UTF8,
- Indent = true,
- };
- using (var writer = XmlWriter.Create(output, settings))
- {
- doc.Save(writer);
- }
- Console.WriteLine($"Generated file in {output}");
- return 0;
- }
- private async Task<int> RunUpdateAsync(
- string documentPath,
- XDocument document,
- IEnumerable<SourceRepository> sourceRepositories)
- {
- var packageMetadataResources = await Task.WhenAll(sourceRepositories.Select(async sr =>
- await sr.GetResourceAsync<PackageMetadataResource>()));
- var logger = new Logger(Error, Out);
- var hasChanged = false;
- using (var cacheContext = new SourceCacheContext { NoCache = true })
- {
- var versionAttribute = document.Root.Attribute("Version");
- hasChanged = await TryUpdateVersionAsync(
- versionAttribute,
- "Microsoft.AspNetCore.App.Runtime.win-x64",
- packageMetadataResources,
- logger,
- cacheContext);
- foreach (var package in document.Root.Descendants("Package"))
- {
- var id = package.Attribute("Id").Value;
- versionAttribute = package.Attribute("Version");
- var attributeChanged = await TryUpdateVersionAsync(
- versionAttribute,
- id,
- packageMetadataResources,
- logger,
- cacheContext);
- hasChanged |= attributeChanged;
- }
- }
- if (hasChanged)
- {
- await Out.WriteLineAsync($"Updating {documentPath}.");
- var settings = new XmlWriterSettings
- {
- Async = true,
- CheckCharacters = true,
- CloseOutput = false,
- Encoding = Encoding.UTF8,
- Indent = true,
- IndentChars = " ",
- NewLineOnAttributes = false,
- OmitXmlDeclaration = true,
- WriteEndDocumentOnClose = true,
- };
- using (var stream = File.OpenWrite(documentPath))
- {
- using (var writer = XmlWriter.Create(stream, settings))
- {
- await document.SaveAsync(writer, CancellationToken.None);
- }
- }
- }
- else
- {
- await Out.WriteLineAsync("No new versions found");
- }
- return 0;
- }
- private static async Task<bool> TryUpdateVersionAsync(
- XAttribute versionAttribute,
- string packageId,
- IEnumerable<PackageMetadataResource> packageMetadataResources,
- ILogger logger,
- SourceCacheContext cacheContext)
- {
- var currentVersion = NuGetVersion.Parse(versionAttribute.Value);
- var versionRange = new VersionRange(
- currentVersion,
- new FloatRange(NuGetVersionFloatBehavior.Patch, currentVersion));
- var searchMetadatas = await Task.WhenAll(
- packageMetadataResources.Select(async pmr => await pmr.GetMetadataAsync(
- packageId,
- includePrerelease: false,
- includeUnlisted: true, // Microsoft.AspNetCore.DataOrotection.Redis package is not listed.
- sourceCacheContext: cacheContext,
- log: logger,
- token: CancellationToken.None)));
- // Find the latest version among each search metadata
- NuGetVersion latestVersion = null;
- foreach (var searchMetadata in searchMetadatas)
- {
- var potentialLatestVersion = versionRange.FindBestMatch(
- searchMetadata.Select(metadata => metadata.Identity.Version));
- if (latestVersion == null ||
- (potentialLatestVersion != null && potentialLatestVersion.CompareTo(latestVersion) > 0))
- {
- latestVersion = potentialLatestVersion;
- }
- }
- if (latestVersion == null)
- {
- logger.LogWarning($"Unable to find latest version of '{packageId}'.");
- return false;
- }
- var hasChanged = false;
- if (latestVersion != currentVersion)
- {
- hasChanged = true;
- versionAttribute.Value = latestVersion.ToNormalizedString();
- }
- return hasChanged;
- }
- private class Logger : ILogger
- {
- private readonly TextWriter _error;
- private readonly TextWriter _out;
- public Logger(TextWriter error, TextWriter @out)
- {
- _error = error;
- _out = @out;
- }
- public void Log(LogLevel level, string data)
- {
- switch (level)
- {
- case LogLevel.Debug:
- LogDebug(data);
- break;
- case LogLevel.Error:
- LogError(data);
- break;
- case LogLevel.Information:
- LogInformation(data);
- break;
- case LogLevel.Minimal:
- LogMinimal(data);
- break;
- case LogLevel.Verbose:
- LogVerbose(data);
- break;
- case LogLevel.Warning:
- LogWarning(data);
- break;
- }
- }
- public void Log(ILogMessage message) => Log(message.Level, message.Message);
- public Task LogAsync(LogLevel level, string data)
- {
- Log(level, data);
- return Task.CompletedTask;
- }
- public Task LogAsync(ILogMessage message) => LogAsync(message.Level, message.Message);
- public void LogDebug(string data) => _out.WriteLine($"Debug: {data}");
- public void LogError(string data) => _error.WriteLine($"Error: {data}");
- public void LogInformation(string data) => _out.WriteLine($"Information: {data}");
- public void LogInformationSummary(string data) => _out.WriteLine($"Summary: {data}");
- public void LogMinimal(string data) => _out.WriteLine($"Minimal: {data}");
- public void LogVerbose(string data) => _out.WriteLine($"Verbose: {data}");
- public void LogWarning(string data) => _out.WriteLine($"Warning: {data}");
- }
- }
|