You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: documentation/general/dotnet-run-file.md
+49-43Lines changed: 49 additions & 43 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -43,7 +43,7 @@ In fact, this command simply materializes the [implicit project file](#implicit-
43
43
This action should not change the behavior of the target program.
44
44
45
45
```ps1
46
-
dotnet project add
46
+
dotnet project convert
47
47
```
48
48
49
49
## Target path
@@ -92,10 +92,10 @@ For example, the remaining command-line arguments after the first argument (the
92
92
If a file is given to `dotnet run`, it has to be an *entry-point file*, otherwise an error is reported.
93
93
We want to report an error for non-entry-point files to avoid the confusion of being able to `dotnet run util.cs`.
94
94
95
-
Currently, entry-point files must contain top-level statements,
96
-
but other entry-point forms like classic `Main` method can be recognized in the future.
97
-
We could modify Roslyn to accept the entry-point path and then it would be the compiler's responsibility
98
-
to check whether the file contains an entry point (of any kind) and report an error otherwise.
95
+
We modify Roslyn to accept the entry-point path and then it is its responsibility
96
+
to check whether the file contains an entrypoint (top-level statements or a valid Main method) and report an error otherwise.
97
+
(We cannot simply use Roslyn APIs to detect entry points ourselves because parsing depends on conditional symbols like those from `<DefineConstants>`
98
+
and we can reliably know the set of those only after invoking MSBuild, and doing that up front would be an unnecessary performance hit just to detect entry points.)
99
99
100
100
Because of the [implicit project file](#implicit-project-file),
101
101
other files in the target directory or its subdirectories are included in the compilation.
@@ -129,6 +129,10 @@ Again, this problem exists with project-based programs as well.
129
129
Note that having a project-based or file-based program in the drive root would result in
For `.csproj` files inside the target directory and its parent directories, we do not report any errors/warnings.
133
+
That's because it might be perfectly reasonable to have file-based programs nested in another project-based program
134
+
(most likely excluded from that project's compilation via something like `<Compile Exclude="./my-scripts/**" />`).
135
+
132
136
### Multiple entry points
133
137
134
138
If there are multiple entry-point files in the target directory, the target path must be a file
@@ -172,56 +176,58 @@ App/Program2/bin/
172
176
App/Program2/obj/
173
177
```
174
178
175
-
## Package references
179
+
## Directives for project metadata
176
180
177
-
It is possible to specify NuGet package references via the `#package` directive.
181
+
It is possible to specify some project metadata via [ignored C# directives][ignored-directives].
182
+
Directives `sdk`, `package`, and `property` are translated into `<Project Sdk="...">`, `<PackageReference>`, and `<Property>` project elements, respectively.
183
+
Other directives result in a warning, reserving them for future use.
178
184
179
185
```cs
180
-
#package Newtonsoft.Json 13.0.1
186
+
#:sdk Microsoft.NET.Sdk.Web
187
+
#:property TargetFramework=net11.0
188
+
#:property LangVersion=preview
189
+
#:package System.CommandLine=2.0.0-*
181
190
```
182
191
183
-
The C# language needs to be updated to ignore these directives (instead of failing the compilation).
184
-
See [the corresponding language proposal][pound].
185
-
186
-
If these directives were limited by the language to only appear near the top of the file (similar to `#define` directives),
187
-
the dotnet CLI could be more efficient in searching for them.
188
-
189
-
It should be also possible to look for these directives from the dotnet CLI via a regex instead of parsing the whole C# file via Roslyn.
190
-
191
-
We do not limit `#package` directives to appear only in entry point files.
192
-
Indeed, it might be beneficial to let a non-entry-point file like `Util.cs` be self-contained and have all the `#package`s it needs specified in it,
192
+
The value must be separated from the name of the directive by white space and any leading and trailing white space is not considered part of the value.
193
+
Any value can optionally have two parts separated by `=` or `/`
194
+
(the former is consistent with how properties are usually passed, e.g., `/p:Prop=Value`, and the latter is what the `<Project Sdk="Name/Version">` attribute uses).
195
+
The value of the first `#:sdk` is injected into `<Project Sdk="{0}">` with the separator (if any) replaced with `/`,
196
+
and the subsequent `#:sdk` directive values are split by the separator and injected as `<Sdk Name="{0}" Version="{1}" />` elements (or without the `Version` attribute if there is no separator).
197
+
It is an error if the first part (name) is empty (the version is allowed to be empty, but that results in empty `Version=""`).
198
+
The value of `#:property` is split by the separator and injected as `<{0}>{1}</{0}>` in a `<PropertyGroup>`.
199
+
It is an error if no separator appears in the value or if the first part (property name) is empty (the property value is allowed to be empty) or contains invalid characters.
200
+
The value of `#:package` is split by the separator and injected as `<PackageReference Include="{0}" Version="{1}">` (or without the `Version` attribute if there is no separator) in an `<ItemGroup>`.
201
+
It is an error if the first part (package name) is empty (the package version is allowed to be empty, but that results in empty `Version=""`).
202
+
203
+
Because these directives are limited by the C# language to only appear before the first "C# token" and any `#if`,
204
+
dotnet CLI can look for them via a regex or Roslyn lexer without any knowledge of defined conditional symbols
205
+
and can do that efficiently by stopping the search when it sees the first "C# token".
206
+
207
+
We do not limit these directives to appear only in entry point files.
208
+
Indeed, it might be beneficial to let a non-entry-point file like `Util.cs` be self-contained and have all the `#:package`s it needs specified in it,
193
209
which also makes it possible to share it independently or symlink it to multiple script folders.
194
210
This is also similar to `global using`s which users usually put into a single file but don't have to.
195
211
196
-
We could consider deduplicating `#package` directives (if they have the same version)
197
-
so separate "self-contained" utilities can reference overlapping sets of packages
212
+
We could consider deduplicating `#:` directives
213
+
(e.g., properties could be concatenated via `;`, more specific package versions could override less specific ones),
214
+
so for example separate "self-contained" utilities could reference overlapping sets of packages
198
215
even if they end up in the same compilation.
199
-
But for starters we can simply translate every `#package`directive into `<PackageReference>`
216
+
But for starters we can translate each directive into the corresponding project element
200
217
and let the existing MSBuild/NuGet logic deal with duplicates.
201
218
202
-
It is valid to have a `#package` directive without a version.
219
+
It is valid to have a `#:package` directive without a version.
203
220
That's useful when central package management (CPM) is used.
204
221
NuGet will report an appropriate error if the version is missing and CPM is not enabled.
205
222
206
-
During [grow up](#grow-up), `#package` directives are removed from the `.cs` files and turned into `<PackageReference>` elements in the corresponding `.csproj` files.
207
-
For project-based programs, `#package` directives are an error (reported by Roslyn when it's told it is in "project-based" mode).
208
-
209
-
## SDK directive
210
-
211
-
We could also recognize `#sdk` directive to allow web file-based programs for example.
212
-
213
-
```cs
214
-
#sdk Microsoft.NET.Sdk.Web
215
-
```
216
-
217
-
It should have similar restrictions as the `#package` directive.
218
-
It should also be an error to specify multiple different `#sdk` directives
219
-
but it could be allowed to specify the same SDK multiple times similarly to `#package` directives
220
-
(again so that self-contained utility files can declare their required SDK).
223
+
During [grow up](#grow-up), `#:` directives are removed from the `.cs` files and turned into elements in the corresponding `.csproj` files.
224
+
For project-based programs, `#:` directives are an error (reported by Roslyn when it's told it is in "project-based" mode).
225
+
`#!` directives are also removed during grow up, although we could consider to have an option to preserve them
226
+
(since they might still be valid after grow up, depending on which program they are actually specifying to "interpret" the file, i.e., it might not be `dotnet run` at all).
221
227
222
228
## Shebang
223
229
224
-
Along with `#package`, the language can also ignore`#!` which could be then used for [shebang][shebang] support.
230
+
Along with `#:`, the language also ignores`#!` which could be then used for [shebang][shebang] support.
225
231
226
232
```cs
227
233
#!/usr/bin/dotnet run
@@ -264,7 +270,7 @@ We could also add `dotnet compile` command that would be the equivalent of `dotn
264
270
### `dotnet package add`
265
271
266
272
Adding package references via `dotnet package add` could be supported for file-based programs as well,
267
-
i.e., the command would add a `#package` directive to the top of a `.cs` file.
273
+
i.e., the command would add a `#:package` directive to the top of a `.cs` file.
268
274
269
275
## Implementation
270
276
@@ -274,8 +280,8 @@ The build is performed using MSBuild APIs on in-memory project files.
274
280
275
281
MSBuild invocation can be skipped in subsequent `dotnet run file.cs` invocations if an up-to-date check detects that inputs didn't change.
276
282
We always need to re-run MSBuild if implicit build files like `Directory.Build.props` change but
277
-
from `.cs` files, the only relevant MSBuild inputs are the `#package` directives,
278
-
hence we can first check the `.cs` file timestamps and for those that have changed, compare the sets of `#package` directives.
283
+
from `.cs` files, the only relevant MSBuild inputs are the `#:` directives,
284
+
hence we can first check the `.cs` file timestamps and for those that have changed, compare the sets of `#:` directives.
279
285
If only `.cs` files change, it is enough to invoke `csc.exe` (directly or via a build server)
280
286
re-using command-line arguments that the last MSBuild invocation passed to the compiler.
281
287
If no inputs change, it is enough to start the target executable without invoking the build at all.
@@ -289,7 +295,7 @@ The plan is to implement the feature in stages (the order might be different):
289
295
- Multiple entry points.
290
296
- Grow up command.
291
297
- Folder support: `dotnet run ./dir/`.
292
-
-Package references via `#package`.
298
+
-Project metadata via `#:` directives.
293
299
294
300
## Alternatives
295
301
@@ -306,5 +312,5 @@ Instead of implicitly including files from the target directory, the importing c
0 commit comments