Skip to content

Commit c804fbb

Browse files
author
Jason Zhai
committed
Merge branch 'main' of https://github.com/dotnet/sdk into darc-main-3dfc7a05-ee14-4fd8-82fa-dc3a820e2bd0
2 parents f9059c6 + 34ae4f2 commit c804fbb

30 files changed

+532
-53
lines changed

documentation/general/dotnet-run-file.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ We want to report an error for non-entry-point files to avoid the confusion of b
7171

7272
Internally, the SDK CLI detects entry points by parsing all `.cs` files in the directory tree of the entry point file with default parsing options (in particular, no `<DefineConstants>`)
7373
and checking which ones contain top-level statements (`Main` methods are not supported for now as that would require full semantic analysis, not just parsing).
74-
Results of this detection are used to exclude other entry points from [builds](#multiple-entry-points) and [app directive collection](#directives-for-project-metadata).
74+
Results of this detection are used to exclude other entry points from [builds](#multiple-entry-points) and [file-level directive collection](#directives-for-project-metadata).
7575
This means the CLI might consider a file to be an entry point which later the compiler doesn't
7676
(for example because its top-level statements are under `#if !SYMBOL` and the build has `DefineConstants=SYMBOL`).
7777
However such inconsistencies should be rare and hence that is a better trade off than letting the compiler decide which files are entry points
78-
because that could require multiple builds (first determine entry points and then re-build with app directives except those from other entry points).
78+
because that could require multiple builds (first determine entry points and then re-build with file-level directives except those from other entry points).
7979
To avoid parsing all C# files twice (in CLI and in the compiler), the CLI could use the compiler server for parsing so the trees are reused
8080
(unless the parse options change via the directives), and also [cache](#optimizations) the results to avoid parsing on subsequent runs.
8181

@@ -146,7 +146,7 @@ They are not cleaned immediately because they can be re-used on subsequent runs
146146

147147
## Directives for project metadata
148148

149-
It is possible to specify some project metadata via *app directives*
149+
It is possible to specify some project metadata via *file-level directives*
150150
which are [ignored][ignored-directives] by the C# language but recognized by the SDK CLI.
151151
Directives `sdk`, `package`, and `property` are translated into `<Project Sdk="...">`, `<PackageReference>`, and `<Property>` project elements, respectively.
152152
Other directives result in an error, reserving them for future use.

src/Cli/Microsoft.DotNet.Cli.Utils/BuiltInCommand.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,9 @@ public ICommand SetCommandArgs(string commandArgs)
181181
{
182182
throw new NotImplementedException();
183183
}
184+
185+
public ICommand StandardOutputEncoding(Encoding encoding)
186+
{
187+
throw new NotImplementedException();
188+
}
184189
}

src/Cli/Microsoft.DotNet.Cli.Utils/Command.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ public ICommand EnvironmentVariable(string name, string? value)
101101
return this;
102102
}
103103

104+
public ICommand StandardOutputEncoding(Encoding encoding)
105+
{
106+
_process.StartInfo.StandardOutputEncoding = encoding;
107+
return this;
108+
}
109+
104110
public ICommand CaptureStdOut()
105111
{
106112
ThrowIfRunning();

src/Cli/Microsoft.DotNet.Cli.Utils/ICommand.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public interface ICommand
2525

2626
ICommand SetCommandArgs(string commandArgs);
2727

28+
ICommand StandardOutputEncoding(Encoding encoding);
29+
2830
string CommandName { get; }
2931

3032
string CommandArgs { get; }

src/Cli/dotnet/Commands/CliCommandStrings.resx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,10 @@ Tool '{1}' (version '{2}') was successfully installed. Entry is added to the man
15131513
<value>Some directives cannot be converted: the first error is at {0}. Run the file to see all compilation errors. Specify '--force' to convert anyway.</value>
15141514
<comment>{Locked="--force"}. {0} is the file path and line number.</comment>
15151515
</data>
1516+
<data name="DuplicateDirective" xml:space="preserve">
1517+
<value>Duplicate directives are not supported: {0} at {1}</value>
1518+
<comment>{0} is the directive type and name. {1} is the file path and line number.</comment>
1519+
</data>
15161520
<data name="InvalidOptionCombination" xml:space="preserve">
15171521
<value>Cannot combine option '{0}' and '{1}'.</value>
15181522
<comment>{0} and {1} are option names like '--no-build'.</comment>

src/Cli/dotnet/Commands/Run/RunCommand.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ public int Execute()
110110
}
111111
else
112112
{
113-
if (EntryPointFileFullPath is not null)
113+
if (NoCache)
114114
{
115-
projectFactory = CreateVirtualCommand().PrepareProjectInstance().CreateProjectInstance;
115+
throw new GracefulException(CliCommandStrings.InvalidOptionCombination, RunCommandParser.NoCacheOption.Name, RunCommandParser.NoBuildOption.Name);
116116
}
117117

118-
if (NoCache)
118+
if (EntryPointFileFullPath is not null)
119119
{
120-
throw new GracefulException(CliCommandStrings.InvalidOptionCombination, RunCommandParser.NoCacheOption.Name, RunCommandParser.NoBuildOption.Name);
120+
projectFactory = CreateVirtualCommand().PrepareProjectInstance().CreateProjectInstance;
121121
}
122122
}
123123

src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ public VirtualProjectBuildingCommand(
8787
public override int Execute()
8888
{
8989
Debug.Assert(!(NoRestore && NoBuild));
90-
9190
var consoleLogger = RunCommand.MakeTerminalLogger(Verbosity);
9291
var binaryLogger = GetBinaryLogger(BinaryLoggerArgs);
9392

@@ -97,11 +96,6 @@ public override int Execute()
9796
{
9897
if (NoCache)
9998
{
100-
if (NoRestore)
101-
{
102-
throw new GracefulException(CliCommandStrings.InvalidOptionCombination, RunCommandParser.NoCacheOption.Name, RunCommandParser.NoRestoreOption.Name);
103-
}
104-
10599
cacheEntry = ComputeCacheEntry(out _);
106100
}
107101
else if (!NeedsToBuild(out cacheEntry))
@@ -625,6 +619,15 @@ public static void WriteProjectFile(
625619
626620
""");
627621

622+
var targetDirectory = Path.GetDirectoryName(targetFilePath) ?? "";
623+
writer.WriteLine($"""
624+
<ItemGroup>
625+
<RuntimeHostConfigurationOption Include="EntryPointFilePath" Value="{EscapeValue(targetFilePath)}" />
626+
<RuntimeHostConfigurationOption Include="EntryPointFileDirectoryPath" Value="{EscapeValue(targetDirectory)}" />
627+
</ItemGroup>
628+
629+
""");
630+
628631
foreach (var sdk in sdkDirectives)
629632
{
630633
WriteImport(writer, "Sdk.targets", sdk);
@@ -709,6 +712,7 @@ public static ImmutableArray<CSharpDirective> FindDirectives(SourceFile sourceFi
709712
{
710713
#pragma warning disable RSEXPERIMENTAL003 // 'SyntaxTokenParser' is experimental
711714

715+
var deduplicated = new HashSet<CSharpDirective.Named>(NamedDirectiveComparer.Instance);
712716
var builder = ImmutableArray.CreateBuilder<CSharpDirective>();
713717
SyntaxTokenParser tokenizer = SyntaxFactory.CreateTokenParser(sourceFile.Text,
714718
CSharpParseOptions.Default.WithFeatures([new("FileBasedProgram", "true")]));
@@ -750,6 +754,28 @@ public static ImmutableArray<CSharpDirective> FindDirectives(SourceFile sourceFi
750754

751755
if (CSharpDirective.Parse(errors, sourceFile, span, name.ToString(), value.ToString()) is { } directive)
752756
{
757+
// If the directive is already present, report an error.
758+
if (deduplicated.TryGetValue(directive, out var existingDirective))
759+
{
760+
var typeAndName = $"#:{existingDirective.GetType().Name.ToLowerInvariant()} {existingDirective.Name}";
761+
if (errors != null)
762+
{
763+
errors.Add(new SimpleDiagnostic
764+
{
765+
Location = sourceFile.GetFileLinePositionSpan(directive.Span),
766+
Message = string.Format(CliCommandStrings.DuplicateDirective, typeAndName, sourceFile.GetLocationString(directive.Span)),
767+
});
768+
}
769+
else
770+
{
771+
throw new GracefulException(CliCommandStrings.DuplicateDirective, typeAndName, sourceFile.GetLocationString(directive.Span));
772+
}
773+
}
774+
else
775+
{
776+
deduplicated.Add(directive);
777+
}
778+
753779
builder.Add(directive);
754780
}
755781
}
@@ -872,7 +898,8 @@ internal static partial class Patterns
872898
}
873899

874900
/// <summary>
875-
/// Represents a C# directive starting with <c>#:</c>. Those are ignored by the language but recognized by us.
901+
/// Represents a C# directive starting with <c>#:</c> (a.k.a., "file-level directive").
902+
/// Those are ignored by the language but recognized by us.
876903
/// </summary>
877904
internal abstract class CSharpDirective
878905
{
@@ -883,14 +910,14 @@ private CSharpDirective() { }
883910
/// </summary>
884911
public required TextSpan Span { get; init; }
885912

886-
public static CSharpDirective? Parse(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText)
913+
public static Named? Parse(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText)
887914
{
888915
return directiveKind switch
889916
{
890917
"sdk" => Sdk.Parse(errors, sourceFile, span, directiveKind, directiveText),
891918
"property" => Property.Parse(errors, sourceFile, span, directiveKind, directiveText),
892919
"package" => Package.Parse(errors, sourceFile, span, directiveKind, directiveText),
893-
_ => ReportError<CSharpDirective>(errors, sourceFile, span, string.Format(CliCommandStrings.UnrecognizedDirective, directiveKind, sourceFile.GetLocationString(span))),
920+
_ => ReportError<Named>(errors, sourceFile, span, string.Format(CliCommandStrings.UnrecognizedDirective, directiveKind, sourceFile.GetLocationString(span))),
894921
};
895922
}
896923

@@ -933,14 +960,18 @@ private static (string, string?)? ParseOptionalTwoParts(ImmutableArray<SimpleDia
933960
/// </summary>
934961
public sealed class Shebang : CSharpDirective;
935962

963+
public abstract class Named : CSharpDirective
964+
{
965+
public required string Name { get; init; }
966+
}
967+
936968
/// <summary>
937969
/// <c>#:sdk</c> directive.
938970
/// </summary>
939-
public sealed class Sdk : CSharpDirective
971+
public sealed class Sdk : Named
940972
{
941973
private Sdk() { }
942974

943-
public required string Name { get; init; }
944975
public string? Version { get; init; }
945976

946977
public static new Sdk? Parse(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText)
@@ -967,11 +998,10 @@ public string ToSlashDelimitedString()
967998
/// <summary>
968999
/// <c>#:property</c> directive.
9691000
/// </summary>
970-
public sealed class Property : CSharpDirective
1001+
public sealed class Property : Named
9711002
{
9721003
private Property() { }
9731004

974-
public required string Name { get; init; }
9751005
public required string Value { get; init; }
9761006

9771007
public static new Property? Parse(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText)
@@ -1007,13 +1037,12 @@ private Property() { }
10071037
/// <summary>
10081038
/// <c>#:package</c> directive.
10091039
/// </summary>
1010-
public sealed class Package : CSharpDirective
1040+
public sealed class Package : Named
10111041
{
10121042
private static readonly SearchValues<char> s_separators = SearchValues.Create(' ', '@');
10131043

10141044
private Package() { }
10151045

1016-
public required string Name { get; init; }
10171046
public string? Version { get; init; }
10181047

10191048
public static new Package? Parse(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText)
@@ -1033,6 +1062,33 @@ private Package() { }
10331062
}
10341063
}
10351064

1065+
/// <summary>
1066+
/// Used for deduplication - compares directives by their type and name (ignoring case).
1067+
/// </summary>
1068+
internal sealed class NamedDirectiveComparer : IEqualityComparer<CSharpDirective.Named>
1069+
{
1070+
public static readonly NamedDirectiveComparer Instance = new();
1071+
1072+
private NamedDirectiveComparer() { }
1073+
1074+
public bool Equals(CSharpDirective.Named? x, CSharpDirective.Named? y)
1075+
{
1076+
if (ReferenceEquals(x, y)) return true;
1077+
1078+
if (x is null || y is null) return false;
1079+
1080+
return x.GetType() == y.GetType() &&
1081+
string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
1082+
}
1083+
1084+
public int GetHashCode(CSharpDirective.Named obj)
1085+
{
1086+
return HashCode.Combine(
1087+
obj.GetType().GetHashCode(),
1088+
obj.Name.GetHashCode(StringComparison.OrdinalIgnoreCase));
1089+
}
1090+
}
1091+
10361092
internal sealed class SimpleDiagnostic
10371093
{
10381094
public required Position Location { get; init; }

src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)