Skip to content

Commit bc422c5

Browse files
committed
Add 'dev' command for fast iteration using dotnet run
1 parent 4d4911c commit bc422c5

9 files changed

Lines changed: 210 additions & 78 deletions

File tree

src/Tests/GoArgsTests.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,36 @@ public class GoArgsTests
77
[Fact]
88
public void Split_without_separator_forwards_all_args_to_app()
99
{
10-
var (publish, app) = GoArgs.Split(["--foo", "bar"]);
10+
var (dotnet, app) = GoArgs.Split(["--foo", "bar"]);
1111

12-
Assert.Empty(publish);
12+
Assert.Empty(dotnet);
1313
Assert.Equal(["--foo", "bar"], app);
1414
}
1515

1616
[Fact]
17-
public void Split_with_separator_routes_publish_and_app_args()
17+
public void Split_with_separator_routes_dotnet_and_app_args()
1818
{
19-
var (publish, app) = GoArgs.Split(["-c", "Release", "--", "arg1", "arg2"]);
19+
var (dotnet, app) = GoArgs.Split(["-c", "Release", "--", "arg1", "arg2"]);
2020

21-
Assert.Equal(["-c", "Release"], publish);
21+
Assert.Equal(["-c", "Release"], dotnet);
2222
Assert.Equal(["arg1", "arg2"], app);
2323
}
2424

2525
[Fact]
2626
public void Split_with_trailing_separator_leaves_app_args_empty()
2727
{
28-
var (publish, app) = GoArgs.Split(["-c", "Release", "--"]);
28+
var (dotnet, app) = GoArgs.Split(["-c", "Release", "--"]);
2929

30-
Assert.Equal(["-c", "Release"], publish);
30+
Assert.Equal(["-c", "Release"], dotnet);
3131
Assert.Empty(app);
3232
}
3333

3434
[Fact]
35-
public void Split_with_leading_separator_leaves_publish_args_empty()
35+
public void Split_with_leading_separator_leaves_dotnet_args_empty()
3636
{
37-
var (publish, app) = GoArgs.Split(["--", "arg1"]);
37+
var (dotnet, app) = GoArgs.Split(["--", "arg1"]);
3838

39-
Assert.Empty(publish);
39+
Assert.Empty(dotnet);
4040
Assert.Equal(["arg1"], app);
4141
}
4242
}

src/Tests/GoBuildCacheTests.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Devlooped;
1+
using Devlooped;
22

33
namespace Tests;
44

@@ -13,9 +13,9 @@ public void IsUpToDate_returns_true_when_app_is_newer_than_all_inputs()
1313
File.SetLastWriteTimeUtc(input, DateTime.UtcNow.AddMinutes(-5));
1414
File.SetLastWriteTimeUtc(app, DateTime.UtcNow);
1515

16-
var state = new BuildState(app, [input]);
16+
var state = new BuildState(app, null, [input]);
1717

18-
Assert.True(BuildManager.IsUpToDate(state));
18+
Assert.True(BuildManager.IsUpToDate(state, app));
1919
}
2020

2121
[Fact]
@@ -27,19 +27,33 @@ public void IsUpToDate_returns_false_when_any_input_is_newer_than_app()
2727
File.SetLastWriteTimeUtc(app, DateTime.UtcNow.AddMinutes(-5));
2828
File.SetLastWriteTimeUtc(input, DateTime.UtcNow);
2929

30-
var state = new BuildState(app, [input]);
30+
var state = new BuildState(app, null, [input]);
3131

32-
Assert.False(BuildManager.IsUpToDate(state));
32+
Assert.False(BuildManager.IsUpToDate(state, app));
3333
}
3434

3535
[Fact]
3636
public void IsUpToDate_returns_false_when_app_is_missing()
3737
{
3838
var dir = CreateTempDir();
3939
var input = WriteFile(dir, "input.cs", "input");
40-
var state = new BuildState(Path.Combine(dir, "missing.exe"), [input]);
40+
var state = new BuildState(Path.Combine(dir, "missing.exe"), null, [input]);
4141

42-
Assert.False(BuildManager.IsUpToDate(state));
42+
Assert.False(BuildManager.IsUpToDate(state, state.App!));
43+
}
44+
45+
[Fact]
46+
public void IsUpToDate_works_with_bin_artifact()
47+
{
48+
var dir = CreateTempDir();
49+
var input = WriteFile(dir, "input.cs", "input");
50+
var bin = WriteFile(dir, "app.dll", "bin");
51+
File.SetLastWriteTimeUtc(input, DateTime.UtcNow.AddMinutes(-5));
52+
File.SetLastWriteTimeUtc(bin, DateTime.UtcNow);
53+
54+
var state = new BuildState(null, bin, [input]);
55+
56+
Assert.True(BuildManager.IsUpToDate(state, bin));
4357
}
4458

