File: ToolPackage\ToolPackageInstance.cs
Web Access
Project: ..\..\..\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.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
using NuGet.Frameworks;
using NuGet.Packaging;
using NuGet.ProjectModel;
using NuGet.Versioning;
 
namespace Microsoft.DotNet.Cli.ToolPackage;
 
// This is named "ToolPackageInstance" because "ToolPackage" would conflict with the namespace
internal class ToolPackageInstance : IToolPackage
{
    private const string PackagedShimsDirectoryConvention = "shims";
 
    public IEnumerable<string> Warnings { get; private set; }
 
    public PackageId Id { get; private set; }
 
    public NuGetVersion Version { get; private set; }
 
    public PackageId ResolvedPackageId { get; private set; }
 
    public NuGetVersion ResolvedPackageVersion { get; private set; }
 
    public DirectoryPath PackageDirectory { get; private set; }
 
    public ToolCommand Command { get; private set; }
 
    public IReadOnlyList<FilePath> PackagedShims { get; private set; }
 
    public IEnumerable<NuGetFramework> Frameworks { get; private set; }
 
    private IFileSystem _fileSystem;
 
    public const string AssetsFileName = "project.assets.json";
    public const string RidSpecificPackageAssetsFileName = "project.assets.ridpackage.json";
    private const string ToolSettingsFileName = "DotnetToolSettings.xml";
 
    public ToolPackageInstance(PackageId id,
        NuGetVersion version,
        DirectoryPath packageDirectory,
        DirectoryPath assetsJsonParentDirectory,
        IFileSystem fileSystem = null)
    {
        Id = id;
        Version = version ?? throw new ArgumentNullException(nameof(version));
        PackageDirectory = packageDirectory;
        _fileSystem = fileSystem ?? new FileSystemWrapper();
 
        bool usingRidSpecificPackage = _fileSystem.File.Exists(assetsJsonParentDirectory.WithFile(RidSpecificPackageAssetsFileName).Value);
 
        string resolvedAssetsFileNameFullPath;
        if (usingRidSpecificPackage)
        {
            resolvedAssetsFileNameFullPath = assetsJsonParentDirectory.WithFile(RidSpecificPackageAssetsFileName).Value;
        }
        else
        {
            resolvedAssetsFileNameFullPath = assetsJsonParentDirectory.WithFile(AssetsFileName).Value;
        }
 
        LockFile lockFile;
 
        try
        {
            using (var stream = _fileSystem.File.OpenRead(resolvedAssetsFileNameFullPath))
            {
                lockFile = new LockFileFormat().Read(stream, resolvedAssetsFileNameFullPath);
            }
        }
        catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException)
        {
            throw new ToolPackageException(string.Format(CliStrings.FailedToReadNuGetLockFile, Id, ex.Message), ex);
        }
 
        var library = FindLibraryInLockFile(lockFile);
        if (library == null)
        {
            throw new ToolPackageException(
                string.Format(CliStrings.FailedToFindLibraryInAssetsFile, Id, resolvedAssetsFileNameFullPath));
        }
 
 
        if (usingRidSpecificPackage)
        {
            ResolvedPackageId = new PackageId(library.Name);
            ResolvedPackageVersion = library.Version;
        }
        else
        {
            ResolvedPackageId = Id;
            ResolvedPackageVersion = Version;
        }
 
        var toolConfiguration = DeserializeToolConfiguration(library, packageDirectory, _fileSystem);
        Warnings = toolConfiguration.Warnings;
 
        var installPath = new VersionFolderPathResolver(PackageDirectory.Value).GetInstallPath(ResolvedPackageId.ToString(), ResolvedPackageVersion);
        var toolsPackagePath = Path.Combine(installPath, "tools");
        Frameworks = _fileSystem.Directory.EnumerateDirectories(toolsPackagePath)
            .Select(path => NuGetFramework.ParseFolder(Path.GetFileName(path))).ToList();
 
        LockFileItem entryPointFromLockFile = FindItemInTargetLibrary(library, toolConfiguration.ToolAssemblyEntryPoint);
        if (entryPointFromLockFile == null)
        {
            throw new ToolConfigurationException(
                string.Format(
                    CliStrings.MissingToolEntryPointFile,
                    toolConfiguration.ToolAssemblyEntryPoint,
                    toolConfiguration.CommandName));
        }
 
