| File: Commands\New\BuiltInTemplatePackageProvider.cs | Web Access |
| Project: src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.DotNet.Cli.Utils; using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Abstractions.TemplatePackage; using NuGet.Versioning; namespace Microsoft.DotNet.Cli.Commands.New; /// <summary> /// Returns list of *.nupkg files from C:\Program Files\dotnet\templates\x.x.x.x\ (on Windows) to be installed. /// </summary> internal sealed class BuiltInTemplatePackageProvider(BuiltInTemplatePackageProviderFactory factory, IEngineEnvironmentSettings settings) : ITemplatePackageProvider { private readonly IEngineEnvironmentSettings _environmentSettings = settings; public ITemplatePackageProviderFactory Factory { get; } = factory; /// <summary> /// We don't trigger this event, we could complicate our life with FileSystemWatcher. /// But since "dotnet new" is short lived process is not worth it, plus it would cause some perf hit... /// To avoid warnings about being unused, implement empty add/remove accessors. /// </summary> public event Action? TemplatePackagesChanged { add { } remove { } } public Task<IReadOnlyList<ITemplatePackage>> GetAllTemplatePackagesAsync(CancellationToken cancellationToken) { var packages = new List<ITemplatePackage>(); foreach (string templateFolder in GetTemplateFolders(_environmentSettings)) { foreach (string nupkgPath in Directory.EnumerateFiles(templateFolder, "*.nupkg", SearchOption.TopDirectoryOnly)) { packages.Add(new TemplatePackage(this, nupkgPath, File.GetLastWriteTime(nupkgPath))); } } return Task.FromResult<IReadOnlyList<ITemplatePackage>>(packages); } private static IEnumerable<string> GetTemplateFolders(IEngineEnvironmentSettings environmentSettings) { var templateFoldersToInstall = new List<string>(); var sdksDirectory = new DirectoryInfo(MSBuildForwardingAppWithoutLogging.GetMSBuildSDKsPath()); var sdkDirectory = sdksDirectory.Parent; var sdkPath = sdkDirectory?.FullName ?? string.Empty; var dotnetRootPath = sdkDirectory?.Parent?.Parent?.FullName ?? string.Empty; // First grab templates from dotnet\templates\M.m folders, in ascending order, up to our version string templatesRootFolder = Path.Combine(dotnetRootPath, "templates"); if (Directory.Exists(templatesRootFolder)) { IReadOnlyDictionary<string, SemanticVersion> parsedNames = GetVersionDirectoriesInDirectory(templatesRootFolder); IList<string> versionedFolders = GetBestVersionsByMajorMinor(parsedNames); templateFoldersToInstall.AddRange(versionedFolders .Select(versionedFolder => Path.Combine(templatesRootFolder, versionedFolder))); } // Now grab templates from our base folder, if present. string templatesDir = Path.Combine(sdkPath, "Templates"); if (Directory.Exists(templatesDir)) { templateFoldersToInstall.Add(templatesDir); } return templateFoldersToInstall; } // Returns a dictionary of fileName -> Parsed version info // including all the directories in the input directory whose names are parse-able as versions. private static IReadOnlyDictionary<string, SemanticVersion> GetVersionDirectoriesInDirectory(string fullPath) { var versionFileInfo = new Dictionary<string, SemanticVersion>(); foreach (string directory in Directory.EnumerateDirectories(fullPath, "*.*", SearchOption.TopDirectoryOnly)) { if (SemanticVersion.TryParse(Path.GetFileName(directory), out SemanticVersion? versionInfo) && versionInfo is not null) { versionFileInfo.Add(directory, versionInfo); } } return versionFileInfo; } internal static IList<string> GetBestVersionsByMajorMinor(IReadOnlyDictionary<string, SemanticVersion> versionDirInfo) { IDictionary<string, (string path, SemanticVersion version)> bestVersionsByBucket = new Dictionary<string, (string path, SemanticVersion version)>(); Version? sdkVersion = typeof(NewCommandParser).Assembly.GetName().Version; foreach (KeyValuePair<string, SemanticVersion> dirInfo in versionDirInfo) { var majorMinorDirVersion = new Version(dirInfo.Value.Major, dirInfo.Value.Minor); // restrict the results to not include from higher versions of the runtime/templates then the SDK if (majorMinorDirVersion <= sdkVersion) { string coreAppVersion = $"{dirInfo.Value.Major}.{dirInfo.Value.Minor}"; if (!bestVersionsByBucket.TryGetValue(coreAppVersion, out (string path, SemanticVersion version) currentHighest) || dirInfo.Value.CompareTo(currentHighest.version) > 0) { bestVersionsByBucket[coreAppVersion] = (dirInfo.Key, dirInfo.Value); } } } return [.. bestVersionsByBucket.OrderBy(x => x.Value.version).Select(x => x.Value.path)]; } }