Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend dotnet run to invoke a run-command-producing Target #42240

Merged
merged 40 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f8997ca
refactor for readability before starting work
baronfel Jul 18, 2024
7af8ca5
simple execution of new target during run evaluation
baronfel Jul 18, 2024
018541a
refactor to push validation into S.CL instead of in Execute
baronfel Jul 18, 2024
3472548
refactor to use FileINfo to reduce bugs froim using just String
baronfel Jul 19, 2024
a7e1315
Revert "refactor to use FileINfo to reduce bugs froim using just String"
baronfel Jul 22, 2024
c35ca62
fix directory resolution bug
baronfel Jul 22, 2024
77b6775
add ProjectCapability for ProjectSystem compatibility checking
baronfel Jul 31, 2024
fb0de87
Revert back to some prior less-strict handling to green up existing t…
baronfel Jul 31, 2024
2e6db66
Merge branch 'main' into extend-run-with-target
baronfel Aug 12, 2024
8e804b2
Correct dotnet-watch dependency
baronfel Aug 12, 2024
b5e6569
Fix run parsing tests to have a valid project file even if not direct…
baronfel Aug 12, 2024
deca1a2
Fix dotnet run tests to account for expected restore args
baronfel Aug 12, 2024
254d308
Make tests that check for -p project file usage green up
baronfel Aug 12, 2024
e4b18d0
Merge branch 'main' into extend-run-with-target
baronfel Aug 12, 2024
42b0e25
generate binlogs as we're testing things out
baronfel Aug 12, 2024
a01b1a6
remove dotnet-watch precompute hook since watch no longer gets run ar…
baronfel Aug 12, 2024
561d8b0
Fix dependent ordering of argument parsing
baronfel Aug 12, 2024
0bb7fc2
Fix run parsing tests in a way that doesn't impact other parsing tests.
baronfel Aug 13, 2024
4eb11f5
Update expectations since we emit long-forms for forwarded properties
baronfel Aug 13, 2024
dc9a792
Remove test that has odd side effect behavior
baronfel Aug 13, 2024
8967f61
Ensure that we check project applicability to validate conditions
baronfel Aug 13, 2024
f26ceb6
quick change to green up parser -- test
baronfel Aug 13, 2024
d2a3a06
Merge branch 'main' into extend-run-with-target
baronfel Aug 13, 2024
5817003
Refactor parsing tests to make them not clobber
baronfel Aug 13, 2024
26c6a73
Make test invocations more isolated
baronfel Aug 13, 2024
9e4e19f
Add test cases and error handling
baronfel Aug 14, 2024
adfcdd3
Create a terminal logger for evaluation-time errors
baronfel Aug 14, 2024
0507d5b
Fix verbosity check that tests caught
baronfel Aug 14, 2024
cd2add3
Strip nonvisible terminal logger progress indicators when comparing o…
baronfel Aug 14, 2024
ebfcb51
Merge branch 'main' into extend-run-with-target
baronfel Aug 15, 2024
068fafd
Merge branch 'main' into extend-run-with-target
baronfel Aug 19, 2024
06f953c
disable test and log issue to reenable
baronfel Aug 19, 2024
daa2434
Try to work around errors parsing stdout from the application being run
baronfel Aug 21, 2024
c1890fe
Safely parse stdout
baronfel Aug 21, 2024
86379f0
Unify TerminalLogger progress stripping code
baronfel Aug 21, 2024
e3d731d
green up watch tests by fixing stdout parsing
baronfel Aug 21, 2024
6c66576
Skip globbing test that works locally but fails in CI.
baronfel Aug 21, 2024
f9332b9
Fix a few more stdout-parsing issues in the dotnet run tests
baronfel Aug 21, 2024
8774bcb
disable one more test that works on local testing
baronfel Aug 22, 2024
39effde
disable one more test that works on local testing
baronfel Aug 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ cmake/

