-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Allow reading file-based app from stdin #49348
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
Changes from all commits
709196d
6720dac
cf01cd6
c82b3a9
3a2cf8a
514d09e
5077f84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
|
||
using System.Collections.Immutable; | ||
using System.CommandLine; | ||
using System.CommandLine.Parsing; | ||
using System.Diagnostics; | ||
using Microsoft.Build.Evaluation; | ||
using Microsoft.Build.Exceptions; | ||
|
@@ -22,11 +23,6 @@ public class RunCommand | |
{ | ||
public bool NoBuild { get; } | ||
|
||
/// <summary> | ||
/// Value of the <c>--project</c> option. | ||
/// </summary> | ||
public string? ProjectFileOrDirectory { get; } | ||
|
||
/// <summary> | ||
/// Full path to a project file to run. | ||
/// <see langword="null"/> if running without a project file | ||
|
@@ -39,6 +35,13 @@ public class RunCommand | |
/// </summary> | ||
public string? EntryPointFileFullPath { get; } | ||
|
||
/// <summary> | ||
/// Whether <c>dotnet run -</c> is being executed. | ||
/// In that case, <see cref="EntryPointFileFullPath"/> points to a temporary file | ||
/// containing all text read from the standard input. | ||
/// </summary> | ||
public bool ReadCodeFromStdin { get; } | ||
|
||
public string[] Args { get; set; } | ||
public bool NoRestore { get; } | ||
public bool NoCache { get; } | ||
|
@@ -63,7 +66,8 @@ public class RunCommand | |
|
||
public RunCommand( | ||
bool noBuild, | ||
string? projectFileOrDirectory, | ||
string? projectFileFullPath, | ||
string? entryPointFileFullPath, | ||
string? launchProfile, | ||
bool noLaunchProfile, | ||
bool noLaunchProfileArguments, | ||
|
@@ -73,12 +77,16 @@ public RunCommand( | |
VerbosityOptions? verbosity, | ||
string[] restoreArgs, | ||
string[] args, | ||
bool readCodeFromStdin, | ||
IReadOnlyDictionary<string, string> environmentVariables) | ||
{ | ||
Debug.Assert(projectFileFullPath is null ^ entryPointFileFullPath is null); | ||
Debug.Assert(!readCodeFromStdin || entryPointFileFullPath is not null); | ||
|
||
NoBuild = noBuild; | ||
ProjectFileOrDirectory = projectFileOrDirectory; | ||
ProjectFileFullPath = DiscoverProjectFilePath(projectFileOrDirectory, ref args, out string? entryPointFileFullPath); | ||
ProjectFileFullPath = projectFileFullPath; | ||
EntryPointFileFullPath = entryPointFileFullPath; | ||
ReadCodeFromStdin = readCodeFromStdin; | ||
LaunchProfile = launchProfile; | ||
NoLaunchProfile = noLaunchProfile; | ||
NoLaunchProfileArguments = noLaunchProfileArguments; | ||
|
@@ -117,6 +125,7 @@ public int Execute() | |
|
||
if (EntryPointFileFullPath is not null) | ||
{ | ||
Debug.Assert(!ReadCodeFromStdin); | ||
projectFactory = CreateVirtualCommand().PrepareProjectInstance().CreateProjectInstance; | ||
} | ||
} | ||
|
@@ -180,7 +189,7 @@ internal bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel | |
return true; | ||
} | ||
|
||
var launchSettingsPath = TryFindLaunchSettings(ProjectFileFullPath ?? EntryPointFileFullPath!); | ||
var launchSettingsPath = ReadCodeFromStdin ? null : TryFindLaunchSettings(ProjectFileFullPath ?? EntryPointFileFullPath!); | ||
if (!File.Exists(launchSettingsPath)) | ||
{ | ||
if (!string.IsNullOrEmpty(LaunchProfile)) | ||
|
@@ -437,7 +446,7 @@ private static void ThrowUnableToRunError(ProjectInstance project) | |
project.GetPropertyValue("OutputType"))); | ||
} | ||
|
||
private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath) | ||
private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, bool readCodeFromStdin, ref string[] args, out string? entryPointFilePath) | ||
{ | ||
bool emptyProjectOption = false; | ||
if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath)) | ||
|
@@ -453,7 +462,7 @@ private static void ThrowUnableToRunError(ProjectInstance project) | |
// If no project exists in the directory and no --project was given, | ||
// try to resolve an entry-point file instead. | ||
entryPointFilePath = projectFilePath is null && emptyProjectOption | ||
? TryFindEntryPointFilePath(ref args) | ||
? TryFindEntryPointFilePath(readCodeFromStdin, ref args) | ||
: null; | ||
|
||
if (entryPointFilePath is null && projectFilePath is null) | ||
|
@@ -480,16 +489,27 @@ private static void ThrowUnableToRunError(ProjectInstance project) | |
return projectFiles[0]; | ||
} | ||
|
||
static string? TryFindEntryPointFilePath(ref string[] args) | ||
static string? TryFindEntryPointFilePath(bool readCodeFromStdin, ref string[] args) | ||
{ | ||
if (args is not [{ } arg, ..] || | ||
!VirtualProjectBuildingCommand.IsValidEntryPointPath(arg)) | ||
if (args is not [{ } arg, ..]) | ||
{ | ||
return null; | ||
} | ||
|
||
if (!readCodeFromStdin) | ||
{ | ||
if (VirtualProjectBuildingCommand.IsValidEntryPointPath(arg)) | ||
{ | ||
arg = Path.GetFullPath(arg); | ||
} | ||
else | ||
{ | ||
return null; | ||
} | ||
} | ||
|
||
args = args[1..]; | ||
return Path.GetFullPath(arg); | ||
return arg; | ||
} | ||
} | ||
|
||
|
@@ -521,9 +541,44 @@ public static RunCommand FromParseResult(ParseResult parseResult) | |
restoreArgs.AddRange(binLogArgs); | ||
} | ||
|
||
// Only consider `-` to mean "read code from stdin" if it is before double dash `--` | ||
// (otherwise it should be fowarded to the target application as its command-line argument). | ||
bool readCodeFromStdin = nonBinLogArgs is ["-", ..] && | ||
parseResult.Tokens.TakeWhile(static t => t.Type != TokenType.DoubleDash) | ||
.Any(static t => t is { Type: TokenType.Argument, Value: "-" }); | ||
|
||
string? projectOption = parseResult.GetValue(RunCommandParser.ProjectOption); | ||
|
||
string[] args = [.. nonBinLogArgs]; | ||
string? projectFilePath = DiscoverProjectFilePath(projectOption, readCodeFromStdin, ref args, out string? entryPointFilePath); | ||
|
||
bool noBuild = parseResult.HasOption(RunCommandParser.NoBuildOption); | ||
|
||
if (readCodeFromStdin && entryPointFilePath != null) | ||
{ | ||
Debug.Assert(projectFilePath is null && entryPointFilePath is "-"); | ||
|
||
if (noBuild) | ||
{ | ||
throw new GracefulException(CliCommandStrings.InvalidOptionForStdin, RunCommandParser.NoBuildOption.Name); | ||
} | ||
|
||
// If '-' is specified as the input file, read all text from stdin into a temporary file and use that as the entry point. | ||
entryPointFilePath = Path.GetTempFileName(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On Unix can't assume that only current user has access to this file path. Don't we need to enforce current user permissions? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. It seems that GetTempFileName uses mkstemps which creates the file with 0600 permissions. I'm not sure if all that is guaranteed, but I'd hope it is, I see GetTempFileName used all over the place without any additional code to enforce the unix mode. |
||
using (var stdinStream = Console.OpenStandardInput()) | ||
using (var fileStream = File.OpenWrite(entryPointFilePath)) | ||
{ | ||
stdinStream.CopyTo(fileStream); | ||
} | ||
|
||
Debug.Assert(nonBinLogArgs[0] == "-"); | ||
nonBinLogArgs[0] = entryPointFilePath; | ||
} | ||
|
||
var command = new RunCommand( | ||
noBuild: parseResult.HasOption(RunCommandParser.NoBuildOption), | ||
projectFileOrDirectory: parseResult.GetValue(RunCommandParser.ProjectOption), | ||
noBuild: noBuild, | ||
projectFileFullPath: projectFilePath, | ||
entryPointFileFullPath: entryPointFilePath, | ||
launchProfile: parseResult.GetValue(RunCommandParser.LaunchProfileOption) ?? string.Empty, | ||
noLaunchProfile: parseResult.HasOption(RunCommandParser.NoLaunchProfileOption), | ||
noLaunchProfileArguments: parseResult.HasOption(RunCommandParser.NoLaunchProfileArgumentsOption), | ||
|
@@ -532,7 +587,8 @@ public static RunCommand FromParseResult(ParseResult parseResult) | |
interactive: parseResult.GetValue(RunCommandParser.InteractiveOption), | ||
verbosity: parseResult.HasOption(CommonOptions.VerbosityOption) ? parseResult.GetValue(CommonOptions.VerbosityOption) : null, | ||
restoreArgs: [.. restoreArgs], | ||
args: [.. nonBinLogArgs], | ||
args: args, | ||
readCodeFromStdin: readCodeFromStdin, | ||
environmentVariables: parseResult.GetValue(CommonOptions.EnvOption) ?? ImmutableDictionary<string, string>.Empty | ||
); | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.