Skip to content

Commit 0c1d73f

Browse files
committed
Consistently use -- to separate go args
This avoids inconsistent results when using with `dnx go` which might parse some of the arguments. By always passing them after --, we get consistent results. This simplifies explaining the subsequent -- to split dotnet args from app args (if present).
1 parent e8b3410 commit 0c1d73f

4 files changed

Lines changed: 56 additions & 59 deletions

File tree

readme.md

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,11 @@ making it optimal for quick iteration and agentic tools authoring and consumptio
3030
dnx go app.cs
3131

3232
# Pass arguments to your app
33-
dnx go app.cs arg1 arg2
34-
35-
# Also works
3633
dnx go app.cs -- arg1 arg2
3734

3835
# Pass arguments to the underlying `dotnet publish`
39-
# First -- is for dotnet publish, second -- is for your app
40-
dnx go app.cs -- /p:MyProp=true -- arg1 arg2
36+
# Args after initial -- are for dotnet publish, after second -- are for your app
37+
dnx go app.cs -- /p:MyProp=true /v:quiet -- arg1 arg2
4138
```
4239

4340
The default mode publishes the app with native AOT and then runs the resulting executable,
@@ -48,13 +45,7 @@ Use `--r2r` when your app needs more dynamic .NET features (reflection, dynamic
4845
that native AOT does not support, while still keeping most publish optimizations:
4946

5047
```console
51-
dnx go app.cs --r2r
52-
53-
# Pass arguments to your app
54-
dnx go app.cs --r2r arg1 arg2
55-
56-
# Also works
57-
dnx go app.cs --r2r -- arg1 arg2
48+
dnx go app.cs -- --r2r -- arg1 arg2
5849
```
5950

6051
This publishes with `/p:PublishAot=false` and `/p:PublishReadyToRun=true`.
@@ -65,13 +56,13 @@ and runs the app directly from the build output without the optimizations
6556
applied by dotnet to published executables (i.e. AOT, RID-specific optimizations):
6657

6758
```console
68-
dnx go dev app.cs
59+
dnx go dev -- app.cs
6960

7061
# Pass arguments to your app
71-
dnx go dev app.cs -- arg1 arg2
62+
dnx go dev -- app.cs arg1 arg2
7263

7364
# Pass arguments to the underlying `dotnet run`
74-
dnx go dev app.cs /p:Configuration=Release -- arg1 arg2
65+
dnx go dev -- app.cs /p:Configuration=Release -- arg1 arg2
7566
```
7667

7768
## Remote references
@@ -81,15 +72,15 @@ the content (when needed) and treat the resulting local file as the entry point:
8172

8273
```console
8374
# Run from a public repo (defaults to github.com, main + program.cs or first .cs)
84-
dnx go kzu/sandbox
75+
dnx go -- kzu/sandbox
8576

8677
# Specific branch/tag and file
87-
dnx go kzu/sandbox@v1.2.3:src/hello.cs
78+
dnx go -- kzu/sandbox@v1.2.3:src/hello.cs
8879

8980
# Full host (GitHub, Gist, GitLab, Azure DevOps)
90-
dnx go github.com/kzu/sandbox@main:hello.cs
91-
dnx go gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381
92-
dnx go gitlab.com/kzu/runcs/-/blob/main/program.cs
81+
dnx go -- github.com/kzu/sandbox@main:hello.cs
82+
dnx go -- gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381
83+
dnx go -- gitlab.com/kzu/runcs/-/blob/main/program.cs
9384
```
9485

9586
The first argument is resolved by first checking if it is a local file (`File.Exists`).
@@ -104,10 +95,10 @@ To force a fresh download for a remote ref, clean its bundle first:
10495

10596
```console
10697
# Clean the downloaded bundle for a remote ref (forces full download on next run)
107-
dnx go clean kzu/sandbox
98+
dnx go -- clean kzu/sandbox
10899