# MSBuild Logs
**/MSBuild_Logs/MSBuild_pid-*.failure.txt

# Test results
**/*.trx
2 changes: 1 addition & 1 deletion src/Cli/dotnet/CommonOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public static CliArgument<string> DefaultToCurrentDirectory(this CliArgument<str
{
Description = CommonLocalizableStrings.DisableBuildServersOptionDescription
}
.ForwardAsMany(_ => new string[] { "-p:UseRazorBuildServer=false", "-p:UseSharedCompilation=false", "/nodeReuse:false" });
.ForwardAsMany(_ => ["--property:UseRazorBuildServer=false", "--property:UseSharedCompilation=false", "/nodeReuse:false"]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testing purposes, since we're using the shared Property option, these output properties to forward to MSBuild are all unified to --project for maximal clarity.


public static CliOption<string> ArchitectureOption =
new ForwardedOption<string>("--arch", "-a")
Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/OptionForwardingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static ForwardedOption<string[]> ForwardAsProperty(this ForwardedOption<s
.SetForwardingFunction((optionVals) =>
optionVals
.SelectMany(Utils.MSBuildPropertyParser.ParseProperties)
.Select(keyValue => $"{option.Name}:{keyValue.key}={keyValue.value}")
.Select(keyValue => keyValue.value == "" ? $"{option.Name}:{keyValue.key}" : $"{option.Name}:{keyValue.key}={keyValue.value}")
);

public static CliOption<T> ForwardAsMany<T>(this ForwardedOption<T> option, Func<T, IEnumerable<string>> format) => option.SetForwardingFunction(format);
Expand Down
6 changes: 3 additions & 3 deletions src/Cli/dotnet/ParseResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static void ShowHelpOrErrorIfAppropriate(this ParseResult parseResult)
}
}

///<summary>Splits a .NET format string by the format placeholders (the {N} parts) to get an array of the literal parts, to be used in message-checking</summary>
///<summary>Splits a .NET format string by the format placeholders (the {N} parts) to get an array of the literal parts, to be used in message-checking</summary>
static string[] DistinctFormatStringParts(string formatString)
{
return Regex.Split(formatString, @"{[0-9]+}"); // match the literal '{', followed by any of 0-9 one or more times, followed by the literal '}'
Expand Down Expand Up @@ -173,8 +173,8 @@ public static bool BothArchAndOsOptionsSpecified(this ParseResult parseResult) =

internal static string GetCommandLineRuntimeIdentifier(this ParseResult parseResult)
{
return parseResult.HasOption(RunCommandParser.RuntimeOption) ?
parseResult.GetValue(RunCommandParser.RuntimeOption) :
return parseResult.HasOption(CommonOptions.RuntimeOption) ?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the bespoke options in favor of the common ones

parseResult.GetValue(CommonOptions.RuntimeOption) :
parseResult.HasOption(CommonOptions.OperatingSystemOption) ||
parseResult.HasOption(CommonOptions.ArchitectureOption) ||
parseResult.HasOption(CommonOptions.LongFormArchitectureOption) ?
Expand Down
60 changes: 32 additions & 28 deletions src/Cli/dotnet/commands/dotnet-run/LocalizableStrings.resx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema

Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple

There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -180,6 +180,10 @@ The current {1} is '{2}'.</value>
<value>The launch profile "{0}" could not be applied.
{1}</value>
</data>
<data name="RunCommandEvaluationExceptionBuildFailed" xml:space="preserve">
<value>Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again.</value>
<comment>{0} is the name of an MSBuild target</comment>
</data>
<data name="DefaultLaunchProfileDisplayName" xml:space="preserve">
<value>(Default)</value>
</data>
Expand All @@ -206,7 +210,7 @@ The current {1} is '{2}'.</value>
<value>An error was encountered when reading launchSettings.json.
{0}</value>
</data>
<data name="RunCommandSpecifiecFileIsNotAValidProject" xml:space="preserve">
<data name="RunCommandSpecifiedFileIsNotAValidProject" xml:space="preserve">
<value>'{0}' is not a valid project file.</value>
</data>
<data name="CouldNotConvertToBoolean" xml:space="preserve">
Expand Down
60 changes: 53 additions & 7 deletions src/Cli/dotnet/commands/dotnet-run/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,21 @@ public static RunCommand FromArgs(string[] args)

public static RunCommand FromParseResult(ParseResult parseResult)
{
var project = parseResult.GetValue(RunCommandParser.ProjectOption);
if (parseResult.UsingRunCommandShorthandProjectOption())
{
Reporter.Output.WriteLine(LocalizableStrings.RunCommandProjectAbbreviationDeprecated.Yellow());
project = parseResult.GetRunCommandShorthandProjectValues().FirstOrDefault();
parseResult = ModifyParseResultForShorthandProjectOption(parseResult);
}

var command = new RunCommand(
configuration: parseResult.GetValue(RunCommandParser.ConfigurationOption),
framework: parseResult.GetValue(RunCommandParser.FrameworkOption),
runtime: parseResult.GetCommandLineRuntimeIdentifier(),
noBuild: parseResult.HasOption(RunCommandParser.NoBuildOption),
project: project,
projectFileOrDirectory: parseResult.GetValue(RunCommandParser.ProjectOption),
launchProfile: parseResult.GetValue(RunCommandParser.LaunchProfileOption),
noLaunchProfile: parseResult.HasOption(RunCommandParser.NoLaunchProfileOption),
noRestore: parseResult.HasOption(RunCommandParser.NoRestoreOption) || parseResult.HasOption(RunCommandParser.NoBuildOption),
interactive: parseResult.HasOption(RunCommandParser.InteractiveOption),
restoreArgs: parseResult.OptionValuesToBeForwarded(RunCommandParser.GetCommand()),
verbosity: parseResult.HasOption(CommonOptions.VerbosityOption) ? parseResult.GetValue(CommonOptions.VerbosityOption) : null,
restoreArgs: parseResult.OptionValuesToBeForwarded(RunCommandParser.GetCommand()).ToArray(),
args: parseResult.GetValue(RunCommandParser.ApplicationArguments)
);

Expand All @@ -48,5 +45,54 @@ public static int Run(ParseResult parseResult)

return FromParseResult(parseResult).Execute();
}

public static ParseResult ModifyParseResultForShorthandProjectOption(ParseResult parseResult)
{
// we know the project is going to be one of the following forms:
// -p:project
// -p project
// so try to find those and filter them out of the arguments array
var possibleProject = parseResult.GetRunCommandShorthandProjectValues().FirstOrDefault()!;
var tokensMinusProject = new List<string>();
var nextTokenMayBeProject = false;
foreach (var token in parseResult.Tokens)
{
if (token.Value == "-p")
{
// skip this token, if the next token _is_ the project then we'll skip that too
// if the next token _isn't_ the project then we'll backfill
nextTokenMayBeProject = true;
continue;
}
else if (token.Value == possibleProject && nextTokenMayBeProject)
{
// skip, we've successfully stripped this option and value entirely
nextTokenMayBeProject = false;
continue;
}
else if (token.Value.StartsWith("-p") && token.Value.EndsWith(possibleProject))
{
// both option and value in the same token, skip and carry on
}
else
{
if (nextTokenMayBeProject)
{
//we skipped a -p, so backfill it
tokensMinusProject.Add("-p");
}
nextTokenMayBeProject = false;
}

tokensMinusProject.Add(token.Value);
}

tokensMinusProject.Add("--project");
tokensMinusProject.Add(possibleProject);

var tokensToParse = tokensMinusProject.ToArray();
var newParseResult = Parser.Instance.Parse(tokensToParse);
return newParseResult;
}
}
}
Loading