Skip to content

Commit 6b0ee2d

Browse files
Support msbuild params in dotnet test (#46562)
1 parent 81f73a0 commit 6b0ee2d

File tree

17 files changed

+280
-277
lines changed

17 files changed

+280
-277
lines changed

Diff for: src/Cli/dotnet/commands/dotnet-test/MSBuildHandler.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,21 @@ public bool RunMSBuild(BuildOptions buildOptions)
3333

3434
int msBuildExitCode;
3535
string path;
36+
PathOptions pathOptions = buildOptions.PathOptions;
3637

37-
if (!string.IsNullOrEmpty(buildOptions.ProjectPath))
38+
if (!string.IsNullOrEmpty(pathOptions.ProjectPath))
3839
{
39-
path = PathUtility.GetFullPath(buildOptions.ProjectPath);
40+
path = PathUtility.GetFullPath(pathOptions.ProjectPath);
4041
msBuildExitCode = RunBuild(path, isSolution: false, buildOptions);
4142
}
42-
else if (!string.IsNullOrEmpty(buildOptions.SolutionPath))
43+
else if (!string.IsNullOrEmpty(pathOptions.SolutionPath))
4344
{
44-
path = PathUtility.GetFullPath(buildOptions.SolutionPath);
45+
path = PathUtility.GetFullPath(pathOptions.SolutionPath);
4546
msBuildExitCode = RunBuild(path, isSolution: true, buildOptions);
4647
}
4748
else
4849
{
49-
path = PathUtility.GetFullPath(buildOptions.DirectoryPath ?? Directory.GetCurrentDirectory());
50+
path = PathUtility.GetFullPath(pathOptions.DirectoryPath ?? Directory.GetCurrentDirectory());
5051
msBuildExitCode = RunBuild(path, buildOptions);
5152
}
5253

Diff for: src/Cli/dotnet/commands/dotnet-test/MSBuildUtility.cs

+24-113
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Concurrent;
5-
using System.Collections.Immutable;
65
using Microsoft.Build.Evaluation;
76
using Microsoft.Build.Execution;
8-
using Microsoft.Build.Framework;
9-
using Microsoft.Build.Logging;
7+
using Microsoft.DotNet.Tools;
108
using Microsoft.DotNet.Tools.Common;
119
using Microsoft.VisualStudio.SolutionPersistence.Model;
1210

@@ -27,10 +25,7 @@ public static (IEnumerable<Module> Projects, bool IsBuiltOrRestored) GetProjects
2725
}
2826

2927
bool isBuiltOrRestored = BuildOrRestoreProjectOrSolution(
30-
solutionFilePath,
31-
projectCollection,
32-
buildOptions,
33-
GetCommands(buildOptions.HasNoRestore, buildOptions.HasNoBuild));
28+
solutionFilePath, buildOptions.MSBuildArgs, buildOptions.HasNoRestore, buildOptions.HasNoBuild);
3429

3530
ConcurrentBag<Module> projects = GetProjectsProperties(projectCollection, solutionModel.SolutionProjects.Select(p => Path.Combine(rootDirectory, p.FilePath)), buildOptions);
3631
return (projects, isBuiltOrRestored);
@@ -39,41 +34,39 @@ public static (IEnumerable<Module> Projects, bool IsBuiltOrRestored) GetProjects
3934
public static (IEnumerable<Module> Projects, bool IsBuiltOrRestored) GetProjectsFromProject(string projectFilePath, BuildOptions buildOptions)
4035
{
4136
var projectCollection = new ProjectCollection();
42-
bool isBuiltOrRestored = true;
4337

44-
if (!buildOptions.HasNoRestore)
45-
{
46-
isBuiltOrRestored = BuildOrRestoreProjectOrSolution(
47-
projectFilePath,
48-
projectCollection,
49-
buildOptions,
50-
[CliConstants.RestoreCommand]);
51-
}
52-
53-
if (!buildOptions.HasNoBuild)
54-
{
55-
isBuiltOrRestored = isBuiltOrRestored && BuildOrRestoreProjectOrSolution(
56-
projectFilePath,
57-
projectCollection,
58-
buildOptions,
59-
[CliConstants.BuildCommand]);
60-
}
38+
bool isBuiltOrRestored = BuildOrRestoreProjectOrSolution(projectFilePath, buildOptions.MSBuildArgs, buildOptions.HasNoRestore, buildOptions.HasNoBuild);
6139

6240
IEnumerable<Module> projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, GetGlobalProperties(buildOptions), projectCollection);
6341

