| File: ToolManifest\ToolManifestFinder.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. #nullable disable using Microsoft.DotNet.Cli.ToolPackage; using Microsoft.DotNet.Cli.Utils; using Microsoft.Extensions.EnvironmentAbstractions; namespace Microsoft.DotNet.Cli.ToolManifest; internal class ToolManifestFinder : IToolManifestFinder, IToolManifestInspector { private readonly DirectoryPath _probeStart; private readonly IFileSystem _fileSystem; private readonly IDangerousFileDetector _dangerousFileDetector; private readonly ToolManifestEditor _toolManifestEditor; private const string ManifestFilenameConvention = "dotnet-tools.json"; private readonly Func<string, string> _getEnvironmentVariable; public ToolManifestFinder( DirectoryPath probeStart, IFileSystem fileSystem = null, IDangerousFileDetector dangerousFileDetector = null, Func<string, string> getEnvironmentVariable = null) { _probeStart = probeStart; _fileSystem = fileSystem ?? new FileSystemWrapper(); _dangerousFileDetector = dangerousFileDetector ?? new DangerousFileDetector(); _toolManifestEditor = new ToolManifestEditor(_fileSystem, dangerousFileDetector); _getEnvironmentVariable = getEnvironmentVariable ?? Environment.GetEnvironmentVariable; } public IReadOnlyCollection<ToolManifestPackage> Find(FilePath? filePath = null) { IEnumerable<(FilePath manifestfile, DirectoryPath _)> allPossibleManifests = filePath != null ? [(filePath.Value, filePath.Value.GetDirectoryPath())] : EnumerateDefaultAllPossibleManifests(); var findAnyManifest = TryFindToolManifestPackages(allPossibleManifests, out var toolManifestPackageAndSource); if (!findAnyManifest) { throw new ToolManifestCannotBeFoundException(string.Format(CliStrings.CannotFindAManifestFile, string.Join(Environment.NewLine, allPossibleManifests.Select(f => "\t" + f.manifestfile.Value)))); } return [.. toolManifestPackageAndSource.Select(t => t.toolManifestPackage)]; } public IReadOnlyCollection<(ToolManifestPackage toolManifestPackage, FilePath SourceManifest)> Inspect( FilePath? filePath = null) { IEnumerable<(FilePath manifestfile, DirectoryPath _)> allPossibleManifests = filePath != null ? [(filePath.Value, filePath.Value.GetDirectoryPath())] : EnumerateDefaultAllPossibleManifests(); if (!TryFindToolManifestPackages(allPossibleManifests, out var toolManifestPackageAndSource)) { toolManifestPackageAndSource = []; } return [.. toolManifestPackageAndSource]; } private bool TryFindToolManifestPackages( IEnumerable<(FilePath manifestfile, DirectoryPath _)> allPossibleManifests, out List<(ToolManifestPackage toolManifestPackage, FilePath SourceManifest)> toolManifestPackageAndSource) { bool findAnyManifest = false; toolManifestPackageAndSource = []; foreach ((FilePath possibleManifest, DirectoryPath correspondingDirectory) in allPossibleManifests) { if (!_fileSystem.File.Exists(possibleManifest.Value)) { continue; } findAnyManifest = true; (List<ToolManifestPackage> toolManifestPackageFromOneManifestFile, bool isRoot) = _toolManifestEditor.Read(possibleManifest, correspondingDirectory); foreach (ToolManifestPackage toolManifestPackage in toolManifestPackageFromOneManifestFile) { if (!toolManifestPackageAndSource.Any(addedToolManifestPackages => addedToolManifestPackages.toolManifestPackage.PackageId.Equals(toolManifestPackage.PackageId))) { toolManifestPackageAndSource.Add((toolManifestPackage, possibleManifest)); } } if (isRoot) { return findAnyManifest; } } return findAnyManifest; } public bool TryFind(ToolCommandName toolCommandName, out ToolManifestPackage toolManifestPackage) { toolManifestPackage = default; foreach ((FilePath possibleManifest, DirectoryPath correspondingDirectory) in EnumerateDefaultAllPossibleManifests()) { if (!_fileSystem.File.Exists(possibleManifest.Value)) { continue; } (List<ToolManifestPackage> manifestPackages, bool isRoot) = _toolManifestEditor.Read(possibleManifest, correspondingDirectory); foreach (var package in manifestPackages) { if (package.CommandNames.Contains(toolCommandName)) { toolManifestPackage = package; return true; } } if (isRoot) { return false; } } return false; } public bool TryFindPackageId(PackageId packageId, out ToolManifestPackage toolManifestPackage) { toolManifestPackage = default; foreach ((FilePath possibleManifest, DirectoryPath correspondingDirectory) in EnumerateDefaultAllPossibleManifests()) { if (!_fileSystem.File.Exists(possibleManifest.Value)) { continue; } (List<ToolManifestPackage> manifestPackages, bool isRoot) = _toolManifestEditor.Read(possibleManifest, correspondingDirectory); foreach (var package in manifestPackages) { if (package.PackageId.Equals(packageId)) { toolManifestPackage = package; return true; } } if (isRoot) { return false; } } return false; } private IEnumerable<(FilePath manifestfile, DirectoryPath manifestFileFirstEffectDirectory)> EnumerateDefaultAllPossibleManifests() { DirectoryPath? currentSearchDirectory = _probeStart; while (currentSearchDirectory.HasValue && (currentSearchDirectory.Value.GetParentPathNullable() != null || AllowManifestInRoot())) { var currentSearchDotConfigDirectory = currentSearchDirectory.Value.WithSubDirectories(Constants.DotConfigDirectoryName); var tryManifest = currentSearchDirectory.Value.WithFile(ManifestFilenameConvention); yield return (currentSearchDotConfigDirectory.WithFile(ManifestFilenameConvention), currentSearchDirectory.Value); yield return (tryManifest, currentSearchDirectory.Value); currentSearchDirectory = currentSearchDirectory.Value.GetParentPathNullable(); } } private bool AllowManifestInRoot() { string environmentVariableValue = _getEnvironmentVariable(EnvironmentVariableNames.DOTNET_TOOLS_ALLOW_MANIFEST_IN_ROOT); if (!string.IsNullOrWhiteSpace(environmentVariableValue)) { if (environmentVariableValue.Equals("true", StringComparison.OrdinalIgnoreCase) || environmentVariableValue.Equals("1", StringComparison.OrdinalIgnoreCase)) { return true; } else { return false; } } return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); } public FilePath FindFirst(bool createIfNotFound = false) { foreach ((FilePath possibleManifest, DirectoryPath _) in EnumerateDefaultAllPossibleManifests()) { if (_fileSystem.File.Exists(possibleManifest.Value)) { return possibleManifest; } } if (createIfNotFound) { DirectoryPath manifestInsertFolder = GetDirectoryToCreateToolManifest(); if (manifestInsertFolder.Value != null) { return new FilePath(WriteManifestFile(manifestInsertFolder)); } } throw new ToolManifestCannotBeFoundException(string.Format(CliStrings.CannotFindAManifestFile, string.Join(Environment.NewLine, EnumerateDefaultAllPossibleManifests().Select(f => "\t" + f.manifestfile.Value)))); } /* The --create-manifest-if-needed will use the following priority to choose the folder where the tool manifest goes: 1. Walk up the directory tree searching for one that has a .git subfolder 2. Walk up the directory tree searching for one that has a .sln(x)/git file in it 3. Use the current working directory */ private DirectoryPath GetDirectoryToCreateToolManifest() { DirectoryPath? currentSearchDirectory = _probeStart; while (currentSearchDirectory.HasValue && currentSearchDirectory.Value.GetParentPathNullable() != null) { var currentSearchGitDirectory = currentSearchDirectory.Value.WithSubDirectories(Constants.GitDirectoryName); if (_fileSystem.Directory.Exists(currentSearchGitDirectory.Value)) { return currentSearchDirectory.Value; } if (currentSearchDirectory.Value.Value != null) { if (_fileSystem.Directory.EnumerateFiles(currentSearchDirectory.Value.Value) .Any(filename => Path.GetExtension(filename).Equals(".sln", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(filename).Equals(".slnx", StringComparison.OrdinalIgnoreCase)) || _fileSystem.File.Exists(currentSearchDirectory.Value.WithFile(".git").Value)) { return currentSearchDirectory.Value; } } currentSearchDirectory = currentSearchDirectory.Value.GetParentPathNullable(); } return _probeStart; } private string WriteManifestFile(DirectoryPath folderPath) { var manifestFileContent = """ { "version": 1, "isRoot": true, "tools": {} } """; string manifestFileLocation = Path.Combine(folderPath.Value, Constants.ToolManifestFileName); _fileSystem.File.WriteAllText(manifestFileLocation, manifestFileContent); return manifestFileLocation; } /// <summary> /// Return manifest file path in the order of the closest probe path first. /// </summary> public IReadOnlyList<FilePath> FindByPackageId(PackageId packageId) { var result = new List<FilePath>(); bool findAnyManifest = false; DirectoryPath? rootPath = null; foreach ((FilePath possibleManifest, DirectoryPath correspondingDirectory) in EnumerateDefaultAllPossibleManifests()) { if (rootPath is not null) { if (!correspondingDirectory.Value.Equals(rootPath.Value)) { break; } } if (_fileSystem.File.Exists(possibleManifest.Value)) { findAnyManifest = true; (List<ToolManifestPackage> content, bool isRoot) = _toolManifestEditor.Read(possibleManifest, correspondingDirectory); if (content.Any(t => t.PackageId.Equals(packageId))) { result.Add(possibleManifest); } if (isRoot) { rootPath = correspondingDirectory; } } } if (!findAnyManifest) { throw new ToolManifestCannotBeFoundException(string.Format(CliStrings.CannotFindAManifestFile, string.Join(Environment.NewLine, EnumerateDefaultAllPossibleManifests().Select(f => "\t" + f.manifestfile.Value)))); } return result; } }