Skip to content
This repository was archived by the owner on Apr 20, 2023. It is now read-only.

Commit 02a98d4

Browse files
author
William Lee
authored
[tools] Integrate NuGet (#8414)
* Integrate NuGet ask * Update NuGet version. Rely on NuGet to filter TFM. And use asset.json to find entrypoint * Update XML file to per TFM * Add extra property to the fake project according to nuget * Treat nuget fallback folder as offline cache for tool * Require -g to install global tool * Copy test asset during test project build * Address code review on LockFileMatchChecker * Get NETCorePlatformsImplicitPackageVersion from PackageDefinitions * Edit and add missing loc * Change LockFileMatchChecker to local function * Adding comment * Add to content instead of copy * Download platform package instead * disable SDK side implicit NuGetFallbackFolder * merge loc * Revert extra line * use a prerelease platforms version that supports alpine
1 parent c7d44be commit 02a98d4

40 files changed

+639
-108
lines changed

build/MSBuildExtensions.targets

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@
9999
<ItemGroup>
100100
<_NETStandardLibraryVersions Include="@(PackageDefinitions->'%(Version)')"
101101
Condition="%(PackageDefinitions.Name) == 'NetStandard.Library'" />
102+
<_NETCorePlatformsImplicitPackageVersion Include="@(PackageDefinitions->'%(Version)')"
103+
Condition="%(PackageDefinitions.Name) == 'Microsoft.NETCore.Platforms'" />
102104
</ItemGroup>
103105

104106
<Error Condition="@(_NETStandardLibraryVersions->Distinct()->Count()) != 1"
@@ -107,6 +109,7 @@
107109
<PropertyGroup>
108110
<_NETCoreAppPackageVersion>$(MicrosoftNETCoreAppPackageVersion)</_NETCoreAppPackageVersion>
109111
<_NETStandardPackageVersion>@(_NETStandardLibraryVersions->Distinct())</_NETStandardPackageVersion>
112+
<_NETCorePlatformsImplicitPackageVersion>@(_NETCorePlatformsImplicitPackageVersion->Distinct())</_NETCorePlatformsImplicitPackageVersion>
110113

111114
<!-- Use only major and minor in target framework version -->
112115
<_NETCoreAppTargetFrameworkVersion>$(_NETCoreAppPackageVersion.Split('.')[0]).$(_NETCoreAppPackageVersion.Split('.')[1])</_NETCoreAppTargetFrameworkVersion>
@@ -131,6 +134,7 @@ Copyright (c) .NET Foundation. All rights reserved.
131134
<BundledNETCoreAppPackageVersion>$(_NETCoreAppPackageVersion)</BundledNETCoreAppPackageVersion>
132135
<BundledNETStandardTargetFrameworkVersion>$(_NETStandardTargetFrameworkVersion)</BundledNETStandardTargetFrameworkVersion>
133136
<BundledNETStandardPackageVersion>$(_NETStandardPackageVersion)</BundledNETStandardPackageVersion>
137+
<NETCorePlatformsImplicitPackageVersion>$(_NETCorePlatformsImplicitPackageVersion)</NETCorePlatformsImplicitPackageVersion>
134138
</PropertyGroup>
135139
</Project>
136140
]]>