6442
return (projects, isBuiltOrRestored);
6543
}
6644

67-
private static bool BuildOrRestoreProjectOrSolution(string filePath, ProjectCollection projectCollection, BuildOptions buildOptions, string[] commands)
45+
public static IEnumerable<string> GetPropertyTokens(IEnumerable<string> unmatchedTokens)
46+
{
47+
return unmatchedTokens.Where(token =>
48+
token.StartsWith("--property:", StringComparison.OrdinalIgnoreCase) ||
49+
token.StartsWith("/property:", StringComparison.OrdinalIgnoreCase) ||
50+
token.StartsWith("-p:", StringComparison.OrdinalIgnoreCase) ||
51+
token.StartsWith("/p:", StringComparison.OrdinalIgnoreCase));
52+
}
53+
54+
public static IEnumerable<string> GetBinaryLoggerTokens(IEnumerable<string> args)
6855
{
69-
var parameters = GetBuildParameters(projectCollection, buildOptions);
70-
var globalProperties = GetGlobalProperties(buildOptions);
56+
return args.Where(arg =>
57+
arg.StartsWith("/bl:", StringComparison.OrdinalIgnoreCase) || arg.Equals("/bl", StringComparison.OrdinalIgnoreCase) ||
58+
arg.StartsWith("--binaryLogger:", StringComparison.OrdinalIgnoreCase) || arg.Equals("--binaryLogger", StringComparison.OrdinalIgnoreCase) ||
59+
arg.StartsWith("-bl:", StringComparison.OrdinalIgnoreCase) || arg.Equals("-bl", StringComparison.OrdinalIgnoreCase));
60+
}
7161

72-
var buildRequestData = new BuildRequestData(filePath, globalProperties, null, commands, null);
62+
private static bool BuildOrRestoreProjectOrSolution(string filePath, List<string> arguments, bool hasNoRestore, bool hasNoBuild)
63+
{
64+
arguments.Add(filePath);
65+
arguments.Add("-target:_MTPBuild");
7366

74-
BuildResult buildResult = BuildManager.DefaultBuildManager.Build(parameters, buildRequestData);
67+
int result = new RestoringCommand(arguments, hasNoRestore || hasNoBuild).Execute();
7568

76-
return buildResult.OverallResult == BuildResultCode.Success;
69+
return result == (int)BuildResultCode.Success;
7770
}
7871

7972
private static ConcurrentBag<Module> GetProjectsProperties(ProjectCollection projectCollection, IEnumerable<string> projects, BuildOptions buildOptions)
@@ -107,71 +100,6 @@ private static string HandleFilteredSolutionFilePath(string solutionFilterFilePa
107100
return solutionFullPath;
108101
}
109102

110-
internal static bool IsBinaryLoggerEnabled(ref List<string> args, out string binLogFileName)
111-
{
112-
binLogFileName = string.Empty;
113-
var binLogArgs = new List<string>();
114-
115-
foreach (var arg in args)
116-
{
117-
if (arg.StartsWith("/bl:") || arg.Equals("/bl")
118-
|| arg.StartsWith("--binaryLogger:") || arg.Equals("--binaryLogger")
119-
|| arg.StartsWith("-bl:") || arg.Equals("-bl"))
120-
{
121-
binLogArgs.Add(arg);
122-
123-
}
124-
}
125-
126-
if (binLogArgs.Count > 0)
127-
{
128-
// Remove all BinLog args from the list of args
129-
args.RemoveAll(arg => binLogArgs.Contains(arg));
130-
131-
// Get BinLog filename
132-
var binLogArg = binLogArgs.LastOrDefault();
133-
134-
if (binLogArg.Contains(CliConstants.Colon))
135-
{
136-
var parts = binLogArg.Split(CliConstants.Colon, 2);
137-
binLogFileName = !string.IsNullOrEmpty(parts[1]) ? parts[1] : CliConstants.BinLogFileName;
138-
}
139-
else
140-
{
141-
binLogFileName = CliConstants.BinLogFileName;
142-
}
143-
144-
return true;
145-
}
146-
147-
return false;
148-
}
149-
150-
private static BuildParameters GetBuildParameters(ProjectCollection projectCollection, BuildOptions buildOptions)
151-
{
152-
BuildParameters parameters = new(projectCollection)
153-
{
154-
Loggers = [new ConsoleLogger(LoggerVerbosity.Quiet)]
155-
};
156-
157-
if (!buildOptions.AllowBinLog)
158-
return parameters;
159-
160-
parameters.Loggers =
161-
[
162-
.. parameters.Loggers,
163-
.. new[]
164-
{
165-
new BinaryLogger
166-
{
167-
Parameters = buildOptions.BinLogFileName
168-
}
169-
},
170-
];
171-
172-
return parameters;
173-
}
174-
175103
private static Dictionary<string, string> GetGlobalProperties(BuildOptions buildOptions)
176104
{
177105
var globalProperties = new Dictionary<string, string>();
@@ -188,22 +116,5 @@ private static Dictionary<string, string> GetGlobalProperties(BuildOptions build
188116

189117
return globalProperties;
190118
}
191-
192-
private static string[] GetCommands(bool hasNoRestore, bool hasNoBuild)
193-
{
194-
var commands = ImmutableArray.CreateBuilder<string>();
195-
196-
if (!hasNoRestore)
197-
{
198-
commands.Add(CliConstants.RestoreCommand);
199-
}
200-
201-
if (!hasNoBuild)
202-
{
203-
commands.Add(CliConstants.BuildCommand);
204-
}
205-
206-
return [.. commands];
207-
}
208119
}
209120
}