        Command = new ToolCommand(
                new ToolCommandName(toolConfiguration.CommandName),
                toolConfiguration.Runner,
                LockFileRelativePathToFullFilePath(entryPointFromLockFile.Path, library));
 
        IEnumerable<LockFileItem> filesUnderShimsDirectory = library
            ?.ToolsAssemblies
            ?.Where(t => LockFileMatcher.MatchesDirectoryPath(t, PackagedShimsDirectoryConvention));
 
        if (filesUnderShimsDirectory == null)
        {
            PackagedShims = [];
        }
        else
        {
            IEnumerable<string> allAvailableShimRuntimeIdentifiers = filesUnderShimsDirectory
               .Select(f => f.Path.Split('\\', '/')?[4]) // ex: "tools/netcoreapp2.1/any/shims/osx-x64/demo" osx-x64 is at [4]
               .Where(f => !string.IsNullOrEmpty(f));
 
            if (new FrameworkDependencyFile().TryGetMostFitRuntimeIdentifier(
                DotnetFiles.VersionFileObject.BuildRid,
                [.. allAvailableShimRuntimeIdentifiers],
                out var mostFitRuntimeIdentifier))
            {
                PackagedShims = library?.ToolsAssemblies?.Where(l => LockFileMatcher.MatchesDirectoryPath(l, $"{PackagedShimsDirectoryConvention}/{mostFitRuntimeIdentifier}"))
                    .Select(l => LockFileRelativePathToFullFilePath(l.Path, library)).ToArray() ?? [];
            }
            else
            {
                PackagedShims = [];
            }
        }
    }
 
    private FilePath LockFileRelativePathToFullFilePath(string lockFileRelativePath, LockFileTargetLibrary library)
    {
        return PackageDirectory
                    .WithSubDirectories(
                        library.Name,
                        library.Version.ToNormalizedString().ToLowerInvariant())
                    .WithFile(lockFileRelativePath);
    }
 
 
    public static ToolConfiguration GetToolConfiguration(PackageId id,
        DirectoryPath packageDirectory,
        DirectoryPath assetsJsonParentDirectory, IFileSystem fileSystem)
    {
        var lockFile = new LockFileFormat().Read(assetsJsonParentDirectory.WithFile(AssetsFileName).Value);
        var lockFileTargetLibrary = FindLibraryInLockFile(lockFile);
        return DeserializeToolConfiguration(lockFileTargetLibrary, packageDirectory, fileSystem);
 
    }
 
    private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library, DirectoryPath packageDirectory, IFileSystem fileSystem)
    {
        try
        {
            var dotnetToolSettings = FindItemInTargetLibrary(library, ToolSettingsFileName);
            if (dotnetToolSettings == null)
            {
                throw new ToolConfigurationException(
                    CliStrings.MissingToolSettingsFile);
            }
 
            var toolConfigurationPath =
                packageDirectory
                    .WithSubDirectories(
                        new PackageId(library.Name).ToString(),
                        library.Version.ToNormalizedString().ToLowerInvariant())
                    .WithFile(dotnetToolSettings.Path);
 
            var configuration = ToolConfigurationDeserializer.Deserialize(toolConfigurationPath.Value, fileSystem);
            return configuration;
        }
        catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException)
        {
            throw new ToolConfigurationException(
                string.Format(
                    CliStrings.FailedToRetrieveToolConfiguration,
                    ex.Message),
                ex);
        }
    }
 
    private static LockFileTargetLibrary FindLibraryInLockFile(LockFile lockFile)
    {
        return lockFile
            ?.Targets?.SingleOrDefault(t => t.RuntimeIdentifier != null)
            ?.Libraries?.SingleOrDefault();
    }
 
    private static LockFileItem FindItemInTargetLibrary(LockFileTargetLibrary library, string targetRelativeFilePath)
    {
        return library
            ?.ToolsAssemblies
            ?.SingleOrDefault(t => LockFileMatcher.MatchesFile(t, targetRelativeFilePath));
    }
 
}