src/dotnet/CommonLocalizableStrings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,4 +584,10 @@ Output: {1}</value>
584584
<data name="FailInstallToolSameName" xml:space="preserve">
585585
<value>Failed to install tool {0}. A command with the same name already exists.</value>
586586
</data>
587+
<data name="ToolPackageMissingEntryPointFile" xml:space="preserve">
588+
<value>Package '{0}' is missing entry point file {1}.</value>
589+
</data>
590+
<data name="ToolPackageMissingSettingsFile" xml:space="preserve">
591+
<value>Package '{0}' is missing tool settings file DotnetToolSettings.xml.</value>
592+
</data>
587593
</root>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Linq;
5+
using NuGet.ProjectModel;
6+
7+
namespace Microsoft.DotNet.ToolPackage
8+
{
9+
internal class LockFileMatcher
10+
{
11+
/// <summary>
12+
/// Check if LockFileItem matches the targetRelativeFilePath.
13+
/// The path in LockFileItem is in pattern tools/TFM/RID/my/tool.dll. Tools/TFM/RID is selected by NuGet.
14+
/// And there will be only one TFM/RID combination.
15+
/// When "my/tools.dll" part matches exactly with the targetRelativeFilePath, return true.
16+
/// </summary>
17+
/// <param name="lockFileItem">LockFileItem from asset.json restored from temp project</param>
18+
/// <param name="targetRelativeFilePath">file path relative to tools/TFM/RID</param>
19+
internal static bool MatchesFile(LockFileItem lockFileItem, string targetRelativeFilePath)
20+
{
21+
string[] pathInLockFilePathInArray = SplitPathByDirectorySeparator(lockFileItem.Path);
22+
string[] entryPointPathInArray = SplitPathByDirectorySeparator(targetRelativeFilePath);
23+
24+
return entryPointPathInArray.Length >= 1
25+
&& PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder()
26+
&& SubPathMatchesTargetFilePath();
27+
28+
bool SubPathMatchesTargetFilePath()
29+
{
30+
string[] pathAfterToolsTfmRid = pathInLockFilePathInArray.Skip(3).ToArray();
31+
return !pathAfterToolsTfmRid
32+
.Where((directoryOnEveryLevel, i) => directoryOnEveryLevel != entryPointPathInArray[i])
33+
.Any();
34+
}
35+
36+
bool PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder()
37+
{
38+
if (pathInLockFilePathInArray.Length - entryPointPathInArray.Length != 3)
39+
{
40+
return false;
41+
}
42+
43+
if (pathInLockFilePathInArray[0] != "tools")
44+
{
45+
return false;
46+
}
47+
48+
return true;
49+
}
50+
51+
string[] SplitPathByDirectorySeparator(string path)
52+
{
53+
return path.Split('\\', '/');
54+
}
55+
}
56+
}
57+
}

src/dotnet/ToolPackage/ToolConfigurationAndExecutableDirectory.cs renamed to src/dotnet/ToolPackage/ToolConfigurationAndExecutablePath.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@
55

66
namespace Microsoft.DotNet.ToolPackage
77
{
8-
internal class ToolConfigurationAndExecutableDirectory
8+
internal class ToolConfigurationAndExecutablePath
99
{
10-
public ToolConfigurationAndExecutableDirectory(
10+
public ToolConfigurationAndExecutablePath(
1111
ToolConfiguration toolConfiguration,
12-
DirectoryPath executableDirectory)
12+
FilePath executable)
1313
{
1414
Configuration = toolConfiguration;
15-
ExecutableDirectory = executableDirectory;
15+
Executable = executable;
1616
}
1717

1818
public ToolConfiguration Configuration { get; }
19-
public DirectoryPath ExecutableDirectory { get; }
19+
20+
public FilePath Executable { get; }
2021
}
2122
}

src/dotnet/ToolPackage/ToolPackageObtainer.cs

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System;
2-
using System.Diagnostics;
32
using System.IO;
43
using System.Linq;
54
using System.Xml.Linq;
65
using Microsoft.DotNet.Tools;
6+
using Microsoft.DotNet.Cli.Utils;
7+
using Microsoft.DotNet.Configurer;
78
using Microsoft.Extensions.EnvironmentAbstractions;
9+
using NuGet.ProjectModel;
810