Diff for: src/Cli/dotnet/commands/dotnet-test/Options.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ namespace Microsoft.DotNet.Cli
55
{
66
internal record TestOptions(bool HasListTests, string Configuration, string Architecture, bool HasFilterMode, bool IsHelp);
77

8-
internal record BuildOptions(string ProjectPath, string SolutionPath, string DirectoryPath, bool HasNoRestore, bool HasNoBuild, string Configuration, string RuntimeIdentifier, bool AllowBinLog, string BinLogFileName, int DegreeOfParallelism, List<string> UnmatchedTokens);
8+
internal record PathOptions(string ProjectPath, string SolutionPath, string DirectoryPath);
9+
10+
internal record BuildOptions(PathOptions PathOptions, bool HasNoRestore, bool HasNoBuild, string Configuration, string RuntimeIdentifier, int DegreeOfParallelism, List<string> UnmatchedTokens, List<string> MSBuildArgs);
911
}

Diff for: src/Cli/dotnet/commands/dotnet-test/TestApplication.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ private string BuildArgsWithDotnetRun(TestOptions testOptions)
304304
builder.Append($"{CliConstants.DotnetRunCommand} {TestingPlatformOptions.ProjectOption.Name} \"{_module.ProjectFullPath}\"");
305305

306306
// Because we restored and built before in MSHandler, we will skip those with dotnet run
307-
builder.Append($" {TestingPlatformOptions.NoRestoreOption.Name}");
307+
builder.Append($" {CommonOptions.NoRestoreOption.Name}");
308308
builder.Append($" {TestingPlatformOptions.NoBuildOption.Name}");
309309

310310
if (!string.IsNullOrEmpty(testOptions.Architecture))

Diff for: src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,10 @@ private static CliCommand GetTestingPlatformCliCommand()
182182
var command = new TestingPlatformCommand("test");
183183
command.SetAction(parseResult => command.Run(parseResult));
184184
command.Options.Add(TestingPlatformOptions.MaxParallelTestModulesOption);
185-
command.Options.Add(TestingPlatformOptions.AdditionalMSBuildParametersOption);
186185
command.Options.Add(TestingPlatformOptions.TestModulesFilterOption);
187186
command.Options.Add(TestingPlatformOptions.TestModulesRootDirectoryOption);
188187
command.Options.Add(TestingPlatformOptions.NoBuildOption);
189-
command.Options.Add(TestingPlatformOptions.NoRestoreOption);
188+
command.Options.Add(CommonOptions.NoRestoreOption);
190189
command.Options.Add(TestingPlatformOptions.ArchitectureOption);
191190
command.Options.Add(TestingPlatformOptions.ConfigurationOption);
192191
command.Options.Add(TestingPlatformOptions.ProjectOption);

Diff for: src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs

+24-12
Original file line numberDiff line numberDiff line change
@@ -174,22 +174,34 @@ private static TestOptions GetTestOptions(ParseResult parseResult, bool hasFilte
174174

175175
private static BuildOptions GetBuildOptions(ParseResult parseResult, int degreeOfParallelism)
176176
{
177+
IEnumerable<string> propertyTokens = MSBuildUtility.GetPropertyTokens(parseResult.UnmatchedTokens);
178+
IEnumerable<string> binaryLoggerTokens = MSBuildUtility.GetBinaryLoggerTokens(parseResult.UnmatchedTokens);
179+
180+
var msbuildArgs = parseResult.OptionValuesToBeForwarded(TestCommandParser.GetCommand())
181+
.Concat(propertyTokens)
182+
.Concat(binaryLoggerTokens).ToList();
183+
177184
List<string> unmatchedTokens = [.. parseResult.UnmatchedTokens];
178-
bool allowBinLog = MSBuildUtility.IsBinaryLoggerEnabled(ref unmatchedTokens, out string binLogFileName);
185+
unmatchedTokens.RemoveAll(arg => propertyTokens.Contains(arg));
186+
unmatchedTokens.RemoveAll(arg => binaryLoggerTokens.Contains(arg));
179187

180-
return new BuildOptions(parseResult.GetValue(TestingPlatformOptions.ProjectOption),
188+
PathOptions pathOptions = new(parseResult.GetValue(TestingPlatformOptions.ProjectOption),
181189
parseResult.GetValue(TestingPlatformOptions.SolutionOption),
182-
parseResult.GetValue(TestingPlatformOptions.DirectoryOption),
183-
parseResult.HasOption(TestingPlatformOptions.NoRestoreOption),
184-
parseResult.HasOption(TestingPlatformOptions.NoBuildOption),
185-
parseResult.GetValue(TestingPlatformOptions.ConfigurationOption),
186-
parseResult.HasOption(TestingPlatformOptions.ArchitectureOption) ?
190+
parseResult.GetValue(TestingPlatformOptions.DirectoryOption));
191+
192+
string runtimeIdentifier = parseResult.HasOption(TestingPlatformOptions.ArchitectureOption) ?
187193
CommonOptions.ResolveRidShorthandOptionsToRuntimeIdentifier(string.Empty, parseResult.GetValue(TestingPlatformOptions.ArchitectureOption)) :
188-
string.Empty,
189-
allowBinLog,
190-
binLogFileName,
194+
string.Empty;
195+
196+
return new BuildOptions(
197+
pathOptions,
198+
parseResult.GetValue(CommonOptions.NoRestoreOption),
199+
parseResult.GetValue(TestingPlatformOptions.NoBuildOption),
200+
parseResult.GetValue(TestingPlatformOptions.ConfigurationOption),
201+
runtimeIdentifier,
191202
degreeOfParallelism,
192-
unmatchedTokens);
203+
unmatchedTokens,
204+
msbuildArgs);
193205
}
194206

195207
private static bool ContainsHelpOption(IEnumerable<string> args) => args.Contains(CliConstants.HelpOptionKey) || args.Contains(CliConstants.HelpOptionKey.Substring(0, 2));
@@ -204,7 +216,7 @@ private void CompleteRun()
204216

205217
private void CleanUp()
206218
{
207-
_msBuildHandler.Dispose();
219+
_msBuildHandler?.Dispose();
208220
foreach (var execution in _executions)
209221
{
210222
execution.Key.Dispose();

Diff for: src/Cli/dotnet/commands/dotnet-test/TestingPlatformOptions.cs

+7-19
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ internal static class TestingPlatformOptions
1313
Description = LocalizableStrings.CmdMaxParallelTestModulesDescription,
1414
};
1515

16-
public static readonly CliOption<string> AdditionalMSBuildParametersOption = new("--additional-msbuild-parameters")
17-
{
18-
Description = LocalizableStrings.CmdAdditionalMSBuildParametersDescription,
19-
};
20-
2116
public static readonly CliOption<string> TestModulesFilterOption = new("--test-modules")
2217
{
2318
Description = LocalizableStrings.CmdTestModulesDescription
@@ -28,29 +23,22 @@ internal static class TestingPlatformOptions
2823
Description = LocalizableStrings.CmdTestModulesRootDirectoryDescription
2924
};
3025

31-
public static readonly CliOption<string> NoBuildOption = new("--no-build")
26+
public static readonly CliOption<bool> NoBuildOption = new ForwardedOption<bool>("--no-build")
3227
{
33-
Description = LocalizableStrings.CmdNoBuildDescription,
34-
Arity = ArgumentArity.Zero
35-
};
28+
Description = LocalizableStrings.CmdNoBuildDescription
29+
}.ForwardAs("-property:MTPNoBuild=true");
3630

37-
public static readonly CliOption<string> NoRestoreOption = new("--no-restore")
38-
{
39-
Description = LocalizableStrings.CmdNoRestoreDescription,
40-
Arity = ArgumentArity.Zero
41-
};
42-
43-
public static readonly CliOption<string> ArchitectureOption = new("--arch")
31+
public static readonly CliOption<string> ArchitectureOption = new ForwardedOption<string>("--arch", "-a")
4432
{
4533
Description = LocalizableStrings.CmdArchitectureDescription,
4634
Arity = ArgumentArity.ExactlyOne
47-
};
35+
}.SetForwardingFunction(CommonOptions.ResolveArchOptionToRuntimeIdentifier);
4836