109100
# Works for refs with @ref or :path too (the bundle for the ref is deleted entirely)
110-
dnx go clean kzu/sandbox@main:program.cs
101+
dnx go -- clean kzu/sandbox@main:program.cs
111102
```
112103

113104
Behavior follows the chosen command:
@@ -124,13 +115,13 @@ user's temp area, which is what makes unchanged re-runs near-instant.
124115

125116
```console
126117
# Delete the cached artifacts for a single app (next run rebuilds)
127-
dnx go clean app.cs
118+
dnx go -- clean app.cs
128119

129120
# Delete the downloaded bundle for a remote ref (next run re-downloads; :path ignored)
130-
dnx go clean owner/repo[@ref][:path]
121+
dnx go -- clean owner/repo[@ref][:path]
131122

132123
# Delete the cached artifacts for all apps
133-
dnx go clean --all
124+
dnx go -- clean --all
134125
```
135126

136127
Unused download locations and published binaries are periodically cleaned up
@@ -140,8 +131,8 @@ in a detached background process. Apps you run regularly are never affected.
140131

141132
The main advantage of `go#` is **fast unchanged re-runs**.
142133
The two core scenarios for `go#` file-based apps are:
143-
* While tweaking 👉 `dnx go dev app.cs` (optimized `dotnet run app.cs`)
144-
* When stable 👉 `dnx go app.cs` (optimized `dotnet publish app.cs; app[.exe]`)
134+
* While tweaking 👉 `dnx go -- dev app.cs` (optimized `dotnet run app.cs`)
135+
* When stable 👉 `dnx go -- app.cs` (optimized `dotnet publish app.cs; app[.exe]`)
145136

146137
The numbers below showcase both scenarios, comparing `go#` to `dotnet run` and
147138
`dotnet publish` for a file-based app with different combinations of `#include` and `#ref` directives.

samples/minimal/app.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/usr/bin/env dotnet
22
#:property TargetFramework=net10.0
33

4-
Console.WriteLine("hello from go");
4+
Console.WriteLine("hello from go: " + string.Join("|", args));

src/go/Program.cs

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using ConsoleAppFramework;
33
using Devlooped;
44

5+
if (Environment.GetEnvironmentVariable("GO_DEBUG") == "1")
6+
System.Diagnostics.Debugger.Launch();
7+
58
var app = ConsoleApp.Create();
69
app.Add("", RunAsync);
710
app.Add("dev", DevAsync);
@@ -13,7 +16,8 @@
1316
/// <param name="input">Path to an existing .cs file or remote ref (owner/repo[@ref][:path]).</param>
1417
/// <param name="r2r">Publish with ReadyToRun instead of native AOT; supports more dynamic .NET features while keeping most publish optimizations. </param>
1518
/// <param name="gdbg">Launch debugger before executing.</param>
16-
static async Task<int> RunAsync([Argument] string input, bool r2r = false, [Hidden] bool gdbg = false)
19+
/// <param name="args">Arguments to pass to the app, or to dotnet publish and the app, separated by --.</param>
20+
static async Task<int> RunAsync([Argument] string input, bool r2r = false, [Hidden] bool gdbg = false, [Argument] params string[] args)
1721
{
1822
if (gdbg)
1923
System.Diagnostics.Debugger.Launch();
@@ -48,6 +52,36 @@ state.App is not null &&
4852
return await ExecuteAppAsync(publishDir, () => ProcessRunner.RunAsync(state.App, appArgs));
4953
}
5054