911
namespace Microsoft.DotNet.ToolPackage
1012
{
@@ -15,9 +17,11 @@ internal class ToolPackageObtainer
1517
private readonly IPackageToProjectFileAdder _packageToProjectFileAdder;
1618
private readonly IProjectRestorer _projectRestorer;
1719
private readonly DirectoryPath _toolsPath;
20+
private readonly DirectoryPath _offlineFeedPath;
1821

1922
public ToolPackageObtainer(
2023
DirectoryPath toolsPath,
24+
DirectoryPath offlineFeedPath,
2125
Func<FilePath> getTempProjectPath,
2226
Lazy<string> bundledTargetFrameworkMoniker,
2327
IPackageToProjectFileAdder packageToProjectFileAdder,
@@ -30,9 +34,10 @@ IProjectRestorer projectRestorer
3034
_packageToProjectFileAdder = packageToProjectFileAdder ??
3135
throw new ArgumentNullException(nameof(packageToProjectFileAdder));
3236
_toolsPath = toolsPath;
37+
_offlineFeedPath = offlineFeedPath;
3338
}
3439

35-
public ToolConfigurationAndExecutableDirectory ObtainAndReturnExecutablePath(
40+
public ToolConfigurationAndExecutablePath ObtainAndReturnExecutablePath(
3641
string packageId,
3742
string packageVersion = null,
3843
FilePath? nugetconfig = null,
@@ -50,7 +55,7 @@ public ToolConfigurationAndExecutableDirectory ObtainAndReturnExecutablePath(
5055
{
5156
throw new PackageObtainException(
5257
string.Format(CommonLocalizableStrings.NuGetConfigurationFileDoesNotExist,
53-
Path.GetFullPath(nugetconfig.Value.Value)));
58+
Path.GetFullPath(nugetconfig.Value.Value)));
5459
}
5560
}
5661

@@ -95,16 +100,52 @@ public ToolConfigurationAndExecutableDirectory ObtainAndReturnExecutablePath(
95100
packageVersion = concreteVersion;
96101
}
97102

98-
ToolConfiguration toolConfiguration = GetConfiguration(packageId: packageId, packageVersion: packageVersion, individualToolVersion: toolDirectory);
103+
LockFile lockFile = new LockFileFormat()
104+
.ReadWithLock(toolDirectory.WithFile("project.assets.json").Value)
105+
.Result;
99106

100-
return new ToolConfigurationAndExecutableDirectory(
107+
LockFileItem dotnetToolSettings = FindAssetInLockFile(lockFile, "DotnetToolSettings.xml", packageId);
108+
109+
if (dotnetToolSettings == null)
110+
{
111+
throw new PackageObtainException(
112+
string.Format(CommonLocalizableStrings.ToolPackageMissingSettingsFile, packageId));
113+
}
114+
115+
FilePath toolConfigurationPath =
116+
toolDirectory
117+
.WithSubDirectories(packageId, packageVersion)
118+
.WithFile(dotnetToolSettings.Path);
119+
120+
ToolConfiguration toolConfiguration =
121+
ToolConfigurationDeserializer.Deserialize(toolConfigurationPath.Value);
122+
123+
var entryPointFromLockFile =
124+
FindAssetInLockFile(lockFile, toolConfiguration.ToolAssemblyEntryPoint, packageId);
125+
126+
if (entryPointFromLockFile == null)
127+
{
128+
throw new PackageObtainException(string.Format(CommonLocalizableStrings.ToolPackageMissingEntryPointFile,
129+
packageId, toolConfiguration.ToolAssemblyEntryPoint));
130+
}
131+
132+
return new ToolConfigurationAndExecutablePath(
101133
toolConfiguration,
102134
toolDirectory.WithSubDirectories(
103-
packageId,
104-
packageVersion,
105-
"tools",
106-
targetframework,
107-
"any"));
135+
packageId,
136+
packageVersion)
137+
.WithFile(entryPointFromLockFile.Path));
138+
}
139+
140+
private static LockFileItem FindAssetInLockFile(
141+
LockFile lockFile,
142+
string targetRelativeFilePath, string packageId)
143+
{
144+
return lockFile
145+
.Targets?.SingleOrDefault(t => t.RuntimeIdentifier != null)
146+
?.Libraries?.SingleOrDefault(l => l.Name == packageId)
147+
?.ToolsAssemblies
148+
?.SingleOrDefault(t => LockFileMatcher.MatchesFile(t, targetRelativeFilePath));
108149
}
109150

