diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 9ebe527..e1c6038 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -19,6 +19,10 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
- dotnet-version: 6.0.x
+ dotnet-version: |
+ 6.0.x
+ 10.0.x
- name: Run CI
run: dotnet fsi src/build.fsx
+ - name: Test NativeAOT package consumer
+ run: dotnet fsi src/build.fsx -- -p NativeAOT
diff --git a/.gitignore b/.gitignore
index a394bdb..29a3321 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,6 +43,8 @@ nupkg
_UpgradeReport_Files/
[Pp]ackages/
+.vscode/
+
Thumbs.db
Desktop.ini
.DS_Store
diff --git a/src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.TrimmerRoots.xml b/src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.TrimmerRoots.xml
new file mode 100644
index 0000000..c72fc92
--- /dev/null
+++ b/src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.TrimmerRoots.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.fsproj b/src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.fsproj
index 7f60725..55fe1c7 100644
--- a/src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.fsproj
+++ b/src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.fsproj
@@ -19,6 +19,11 @@
+
+
+
+
diff --git a/src/TestConsole/TestConsole.fsproj b/src/TestConsole/TestConsole.fsproj
index e6b7ef0..141fe43 100644
--- a/src/TestConsole/TestConsole.fsproj
+++ b/src/TestConsole/TestConsole.fsproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net6.0;net10.0
diff --git a/src/TestNativeAot/Program.fs b/src/TestNativeAot/Program.fs
new file mode 100644
index 0000000..0651a3d
--- /dev/null
+++ b/src/TestNativeAot/Program.fs
@@ -0,0 +1,18 @@
+open System
+open System.Runtime.CompilerServices
+open FSharp.SystemCommandLine
+open Input
+
+[]
+let main argv =
+ if RuntimeFeature.IsDynamicCodeSupported then
+ failwith "This smoke test must run as a NativeAOT executable."
+
+ rootCommand argv {
+ description "NativeAOT startup and scalar/array parsing regression test"
+ inputs (argument "words" |> arity ZeroOrMore, option "--count")
+ setAction (fun (words, count) ->
+ // Avoid printf's own reflection requirements so this tests the library's input path.
+ Console.WriteLine("words=" + String.Join(",", words) + ";count=" + count.ToString())
+ )
+ }
diff --git a/src/TestNativeAot/TestNativeAot.fsproj b/src/TestNativeAot/TestNativeAot.fsproj
new file mode 100644
index 0000000..0d8375b
--- /dev/null
+++ b/src/TestNativeAot/TestNativeAot.fsproj
@@ -0,0 +1,15 @@
+
+
+ Exe
+ net10.0
+ true
+ false
+ $(MSBuildThisFileDirectory)obj/packages
+
+
+
+
+
+
+
+
diff --git a/src/TestNativeAot/global.json b/src/TestNativeAot/global.json
new file mode 100644
index 0000000..d46d21e
--- /dev/null
+++ b/src/TestNativeAot/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "latestFeature",
+ "allowPrerelease": false
+ }
+}
diff --git a/src/build.fsx b/src/build.fsx
index 460f9f5..c6b4f24 100644
--- a/src/build.fsx
+++ b/src/build.fsx
@@ -1,6 +1,10 @@
#r "nuget: Fun.Build, 1.1.17"
open Fun.Build
+open System
+open System.Diagnostics
+open System.IO
+open System.Runtime.InteropServices
let src = __SOURCE_DIRECTORY__
let fsproj = $"{src}/FSharp.SystemCommandLine/FSharp.SystemCommandLine.fsproj"
@@ -19,6 +23,57 @@ pipeline "Build" {
runIfOnlySpecified false
}
+// Use a unique package version so repeated local runs cannot reuse a cached build.
+let smokeDir = Path.Combine(src, "TestNativeAot")
+let smokePackages = Path.Combine(smokeDir, "obj", "packages")
+let smokeOutput = Path.Combine(smokeDir, "bin", "native-aot")
+let smokeVersion = "0.0.0-aot-" + Guid.NewGuid().ToString("N")
+let smokeExecutable =
+ Path.Combine(smokeOutput, if OperatingSystem.IsWindows() then "TestNativeAot.exe" else "TestNativeAot")
+
+let checkNativeAot args expectedExitCode expectedOutput =
+ let startInfo = ProcessStartInfo(smokeExecutable)
+ startInfo.UseShellExecute <- false
+ startInfo.RedirectStandardOutput <- true
+ startInfo.RedirectStandardError <- true
+ for arg in args do startInfo.ArgumentList.Add(arg)
+ use child = Process.Start(startInfo)
+ let stdout = child.StandardOutput.ReadToEndAsync()
+ let stderr = child.StandardError.ReadToEndAsync()
+ if not (child.WaitForExit(30_000)) then
+ child.Kill(true)
+ failwithf "NativeAOT smoke test timed out: %A" args
+ let output = stdout.GetAwaiter().GetResult()
+ let errors = stderr.GetAwaiter().GetResult()
+ if child.ExitCode <> expectedExitCode || not (output.Contains(expectedOutput: string)) then
+ failwithf "NativeAOT smoke test %A: expected exit %d and output containing %A; got exit %d.\n%s\n%s"
+ args expectedExitCode expectedOutput child.ExitCode output errors
+ printfn "NativeAOT smoke test passed: %A" args
+
+pipeline "NativeAOT" {
+ // The consumer's global.json selects .NET 10 for both pack and publish.
+ workingDir smokeDir
+
+ stage "Pack library for NativeAOT" {
+ run $"dotnet pack \"{fsproj}\" --configuration Release --output \"{smokePackages}\" -p:PackageVersion={smokeVersion}"
+ }
+
+ stage "Publish NativeAOT package consumer" {
+ run $"dotnet publish TestNativeAot.fsproj --configuration Release --runtime {RuntimeInformation.RuntimeIdentifier} --output \"{smokeOutput}\" -p:SmokePackageVersion={smokeVersion}"
+ }
+
+ stage "Run NativeAOT smoke tests" {
+ run (fun _ ->
+ checkNativeAot [ "--help" ] 0 "--count"
+ checkNativeAot [ "hello"; "world"; "--count"; "2" ] 0 "words=hello,world;count=2"
+ checkNativeAot [] 0 "words=;count=0"
+ checkNativeAot [ "--count"; "invalid" ] 1 ""
+ )
+ }
+
+ runIfOnlySpecified true
+}
+
open System.Xml.Linq
pipeline "Publish" {
@@ -39,4 +94,4 @@ pipeline "Publish" {
runIfOnlySpecified true
}
-tryPrintPipelineCommandHelp ()
\ No newline at end of file
+tryPrintPipelineCommandHelp ()