55+
/// <summary>Runs a file-based .NET app from a .cs entrypoint using dotnet run for fast iteration.</summary>
56+
/// <param name="input">Path to an existing .cs file or remote ref (owner/repo[@ref][:path]).</param>
57+
/// <param name="r2r">Accepted for consistency (ignored for dev which uses dotnet run).</param>
58+
/// <param name="gdbg">Launch debugger before executing.</param>
59+
/// <param name="args">Arguments to pass to the app, or to dotnet run and the app, separated by --.</param>
60+
static async Task<int> DevAsync([Argument] string input, [Hidden] bool r2r = false, [Hidden] bool gdbg = false, [Argument] params string[] args)
61+
{
62+
if (gdbg)
63+
System.Diagnostics.Debugger.Launch();
64+
65+
var source = await GetEffectiveSourceAsync(input);
66+
if (source is null)
67+
return 1;
68+
69+
var context = Prepare(source, GoArgs.ForwardArgs);
70+
if (context is null)
71+
return 1;
72+
73+
var (dotnet, cs, publishDir, stamp, targets, _, dotnetArgs, appArgs) = context.Value;
74+
75+
if (BuildState.TryRead(stamp, out var state) &&
76+
state.Bin is not null &&
77+
BuildManager.IsUpToDate(state, state.Bin))
78+
return await ExecuteAppAsync(publishDir, () => ProcessRunner.DotnetExecAsync(dotnet, state.Bin, appArgs));
79+
80+
File.WriteAllText(stamp, string.Empty, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
81+
82+
return await ExecuteAppAsync(publishDir, () => ProcessRunner.DotnetRunAsync(dotnet, cs, stamp, targets, dotnetArgs, appArgs));
83+
}
84+
5185
/// <summary>Deletes cached publish artifacts for a file-based .NET app, or for a remote ref.</summary>
5286
/// <param name="input">Path to an existing .cs file or remote ref (owner/repo[@ref][:path]).</param>
5387
/// <param name="all">Delete cached artifacts for all apps instead.</param>
@@ -138,35 +172,6 @@ static int CleanAsync([Argument] string? input = null, bool all = false, [Hidden
138172
return AppCleaner.Clean(pdir, stmp, cs);
139173
}
140174

141-
/// <summary>Runs a file-based .NET app from a .cs entrypoint using dotnet run for fast iteration.</summary>
142-
/// <param name="input">Path to an existing .cs file or remote ref (owner/repo[@ref][:path]).</param>
143-
/// <param name="r2r">Accepted for consistency (ignored for dev which uses dotnet run).</param>
144-
/// <param name="gdbg">Launch debugger before executing.</param>
145-
static async Task<int> DevAsync([Argument] string input, [Hidden] bool r2r = false, [Hidden] bool gdbg = false)
146-
{
147-
if (gdbg)
148-
System.Diagnostics.Debugger.Launch();
149-
150-
var source = await GetEffectiveSourceAsync(input);
151-
if (source is null)
152-
return 1;
153-
154-
var context = Prepare(source, GoArgs.ForwardArgs);
155-
if (context is null)
156-
return 1;
157-
158-
var (dotnet, cs, publishDir, stamp, targets, _, dotnetArgs, appArgs) = context.Value;
159-
160-
if (BuildState.TryRead(stamp, out var state) &&
161-
state.Bin is not null &&
162-
BuildManager.IsUpToDate(state, state.Bin))
163-
return await ExecuteAppAsync(publishDir, () => ProcessRunner.DotnetExecAsync(dotnet, state.Bin, appArgs));
164-
165-
File.WriteAllText(stamp, string.Empty, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
166-
167-
return await ExecuteAppAsync(publishDir, () => ProcessRunner.DotnetRunAsync(dotnet, cs, stamp, targets, dotnetArgs, appArgs));
168-
}
169-
170175
static async Task<int> ExecuteAppAsync(string publishDir, Func<Task<int>> execute)
171176
{
172177
Directory.Touch(publishDir);

src/go/help.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Usage: [command] [arguments...] [options...] [-h|--help] [--version]
44
Runs a file-based .NET app from a .cs entrypoint.
55

66
Arguments:
7-
[0] <string> Path to an existing .cs file or remote ref (owner/repo[@ref][:path]).
7+
[0] <string> Path to an existing .cs file or remote ref (owner/repo[@ref][:path]).
8+
[1] <string[]> Arguments to pass to the app, or to dotnet publish and the app, separated by --.
89

910
Options:
1011
--r2r Publish with ReadyToRun instead of native AOT; supports more dynamic .NET features while keeping most publish optimizations.

0 commit comments

Comments
 (0)