4559
static string CreateTempDir()

src/Tests/GoConfigReaderTests.cs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Text;
1+
using System.Text;
22
using Devlooped;
33

44
namespace Tests;
@@ -24,7 +24,8 @@ public void TryRead_parses_multiple_inputs_and_app()
2424

2525
Assert.True(success);
2626
Assert.NotNull(state);
27-
Assert.Equal(Path.GetFullPath(app), Path.GetFullPath(state.App));
27+
Assert.Equal(Path.GetFullPath(app), Path.GetFullPath(state.App!));
28+
Assert.Null(state.Bin);
2829
Assert.Equal(2, state.Inputs.Count);
2930
Assert.Contains(state.Inputs, p => Path.GetFullPath(p) == Path.GetFullPath(inputA));
3031
Assert.Contains(state.Inputs, p => Path.GetFullPath(p) == Path.GetFullPath(inputB));
@@ -48,7 +49,7 @@ public void TryRead_parses_stamp_written_with_utf8_bom()
4849

4950
Assert.True(success);
5051
Assert.NotNull(state);
51-
Assert.Equal(Path.GetFullPath(app), Path.GetFullPath(state!.App));
52+
Assert.Equal(Path.GetFullPath(app), Path.GetFullPath(state!.App!));
5253
}
5354

5455
[Fact]
@@ -68,16 +69,59 @@ public void TryRead_parses_windows_paths_written_with_forward_slashes()
6869

6970
Assert.True(success);
7071
Assert.NotNull(state);
71-
Assert.Equal(Path.GetFullPath(app), Path.GetFullPath(state!.App));
72+
Assert.Equal(Path.GetFullPath(app), Path.GetFullPath(state!.App!));
7273
}
7374