110151
private static void MoveToVersionedDirectory(
@@ -119,22 +160,6 @@ private static void MoveToVersionedDirectory(
119160
Directory.Move(temporary.Value, versioned.Value);
120161
}
121162

122-
private static ToolConfiguration GetConfiguration(
123-
string packageId,
124-
string packageVersion,
125-
DirectoryPath individualToolVersion)
126-
{
127-
FilePath toolConfigurationPath =
128-
individualToolVersion
129-
.WithSubDirectories(packageId, packageVersion, "tools")
130-
.WithFile("DotnetToolSettings.xml");
131-
132-
ToolConfiguration toolConfiguration =
133-
ToolConfigurationDeserializer.Deserialize(toolConfigurationPath.Value);
134-
135-
return toolConfiguration;
136-
}
137-
138163
private FilePath CreateTempProject(
139164
string packageId,
140165
PackageVersion packageVersion,
@@ -153,9 +178,16 @@ private FilePath CreateTempProject(
153178
new XAttribute("Sdk", "Microsoft.NET.Sdk"),
154179
new XElement("PropertyGroup",
155180
new XElement("TargetFramework", targetframework),
156-
new XElement("RestorePackagesPath", individualToolVersion.Value),
157-
new XElement("RestoreSolutionDirectory", Directory.GetCurrentDirectory()), // https://github.com/NuGet/Home/issues/6199
158-
new XElement("DisableImplicitFrameworkReferences", "true")
181+
new XElement("RestorePackagesPath", individualToolVersion.Value), // tool package will restore to tool folder
182+
new XElement("RestoreProjectStyle", "DotnetToolReference"), // without it, project cannot reference tool package
183+
new XElement("RestoreRootConfigDirectory", Directory.GetCurrentDirectory()), // config file probing start directory
184+
new XElement("DisableImplicitFrameworkReferences", "true"), // no Microsoft.NETCore.App in tool folder
185+
new XElement("RestoreFallbackFolders", "clear"), // do not use fallbackfolder, tool package need to be copied to tool folder
186+
new XElement("RestoreAdditionalProjectSources", // use fallbackfolder as feed to enable offline
187+
Directory.Exists(_offlineFeedPath.Value) ? _offlineFeedPath.Value : string.Empty),
188+
new XElement("RestoreAdditionalProjectFallbackFolders", string.Empty), // block other
189+
new XElement("RestoreAdditionalProjectFallbackFoldersExcludes", string.Empty), // block other
190+
new XElement("DisableImplicitNuGetFallbackFolder","true") // disable SDK side implicit NuGetFallbackFolder
159191
),
160192
packageVersion.IsConcreteValue
161193
? new XElement("ItemGroup",

src/dotnet/commands/dotnet-install/InstallCommand.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
using Microsoft.DotNet.Cli;
77
using Microsoft.DotNet.Cli.CommandLine;
88
using Microsoft.DotNet.Cli.Utils;
9-
using Microsoft.DotNet.Tools.Add;
109
using Microsoft.DotNet.Tools.Install.Tool;
11-
using LocalizableStrings = Microsoft.DotNet.Tools.Install.LocalizableStrings;
1210

1311
namespace Microsoft.DotNet.Tools.Install
1412
{

src/dotnet/commands/dotnet-install/dotnet-install-tool/InstallToolCommand.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class InstallToolCommand : CommandBase
2121
private static string _configFilePath;
2222
private static string _framework;
2323
private static string _source;
24+
private static bool _global;
2425

2526
public InstallToolCommand(
2627
AppliedOption appliedCommand,
@@ -37,27 +38,28 @@ public InstallToolCommand(
3738
_configFilePath = appliedCommand.ValueOrDefault<string>("configfile");
3839
_framework = appliedCommand.ValueOrDefault<string>("framework");
3940
_source = appliedCommand.ValueOrDefault<string>("source");
41+
_global = appliedCommand.ValueOrDefault<bool>("global");
4042
}
4143

4244
public override int Execute()
4345
{
44-
var executablePackagePath = new DirectoryPath(new CliFolderPathCalculator().ExecutablePackagesPath);
46+
if (!_global)
47+
{
48+
throw new GracefulException(LocalizableStrings.InstallToolCommandOnlySupportGlobal);
49+
}
4550

46-
var toolConfigurationAndExecutableDirectory = ObtainPackage(executablePackagePath);
51+
var cliFolderPathCalculator = new CliFolderPathCalculator();
52+
var executablePackagePath = new DirectoryPath(cliFolderPathCalculator.ExecutablePackagesPath);
53+
var offlineFeedPath = new DirectoryPath(cliFolderPathCalculator.CliFallbackFolderPath);
4754

48-
DirectoryPath executable = toolConfigurationAndExecutableDirectory
49-
.ExecutableDirectory
50-
.WithSubDirectories(
51-
toolConfigurationAndExecutableDirectory
52-
.Configuration
53-
.ToolAssemblyEntryPoint);
55+
var toolConfigurationAndExecutablePath = ObtainPackage(executablePackagePath, offlineFeedPath);
5456

5557
var shellShimMaker = new ShellShimMaker(executablePackagePath.Value);
56-
var commandName = toolConfigurationAndExecutableDirectory.Configuration.CommandName;
58+
var commandName = toolConfigurationAndExecutablePath.Configuration.CommandName;
5759
shellShimMaker.EnsureCommandNameUniqueness(commandName);
5860

5961
shellShimMaker.CreateShim(
60-
executable.Value,
62+
toolConfigurationAndExecutablePath.Executable.Value,
6163
commandName);
6264

6365
EnvironmentPathFactory
@@ -70,7 +72,9 @@ public override int Execute()
7072
return 0;
7173
}
7274

73-
private static ToolConfigurationAndExecutableDirectory ObtainPackage(DirectoryPath executablePackagePath)
75+
private static ToolConfigurationAndExecutablePath ObtainPackage(
76+
DirectoryPath executablePackagePath,
77+
DirectoryPath offlineFeedPath)
7478
{
7579
try
7680
{
@@ -83,6 +87,7 @@ private static ToolConfigurationAndExecutableDirectory ObtainPackage(DirectoryPa
8387
var toolPackageObtainer =
8488
new ToolPackageObtainer(
8589
executablePackagePath,
90+
offlineFeedPath,
8691
() => new DirectoryPath(Path.GetTempPath())
8792
.WithSubDirectories(Path.GetRandomFileName())
8893
.WithFile(Path.GetRandomFileName() + ".csproj"),
@@ -97,6 +102,7 @@ private static ToolConfigurationAndExecutableDirectory ObtainPackage(DirectoryPa
97102
targetframework: _framework,
98103
source: _source);
99104
}
105+
100106
catch (PackageObtainException ex)
101107
{
102108
throw new GracefulException(

src/dotnet/commands/dotnet-install/dotnet-install-tool/InstallToolCommandParser.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ public static Command InstallTool()
1515
Accept.ExactlyOneArgument(errorMessage: o => LocalizableStrings.SpecifyExactlyOnePackageId)
1616
.With(name: LocalizableStrings.PackageIdArgumentName,
1717
description: LocalizableStrings.PackageIdArgumentDescription),
18+
Create.Option(
19+
"-g|--global",
20+
LocalizableStrings.GlobalOptionDescription,
21+
Accept.NoArguments()),
1822
Create.Option(
1923
"--version",
2024
LocalizableStrings.VersionOptionDescription,

src/dotnet/commands/dotnet-install/dotnet-install-tool/LocalizableStrings.resx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,13 @@
129129
<data name="VersionOptionDescription" xml:space="preserve">
130130
<value>Version of the tool package in NuGet.</value>
131131
</data>
132-
<data name="SourceOptionDescription" xml:space="preserve">
132+
<data name="SourceOptionDescription" xml:space="preserve">
133133
<value>Specifies a NuGet package source to use during installation.</value>
134134
</data>
135135
<data name="SourceOptionName" xml:space="preserve">
136136
<value>SOURCE</value>
137137
</data>
138-
<data name="CommandDescription" xml:space="preserve">
138+
<data name="CommandDescription" xml:space="preserve">
139139
<value>Installs a tool for use on the command line.</value>
140140
</data>
141141
<data name="ConfigFileOptionDescription" xml:space="preserve">
@@ -172,4 +172,13 @@ The error was:
172172
<value>
173173
The installation succeeded. If there are no further instructions, you can type the following command in shell directly to invoke: {0}</value>
174174
</data>
175+
<data name="GlobalOptionDescription" xml:space="preserve">
176+
<value>Install user wide.</value>
177+
</data>
178+
<data name="InstallFullCommandNameLocalized" xml:space="preserve">
179+
<value>.NET Install Command</value>
180+
</data>
181+
<data name="InstallToolCommandOnlySupportGlobal" xml:space="preserve">
182+
<value>The --global switch (-g) is currently required because only user wide tools are supported.</value>
183+
</data>
175184
</root>

0 commit comments

Comments
 (0)