Skip to content

Commit d070eb6

Browse files
authored
Add support for file-based apps to the XPlat CLI (#7169)
1 parent 4ee8cd0 commit d070eb6

50 files changed

Lines changed: 1062 additions & 257 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Update/PackageUpdateCommand.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ namespace NuGet.CommandLine.XPlat.Commands.Package.Update;
1515

1616
internal static class PackageUpdateCommand
1717
{
18-
internal static void Register(Command packageCommand, Option<bool> interactiveOption)
18+
internal static void Register(Command packageCommand, Option<bool> interactiveOption, IVirtualProjectBuilder? virtualProjectBuilder = null)
1919
{
20-
Register(packageCommand, interactiveOption, PackageUpdateCommandRunner.Run);
20+
Register(packageCommand, interactiveOption, (args, ct) => PackageUpdateCommandRunner.Run(args, virtualProjectBuilder, ct));
2121
}
2222

2323
internal static void Register(Command packageCommand, Option<bool> interactiveOption, Func<PackageUpdateArgs, CancellationToken, Task<int>> action)
@@ -32,7 +32,7 @@ internal static void Register(Command packageCommand, Option<bool> interactiveOp
3232
};
3333
command.Arguments.Add(packagesArguments);
3434

35-
var projectOption = new Option<FileSystemInfo>("--project").AcceptExistingOnly();
35+
var projectOption = new Option<FileSystemInfo>("--project", "--file").AcceptExistingOnly();
3636
projectOption.Description = Strings.PackageUpdateCommand_ProjectOptionDescription;
3737
command.Options.Add(projectOption);
3838

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Update/PackageUpdateCommandRunner.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace NuGet.CommandLine.XPlat.Commands.Package.Update;
2626
internal static class PackageUpdateCommandRunner
2727
{
2828
// This overload sets static state, so should not be used in tests.
29-
internal static Task<int> Run(PackageUpdateArgs args, CancellationToken cancellationToken)
29+
internal static Task<int> Run(PackageUpdateArgs args, IVirtualProjectBuilder? virtualProjectBuilder, CancellationToken cancellationToken)
3030
{
3131
ILoggerWithColor logger = new CommandOutputLogger(args.LogLevel)
3232
{
@@ -39,7 +39,7 @@ internal static Task<int> Run(PackageUpdateArgs args, CancellationToken cancella
3939
// MSBuildAPIUtility's output is different to what we want for package update.
4040
// While it would probably be a good idea to align the output of all commands using MSBuildAPIUtility,
4141
// in order to meet deadlines, we'll suppress its output, and leave improvements for later.
42-
MSBuildAPIUtility msBuild = new(NullLogger.Instance);
42+
MSBuildAPIUtility msBuild = new(NullLogger.Instance, virtualProjectBuilder);
4343

4444
var restoreHelper = new PackageUpdateIO(args.Project, msBuild, EnvironmentVariableWrapper.Instance);
4545

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Update/PackageUpdateIO.cs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ public void Dispose()
7373

7474
DependencyGraphSpec result = DependencyGraphSpec.Load(tempFile);
7575

76+
// Fixup virtual project paths.
77+
if (_msbuildUtility.VirtualProjectBuilder?.GetVirtualProjectPath(project) is { } virtualProjectPath)
78+
{
79+
foreach (var packageSpec in result.Projects)
80+
{
81+
if (packageSpec.FilePath == virtualProjectPath)
82+
{
83+
packageSpec.FilePath = project;
84+
}
85+
}
86+
}
87+
7688
return result;
7789
}
7890
finally
@@ -86,20 +98,25 @@ bool RunMsbuildTarget(string project, string tempFile)
8698
// But when NuGet.CommandLine.XPlat is being called directly, call dotnet on the path, so this code is debuggable.
8799
string dotnetPath = _environmentVariableReader.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet";
88100

101+
bool isFileBasedApp = _msbuildUtility.VirtualProjectBuilder?.IsValidEntryPointPath(project) == true;
102+
89103
// don't redirect stdout or stderr, so errors are output. But use quiet verbosity, so that success has no output.
90104
ProcessStartInfo processStartInfo = new ProcessStartInfo(dotnetPath)
91105
{
92-
Arguments = $"msbuild " +
106+
Arguments = (isFileBasedApp ? "build " : "msbuild ") +
93107
$"\"{project}\" " +
94-
$"-restore:false " +
95-
$"-target:GenerateRestoreGraphFile " +
108+
(isFileBasedApp ? "--no-restore " : "-restore:false ") +
109+
"-target:GenerateRestoreGraphFile " +
96110
$"-property:RestoreGraphOutputPath=\"{tempFile}\" " +
97-
$"-property:RestoreRecursive=false " +
98-
$"-nologo " +
99-
$"-verbosity:quiet " +
100-
$"-tl:false " +
101-
$"-noautoresponse",
111+
"-property:RestoreRecursive=false " +
112+
"-nologo " +
113+
"-verbosity:quiet " +
114+
(!isFileBasedApp ? $"-noautoresponse" : null), // currently not supported for file-based apps
102115
UseShellExecute = false,
116+
Environment =
117+
{
118+
{ "MSBUILDTERMINALLOGGER", "off" },
119+
},
103120
};
104121

105122
using var process = Process.Start(processStartInfo);

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/PackageReferenceCommands/AddPackageReferenceCommand.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ namespace NuGet.CommandLine.XPlat
1515
internal static class AddPackageReferenceCommand
1616
{
1717
public static void Register(CommandLineApplication app, Func<ILogger> getLogger,
18-
Func<IPackageReferenceCommandRunner> getCommandRunner)
18+
Func<IPackageReferenceCommandRunner> getCommandRunner,
19+
Func<IVirtualProjectBuilder?>? getVirtualProjectBuilder = null)
1920
{
2021
app.Command("add", addpkg =>
2122
{
@@ -79,9 +80,11 @@ public static void Register(CommandLineApplication app, Func<ILogger> getLogger,
7980

8081
addpkg.OnExecute(() =>
8182
{
83+
var virtualProjectBuilder = getVirtualProjectBuilder?.Invoke();
84+
8285
ValidateArgument(id, addpkg.Name);
8386
ValidateArgument(projectPath, addpkg.Name);
84-
ValidateProjectPath(projectPath, addpkg.Name);
87+
ValidateProjectPath(projectPath, addpkg.Name, virtualProjectBuilder);
8588
if (!noRestore.HasValue())
8689
{
8790
ValidateArgument(dgFilePath, addpkg.Name);
@@ -103,7 +106,7 @@ public static void Register(CommandLineApplication app, Func<ILogger> getLogger,
103106
PackageVersion = packageVersion,
104107
PackageId = id.Values[0]
105108
};
106-
var msBuild = new MSBuildAPIUtility(logger);
109+
var msBuild = new MSBuildAPIUtility(logger, virtualProjectBuilder);
107110

108111
X509TrustStore.InitializeForDotNetSdk(logger);
109112

@@ -132,9 +135,11 @@ private static void ValidateArgument(CommandOption arg, string commandName)
132135
}
133136
}
134137

135-
private static void ValidateProjectPath(CommandOption projectPath, string commandName)
138+
private static void ValidateProjectPath(CommandOption projectPath, string commandName, IVirtualProjectBuilder? virtualProjectBuilder)
136139
{
137-
if (!File.Exists(projectPath.Value()) || !projectPath.Value().EndsWith("proj", StringComparison.OrdinalIgnoreCase))
140+
if (!File.Exists(projectPath.Value())
141+
|| (!projectPath.Value().EndsWith("proj", StringComparison.OrdinalIgnoreCase)
142+
&& virtualProjectBuilder?.IsValidEntryPointPath(projectPath.Value()) != true))
138143
{
139144
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
140145
Strings.Error_PkgMissingOrInvalidProjectFile,

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/PackageReferenceCommands/AddPackageReferenceCommandRunner.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ public async Task<int> ExecuteCommand(PackageReferenceArgs packageReferenceArgs,
7878

7979
var projectFullPath = Path.GetFullPath(packageReferenceArgs.ProjectPath);
8080

81+
if (msBuild.VirtualProjectBuilder?.IsValidEntryPointPath(projectFullPath) == true)
82+
{
83+
projectFullPath = msBuild.VirtualProjectBuilder.GetVirtualProjectPath(projectFullPath);
84+
}
85+
8186
var matchingPackageSpecs = dgSpec
8287
.Projects
8388
.Where(p => p.RestoreMetadata.ProjectStyle == ProjectStyle.PackageReference &&
@@ -104,7 +109,7 @@ public async Task<int> ExecuteCommand(PackageReferenceArgs packageReferenceArgs,
104109
var originalPackageSpec = matchingPackageSpecs.FirstOrDefault();
105110

106111
// Check if the project files are correct for CPM
107-
if (originalPackageSpec.RestoreMetadata.CentralPackageVersionsEnabled && !MSBuildAPIUtility.AreCentralVersionRequirementsSatisfied(packageReferenceArgs, originalPackageSpec))
112+
if (originalPackageSpec.RestoreMetadata.CentralPackageVersionsEnabled && !msBuild.AreCentralVersionRequirementsSatisfied(packageReferenceArgs, originalPackageSpec))
108113
{
109114
return 1;
110115
}

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/PackageReferenceCommands/ListPackage/ListPackageArgs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private string GetReportParameters()
119119

120120
if (HighestPatch)
121121
{
122-
sb.Append("--highest-patch");
122+
sb.Append(" --highest-patch");
123123
}
124124

125125
return sb.ToString().Trim();

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/PackageReferenceCommands/ListPackage/ListPackageCommand.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public static void Register(
127127
isDeprecated: deprecatedReport.HasValue(),
128128
isVulnerable: vulnerableReport.HasValue());
129129

130-
IReportRenderer reportRenderer = GetOutputType(outputFormat.Value(), outputVersionOption: outputVersion.Value());
130+
IReportRenderer reportRenderer = GetOutputType(app.Out, app.Error, outputFormat.Value(), outputVersionOption: outputVersion.Value());
131131
var provider = new PackageSourceProvider(settings);
132132
var packageRefArgs = new ListPackageArgs(
133133
path.Value,
@@ -171,7 +171,7 @@ private static ReportType GetReportType(bool isDeprecated, bool isOutdated, bool
171171
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.ListPkg_InvalidOptions));
172172
}
173173

174-
private static IReportRenderer GetOutputType(string outputFormatOption, string outputVersionOption)
174+
private static IReportRenderer GetOutputType(TextWriter consoleOut, TextWriter consoleError, string outputFormatOption, string outputVersionOption)
175175
{
176176
ReportOutputFormat outputFormat = ReportOutputFormat.Console;
177177
if (!string.IsNullOrEmpty(outputFormatOption) &&
@@ -187,7 +187,7 @@ private static IReportRenderer GetOutputType(string outputFormatOption, string o
187187
{
188188
throw new ArgumentException(string.Format(Strings.ListPkg_OutputVersionNotApplicable));
189189
}
190-
return new ListPackageConsoleRenderer();
190+
return new ListPackageConsoleRenderer(consoleOut, consoleError);
191191
}
192192

193193
IReportRenderer jsonReportRenderer;
@@ -200,7 +200,7 @@ private static IReportRenderer GetOutputType(string outputFormatOption, string o
200200
}
201201
else
202202
{
203-
jsonReportRenderer = new ListPackageJsonRenderer();
203+
jsonReportRenderer = new ListPackageJsonRenderer(consoleOut);
204204
}
205205

206206
return jsonReportRenderer;

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/PackageReferenceCommands/ListPackage/ListPackageCommandRunner.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ internal class ListPackageCommandRunner : IListPackageCommandRunner
3131
private const string ProjectName = "MSBuildProjectName";
3232
private const int GenericSuccessExitCode = 0;
3333
private const int GenericFailureExitCode = 1;
34-
private Dictionary<PackageSource, SourceRepository> _sourceRepositoryCache;
34+
private readonly MSBuildAPIUtility _msbuildUtility;
35+
private readonly Dictionary<PackageSource, SourceRepository> _sourceRepositoryCache;
3536

36-
public ListPackageCommandRunner()
37+
public ListPackageCommandRunner(MSBuildAPIUtility msbuildUtility)
3738
{
39+
_msbuildUtility = msbuildUtility;
3840
_sourceRepositoryCache = new Dictionary<PackageSource, SourceRepository>();
3941
}
4042

@@ -71,11 +73,9 @@ public async Task<int> ExecuteCommandAsync(ListPackageArgs listPackageArgs)
7173
? MSBuildAPIUtility.GetProjectsFromSolution(listPackageArgs.Path).Where(File.Exists)
7274
: [listPackageArgs.Path];
7375

74-
MSBuildAPIUtility msBuild = listPackageReportModel.MSBuildAPIUtility;
75-
7676
foreach (string projectPath in projectsPaths)
7777
{
78-
await GetProjectMetadataAsync(projectPath, listPackageReportModel, msBuild, listPackageArgs);
78+
await GetProjectMetadataAsync(projectPath, listPackageReportModel, listPackageArgs);
7979
}
8080

8181
// if there is any error then return failure code.
@@ -90,12 +90,11 @@ public async Task<int> ExecuteCommandAsync(ListPackageArgs listPackageArgs)
9090
private async Task GetProjectMetadataAsync(
9191
string projectPath,
9292
ListPackageReportModel listPackageReportModel,
93-
MSBuildAPIUtility msBuild,
9493
ListPackageArgs listPackageArgs)
9594
{
9695
//Open project to evaluate properties for the assets
9796
//file and the name of the project
98-
Project project = MSBuildAPIUtility.GetProject(projectPath);
97+
Project project = _msbuildUtility.GetProject(projectPath).Project;
9998
var projectName = project.GetPropertyValue(ProjectName);
10099
ListPackageProjectModel projectModel = listPackageReportModel.CreateProjectReportData(projectPath: projectPath, projectName);
101100

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/PackageReferenceCommands/RemovePackageReferenceCommand.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ namespace NuGet.CommandLine.XPlat
1414
internal class RemovePackageReferenceCommand
1515
{
1616
public static void Register(CommandLineApplication app, Func<ILogger> getLogger,
17-
Func<IPackageReferenceCommandRunner> getCommandRunner)
17+
Func<IPackageReferenceCommandRunner> getCommandRunner,
18+
Func<IVirtualProjectBuilder?>? getVirtualProjectBuilder = null)
1819
{
1920
app.Command("remove", removePkg =>
2021
{
@@ -43,16 +44,18 @@ public static void Register(CommandLineApplication app, Func<ILogger> getLogger,
4344

4445
removePkg.OnExecute(() =>
4546
{
47+
var virtualProjectBuilder = getVirtualProjectBuilder?.Invoke();
48+
4649
ValidateArgument(id, removePkg.Name);
4750
ValidateArgument(projectPath, removePkg.Name);
48-
ValidateProjectPath(projectPath, removePkg.Name);
51+
ValidateProjectPath(projectPath, removePkg.Name, virtualProjectBuilder);
4952
var logger = getLogger();
5053
var packageRefArgs = new PackageReferenceArgs(projectPath.Value(), logger)
5154
{
5255
Interactive = interactive.HasValue(),
5356
PackageId = id.Value()
5457
};
55-
var msBuild = new MSBuildAPIUtility(logger);
58+
var msBuild = new MSBuildAPIUtility(logger, virtualProjectBuilder);
5659
var removePackageRefCommandRunner = getCommandRunner();
5760
return removePackageRefCommandRunner.ExecuteCommand(packageRefArgs, msBuild);
5861
});
@@ -69,9 +72,11 @@ private static void ValidateArgument(CommandOption arg, string commandName)
6972
}
7073
}
7174

72-
private static void ValidateProjectPath(CommandOption projectPath, string commandName)
75+
private static void ValidateProjectPath(CommandOption projectPath, string commandName, IVirtualProjectBuilder? virtualProjectBuilder)
7376
{
74-
if (!File.Exists(projectPath.Value()) || !projectPath.Value().EndsWith("proj", StringComparison.OrdinalIgnoreCase))
77+
if (!File.Exists(projectPath.Value())
78+
|| (!projectPath.Value().EndsWith("proj", StringComparison.OrdinalIgnoreCase)
79+
&& virtualProjectBuilder?.IsValidEntryPointPath(projectPath.Value()) != true))
7580
{
7681
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
7782
Strings.Error_PkgMissingOrInvalidProjectFile,

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommand.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.IO;
1212
using System.Threading.Tasks;
1313
using Microsoft.Extensions.CommandLineUtils;
14+
using NuGet.Common;
1415
using Spectre.Console;
1516

1617
namespace NuGet.CommandLine.XPlat.Commands.Why
@@ -25,21 +26,34 @@ internal static void Register(CommandLineApplication app)
2526
});
2627
}
2728

28-
internal static void Register(Command rootCommand, Lazy<IAnsiConsole> console)
29+
internal static void Register(Command rootCommand, Lazy<IAnsiConsole> console, IVirtualProjectBuilder? virtualProjectBuilder = null)
2930
{
30-
Register(rootCommand, console, WhyCommandRunner.ExecuteCommand);
31+
Register(rootCommand, console,
32+
() => new WhyCommandRunner(new MSBuildAPIUtility(NullLogger.Instance, virtualProjectBuilder)));
3133
}
3234

3335
/// <summary>
3436
/// This is a temporary API until NuGet migrates all our commands to System.CommandLine, at which time I suspect we'll have a NuGetParser.GetNuGetCommand for all the `dotnet nuget *` commands.
3537
/// For now, this allows the dotnet CLI to invoke why directly, instead of running NuGet.CommandLine.XPlat as a child process.
3638
/// </summary>
3739
/// <param name="rootCommand">The <c>dotnet nuget</c> command handler, to add <c>why</c> to.</param>
38-
public static void GetWhyCommand(Command rootCommand)
40+
/// <param name="virtualProjectBuilder">For handling file-based apps.</param>
41+
public static void GetWhyCommand(Command rootCommand, IVirtualProjectBuilder? virtualProjectBuilder = null)
3942
{
4043
Register(rootCommand,
4144
new Lazy<IAnsiConsole>(() => Spectre.Console.AnsiConsole.Console),
42-
WhyCommandRunner.ExecuteCommand);
45+
virtualProjectBuilder);
46+
}
47+
48+
// For binary backcompat. To delete once the SDK starts using the other overload.
49+
public static void GetWhyCommand(Command rootCommand)
50+
{
51+
GetWhyCommand(rootCommand, virtualProjectBuilder: null);
52+
}
53+
54+
internal static void Register(Command rootCommand, Lazy<IAnsiConsole> console, Func<WhyCommandRunner> getCommandRunner)
55+
{
56+
Register(rootCommand, console, action: (args) => getCommandRunner().ExecuteCommand(args));
4357
}
4458

4559
// console must be lazy, because Spectre.Console's AnsiConsole will send VT sequences to the output
@@ -48,7 +62,7 @@ internal static void Register(Command rootCommand, Lazy<IAnsiConsole> console, F
4862
{
4963
var whyCommand = new DocumentedCommand("why", Strings.WhyCommand_Description, "https://aka.ms/dotnet/nuget/why");
5064

51-
Argument<string> path = new Argument<string>("PROJECT|SOLUTION")
65+
Argument<string> path = new Argument<string>("PROJECT|SOLUTION|FILE")
5266
{
5367
Description = Strings.WhyCommand_PathArgument_Description,
5468
// We really want this to be zero or one, however, because this is the first argument, it doesn't work.

0 commit comments

Comments
 (0)