|
// 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;
}
}
|