49-
public static readonly CliOption<string> ConfigurationOption = new("--configuration")
37+
public static readonly CliOption<string> ConfigurationOption = new ForwardedOption<string>("--configuration", "-c")
5038
{
5139
Description = LocalizableStrings.CmdConfigurationDescription,
5240
Arity = ArgumentArity.ExactlyOne
53-
};
41+
}.ForwardAsSingle(p => $"/p:configuration={p}");
5442

5543
public static readonly CliOption<string> ProjectOption = new("--project")
5644
{

Diff for: src/Cli/dotnet/commands/dotnet-test/ValidationUtility.cs

+10-9
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,28 @@ internal static class ValidationUtility
1010
{
1111
public static bool ValidateBuildPathOptions(BuildOptions buildPathOptions, TerminalTestReporter output)
1212
{
13-
if ((!string.IsNullOrEmpty(buildPathOptions.ProjectPath) && !string.IsNullOrEmpty(buildPathOptions.SolutionPath)) ||
14-
(!string.IsNullOrEmpty(buildPathOptions.ProjectPath) && !string.IsNullOrEmpty(buildPathOptions.DirectoryPath)) ||
15-
(!string.IsNullOrEmpty(buildPathOptions.SolutionPath) && !string.IsNullOrEmpty(buildPathOptions.DirectoryPath)))
13+
PathOptions pathOptions = buildPathOptions.PathOptions;
14+
if ((!string.IsNullOrEmpty(pathOptions.ProjectPath) && !string.IsNullOrEmpty(pathOptions.SolutionPath)) ||
15+
(!string.IsNullOrEmpty(pathOptions.ProjectPath) && !string.IsNullOrEmpty(pathOptions.DirectoryPath)) ||
16+
(!string.IsNullOrEmpty(pathOptions.SolutionPath) && !string.IsNullOrEmpty(pathOptions.DirectoryPath)))
1617
{
1718
output.WriteMessage(LocalizableStrings.CmdMultipleBuildPathOptionsErrorDescription);
1819
return false;
1920
}
2021

21-
if (!string.IsNullOrEmpty(buildPathOptions.ProjectPath))
22+
if (!string.IsNullOrEmpty(pathOptions.ProjectPath))
2223
{
23-
return ValidateFilePath(buildPathOptions.ProjectPath, CliConstants.ProjectExtensions, LocalizableStrings.CmdInvalidProjectFileExtensionErrorDescription, output);
24+
return ValidateFilePath(pathOptions.ProjectPath, CliConstants.ProjectExtensions, LocalizableStrings.CmdInvalidProjectFileExtensionErrorDescription, output);
2425
}
2526

26-
if (!string.IsNullOrEmpty(buildPathOptions.SolutionPath))
27+
if (!string.IsNullOrEmpty(pathOptions.SolutionPath))
2728
{
28-
return ValidateFilePath(buildPathOptions.SolutionPath, CliConstants.SolutionExtensions, LocalizableStrings.CmdInvalidSolutionFileExtensionErrorDescription, output);
29+
return ValidateFilePath(pathOptions.SolutionPath, CliConstants.SolutionExtensions, LocalizableStrings.CmdInvalidSolutionFileExtensionErrorDescription, output);
2930
}
3031

31-
if (!string.IsNullOrEmpty(buildPathOptions.DirectoryPath) && !Directory.Exists(buildPathOptions.DirectoryPath))
32+
if (!string.IsNullOrEmpty(pathOptions.DirectoryPath) && !Directory.Exists(pathOptions.DirectoryPath))
3233
{
33-
output.WriteMessage(string.Format(LocalizableStrings.CmdNonExistentDirectoryErrorDescription, buildPathOptions.DirectoryPath));
34+
output.WriteMessage(string.Format(LocalizableStrings.CmdNonExistentDirectoryErrorDescription, pathOptions.DirectoryPath));
3435
return false;
3536
}
3637

0 commit comments

Comments
 (0)