7475
[Fact]
75-
public void TryRead_returns_false_when_app_is_missing()
76+
public void TryRead_parses_bin_and_uses_last_bin_when_duplicated()
77+
{
78+
var dir = CreateTempDir();
79+
var input = WriteFile(dir, "app.cs", "app");
80+
var binOld = WriteFile(dir, "old.dll", "old");
81+
var binNew = WriteFile(dir, "new.dll", "new");
82+
var stampPath = Path.Combine(dir, "app.stamp");
83+
84+
File.WriteAllText(stampPath, $"""
85+
input = {ToStampPath(input)}
86+
bin = {ToStampPath(binOld)}
87+
bin = {ToStampPath(binNew)}
88+
""");
89+
90+
var success = BuildState.TryRead(stampPath, out var state);
91+
92+
Assert.True(success);
93+
Assert.NotNull(state);
94+
Assert.Equal(Path.GetFullPath(binNew), Path.GetFullPath(state!.Bin!));
95+
}
96+
97+
[Fact]
98+
public void TryRead_succeeds_with_bin_only_no_app()
99+
{
100+
var dir = CreateTempDir();
101+
var input = WriteFile(dir, "app.cs", "app");
102+
var bin = WriteFile(dir, "app.dll", "bin");
103+
var stampPath = Path.Combine(dir, "app.stamp");
104+
105+
File.WriteAllText(stampPath, $"""
106+
input = {ToStampPath(input)}
107+
bin = {ToStampPath(bin)}
108+
""");
109+
110+
var success = BuildState.TryRead(stampPath, out var state);
111+
112+
Assert.True(success);
113+
Assert.NotNull(state);
114+
Assert.Null(state.App);
115+
Assert.Equal(Path.GetFullPath(bin), Path.GetFullPath(state.Bin!));
116+
}
117+
118+
[Fact]
119+
public void TryRead_returns_false_when_no_inputs()
76120
{
77121
var dir = CreateTempDir();
78122
var stampPath = Path.Combine(dir, "app.stamp");
79123
File.WriteAllText(stampPath, """
80-
input = app.cs
124+
app = app.exe
81125
""");
82126

83127
var success = BuildState.TryRead(stampPath, out var state);

src/go/BuildManager.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.Diagnostics.CodeAnalysis;
22

33
namespace Devlooped;
44

5-
public record BuildState(string App, IReadOnlyList<string> Inputs)
5+
public record BuildState(string? App, string? Bin, IReadOnlyList<string> Inputs)
66
{
77
public static bool TryRead(string path, [NotNullWhen(true)] out BuildState? state)
88
{
@@ -11,6 +11,7 @@ public static bool TryRead(string path, [NotNullWhen(true)] out BuildState? stat
1111
return false;
1212

1313
string? app = null;
14+
string? bin = null;
1415
var inputs = new List<string>();
1516

1617
foreach (var line in File.ReadLines(path))
@@ -28,38 +29,40 @@ public static bool TryRead(string path, [NotNullWhen(true)] out BuildState? stat
2829

2930
if (key == "app")
3031
app = value;
32+
else if (key == "bin")
33+
bin = value;
3134
else if (key == "input")
3235
inputs.Add(value);
3336
}
3437

35-
if (app is null || !File.Exists(app))
38+
if (inputs.Count == 0)
3639
return false;
3740

38-
state = new BuildState(app, inputs);
41+
state = new BuildState(app, bin, inputs);
3942
return true;
4043
}
4144
}
4245

4346
public static class BuildManager
4447
{
45-
public static bool IsUpToDate(BuildState state)
48+
public static bool IsUpToDate(BuildState state, string artifact)
4649
{
47-
if (string.IsNullOrWhiteSpace(state.App) || !File.Exists(state.App))
50+
if (string.IsNullOrWhiteSpace(artifact) || !File.Exists(artifact))
4851
return false;
4952

5053
if (state.Inputs.Count == 0)
5154
return false;
5255

53-
var appTime = File.GetLastWriteTimeUtc(state.App);
56+
var artifactTime = File.GetLastWriteTimeUtc(artifact);
5457
foreach (var input in state.Inputs)
5558
{
5659
if (!File.Exists(input))
5760
return false;
5861

59-
if (File.GetLastWriteTimeUtc(input) > appTime)
62+
if (File.GetLastWriteTimeUtc(input) > artifactTime)
6063
return false;
6164
}
6265

6366
return true;
6467
}
65-
}
68+
}

src/go/GoArgs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace Devlooped;
22

33
public static class GoArgs
44
{
5-
public static (string[] Publish, string[] App) Split(string[] extraArgs)
5+
public static (string[] Dotnet, string[] App) Split(string[] extraArgs)
66
{
77
var separator = Array.IndexOf(extraArgs, "--");
88
if (separator < 0)

src/go/ProcessRunner.cs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,46 @@
1-
using System.Diagnostics;
1+
using System.Diagnostics;
22

33
namespace Devlooped;
44

55
public static class ProcessRunner
66
{
7-
public static Task<int> PublishAsync(string dotnet, string cs, string config, string targets, IReadOnlyList<string>? publishArgs = null)
7+
public static Task<int> PublishAsync(string dotnet, string cs, string config, string targets, IReadOnlyList<string>? dotnetArgs = null)
88
{
9-
var environment = new Dictionary<string, string>
10-
{
11-
["GoConfig"] = config,
12-
["CustomAfterMicrosoftCSharpTargets"] = targets,
13-
};
9+
var environment = CreateGoEnvironment(config, targets);
1410

1511
var arguments = new List<string> { "publish", "--ucr", cs };
16-
if (publishArgs is not null)
17-
arguments.AddRange(publishArgs);
12+
if (dotnetArgs is not null)
13+
arguments.AddRange(dotnetArgs);
1814

1915
return RunAsync(dotnet, arguments, environment);
2016
}
2117

18+
public static Task<int> DotnetRunAsync(string dotnet, string cs, string config, string targets, IReadOnlyList<string>? dotnetArgs, IReadOnlyList<string>? appArgs)
19+
{
20+
var environment = CreateGoEnvironment(config, targets);
21+
22+
var arguments = new List<string> { "run", "--ucr", cs };
23+
if (dotnetArgs is not null)
24+
arguments.AddRange(dotnetArgs);
25+
26+
if (appArgs is { Count: > 0 })
27+
{
28+
arguments.Add("--");
29+
arguments.AddRange(appArgs);
30+
}
31+
32+
return RunAsync(dotnet, arguments, environment);
33+
}
34+
35+
public static Task<int> DotnetExecAsync(string dotnet, string assembly, IReadOnlyList<string>? appArgs)
36+
{
37+
var arguments = new List<string> { assembly };
38+
if (appArgs is not null)
39+
arguments.AddRange(appArgs);
40+
41+
return RunAsync(dotnet, arguments, environment: null);
42+
}
43+
2244
public static Task<int> RunAsync(string appPath, IReadOnlyList<string>? extraArgs = null)
2345
=> RunAsync(appPath, extraArgs, environment: null);
2446

@@ -49,4 +71,10 @@ public static async Task<int> RunAsync(string fileName, IReadOnlyList<string>? a
4971

5072
return process.ExitCode;
5173
}
74+
75+
static Dictionary<string, string> CreateGoEnvironment(string config, string targets) => new()
76+
{
77+
["GoConfig"] = config,
78+
["CustomAfterMicrosoftCSharpTargets"] = targets,
79+
};
5280
}

0 commit comments

Comments
 (0)