From 225f43bfdca83b3e5650348c72a315961dff8368 Mon Sep 17 00:00:00 2001 From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com> Date: Tue, 13 Oct 2026 22:54:32 +1100 Subject: [PATCH] Native AOT migrate binary builds again; CI publishes it on every PR Microsoft.Data.SqlClient is not trim or Native AOT compatible, so the native DataProviderMigrate build leaves out the SQL Server provider and reports it as unavailable; the managed dotnet tool keeps it. The SQLite generator's config reads use a source-generated JSON context so the AOT analyzers accept the assembly the migrate binary now compiles for its SQLite parser. make aot no longer writes its smoke database to /tmpdataprovider_aot_smoke.db when TMPDIR is unset, and CI runs make aot plus the native smoke tests. --- .github/workflows/ci.yml | 15 +++++ .../SqliteCodeGenerator.cs | 17 ++---- .../SqliteGeneratorJsonContext.cs | 13 ++++ Makefile | 2 +- .../DataProviderMigrate.csproj | 6 ++ .../DataProviderMigrate/Program.SqlServer.cs | 61 +++++++++++++++++++ Migration/DataProviderMigrate/Program.cs | 42 ++----------- .../NativeAotMigrateSmokeTests.cs | 20 ++++++ docs/plans/dataprovider-migrate-aot.md | 11 ++++ 9 files changed, 137 insertions(+), 50 deletions(-) create mode 100644 DataProvider/Nimblesite.DataProvider.SQLite/SqliteGeneratorJsonContext.cs create mode 100644 Migration/DataProviderMigrate/Program.SqlServer.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ed033c2..79edd6e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,6 +103,21 @@ jobs: make _test_dotnet DOTNET_TEST_PROJECTS="DataProvider/Nimblesite.DataProvider.Tests DataProvider/Nimblesite.DataProvider.Example.Tests" + # Implements [MIG-AOT-CI]: the release ships DataProviderMigrate as a Native AOT + # binary, so every PR proves that publish and the native smoke tests still pass. + - name: Install Linux AOT toolchain + run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev + + - name: Native AOT publish + smoke migrate (DataProviderMigrate) + run: make aot RID=linux-x64 + + - name: Native AOT smoke tests (DataProviderMigrate) + env: + DATAPROVIDERMIGRATE_AOT_BIN: ${{ github.workspace }}/Migration/DataProviderMigrate/bin/Release/net9.0/linux-x64/publish/DataProviderMigrate + run: >- + dotnet test Migration/Nimblesite.DataProvider.Migration.Tests + --filter "FullyQualifiedName~NativeAotMigrateSmokeTests" + - name: Upload test results uses: actions/upload-artifact@v4 if: always() diff --git a/DataProvider/Nimblesite.DataProvider.SQLite/SqliteCodeGenerator.cs b/DataProvider/Nimblesite.DataProvider.SQLite/SqliteCodeGenerator.cs index 7df729ae..f4953e87 100644 --- a/DataProvider/Nimblesite.DataProvider.SQLite/SqliteCodeGenerator.cs +++ b/DataProvider/Nimblesite.DataProvider.SQLite/SqliteCodeGenerator.cs @@ -16,11 +16,6 @@ namespace Nimblesite.DataProvider.SQLite; [Generator] public sealed class SqliteCodeGenerator : IIncrementalGenerator { - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNameCaseInsensitive = true, - }; - /// /// Generates C# source for a SQL file using real database metadata. /// Delegates to @@ -260,9 +255,9 @@ ImmutableArray GroupingFiles { try { - config = JsonSerializer.Deserialize( - configContent!, - JsonOptions + config = JsonSerializer.Deserialize( + configContent, + SqliteGeneratorJsonContext.Default.SourceGeneratorDataProviderConfiguration ); } catch (JsonException ex) @@ -495,9 +490,9 @@ as Result, SqlError>.Error< { try { - groupingConfig = JsonSerializer.Deserialize( - groupingText!, - JsonOptions + groupingConfig = JsonSerializer.Deserialize( + groupingText, + SqliteGeneratorJsonContext.Default.GroupingConfig ); } catch (JsonException ex) diff --git a/DataProvider/Nimblesite.DataProvider.SQLite/SqliteGeneratorJsonContext.cs b/DataProvider/Nimblesite.DataProvider.SQLite/SqliteGeneratorJsonContext.cs new file mode 100644 index 00000000..f64bfbc2 --- /dev/null +++ b/DataProvider/Nimblesite.DataProvider.SQLite/SqliteGeneratorJsonContext.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Nimblesite.DataProvider.SQLite; + +/// +/// Source-generated JSON metadata for the SQLite generator's configuration files. +/// Implements [MIG-AOT-JSON]: the Native AOT migrate binary compiles this assembly +/// for its SQLite parser, so its JSON reads must be reflection-free. +/// +[JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)] +[JsonSerializable(typeof(SourceGeneratorDataProviderConfiguration))] +[JsonSerializable(typeof(GroupingConfig))] +internal sealed partial class SqliteGeneratorJsonContext : JsonSerializerContext { } diff --git a/Makefile b/Makefile index 529ca3b8..5c810c4e 100644 --- a/Makefile +++ b/Makefile @@ -304,7 +304,7 @@ _aot_dotnet: ifeq ($(OS),Windows_NT) AOT_SMOKE_DB := $(TEMP)\dataprovider_aot_smoke.db else -AOT_SMOKE_DB := $(shell printf '%s' "$${TMPDIR:-/tmp}")dataprovider_aot_smoke.db +AOT_SMOKE_DB := $(patsubst %/,%,$(shell printf '%s' "$${TMPDIR:-/tmp}"))/dataprovider_aot_smoke.db endif # --- RUST (LQL LSP) --- diff --git a/Migration/DataProviderMigrate/DataProviderMigrate.csproj b/Migration/DataProviderMigrate/DataProviderMigrate.csproj index f4c6c520..76638638 100644 --- a/Migration/DataProviderMigrate/DataProviderMigrate.csproj +++ b/Migration/DataProviderMigrate/DataProviderMigrate.csproj @@ -46,6 +46,12 @@ + + + + diff --git a/Migration/DataProviderMigrate/Program.SqlServer.cs b/Migration/DataProviderMigrate/Program.SqlServer.cs new file mode 100644 index 00000000..19cc5ce7 --- /dev/null +++ b/Migration/DataProviderMigrate/Program.SqlServer.cs @@ -0,0 +1,61 @@ +#if !AOT +using Microsoft.Data.SqlClient; +using Nimblesite.DataProvider.Migration.Core; +using Nimblesite.DataProvider.Migration.SqlServer; +#endif + +namespace DataProviderMigrate; + +// Implements [MIG-AOT-SQLSERVER]. Microsoft.Data.SqlClient is not trim or Native AOT +// compatible, so the native binary leaves out the SQL Server provider. SQL Server +// migrations remain available in the managed `dotnet tool` and the NuGet packages. +public static partial class Program +{ +#if AOT + private static int ShowSqlServerUnavailable() + { + Console.WriteLine( + "Error: the 'sqlserver' provider is not available in the native (AOT) build.\n" + + "Microsoft.Data.SqlClient does not support Native AOT.\n" + + "Use the managed dotnet tool 'DataProviderMigrate migrate --provider sqlserver ...' instead." + ); + return 1; + } +#else + // Implements [MIG-SQLSERVER]. + private static int MigrateSqlServerDatabase( + SchemaDefinition schema, + string connectionString, + bool allowDestructive, + MigratePhase phase + ) + { + try + { + using var connection = new SqlConnection(connectionString); + connection.Open(); + Console.WriteLine("Connected to SQL Server database"); + + return ApplyDiff( + schema, + allowDestructive, + phase, + () => SqlServerSchemaInspector.Inspect(connection), + ops => + MigrationRunner.Apply( + connection, + ops, + SqlServerDdlGenerator.Generate, + new MigrationOptions { AllowDestructive = allowDestructive } + ), + (_, desired) => SqlServerSchemaNormalizer.Normalize(desired) + ); + } + catch (Exception ex) + { + Console.WriteLine($"Error: SQL Server connection/migration failed: {ex}"); + return 1; + } + } +#endif +} diff --git a/Migration/DataProviderMigrate/Program.cs b/Migration/DataProviderMigrate/Program.cs index 7486aad4..622c2fa0 100644 --- a/Migration/DataProviderMigrate/Program.cs +++ b/Migration/DataProviderMigrate/Program.cs @@ -1,10 +1,8 @@ using System.Collections.Immutable; -using Microsoft.Data.SqlClient; using Microsoft.Data.Sqlite; using Nimblesite.DataProvider.Migration.Core; using Nimblesite.DataProvider.Migration.Postgres; using Nimblesite.DataProvider.Migration.SQLite; -using Nimblesite.DataProvider.Migration.SqlServer; using Npgsql; using SchemaIntegrityResultError = Outcome.Result< System.Collections.Immutable.ImmutableArray, @@ -142,12 +140,16 @@ private static int ExecuteMigration(MigrateParseResult.Success args) args.AllowDestructive, args.Phase ), +#if AOT + "sqlserver" or "mssql" => ShowSqlServerUnavailable(), +#else "sqlserver" or "mssql" => MigrateSqlServerDatabase( schema, args.OutputPath, args.AllowDestructive, args.Phase ), +#endif _ => ShowProviderError(args.Provider), }; } @@ -246,42 +248,6 @@ MigratePhase phase } } - // Implements [MIG-SQLSERVER]. - private static int MigrateSqlServerDatabase( - SchemaDefinition schema, - string connectionString, - bool allowDestructive, - MigratePhase phase - ) - { - try - { - using var connection = new SqlConnection(connectionString); - connection.Open(); - Console.WriteLine("Connected to SQL Server database"); - - return ApplyDiff( - schema, - allowDestructive, - phase, - () => SqlServerSchemaInspector.Inspect(connection), - ops => - MigrationRunner.Apply( - connection, - ops, - SqlServerDdlGenerator.Generate, - new MigrationOptions { AllowDestructive = allowDestructive } - ), - (_, desired) => SqlServerSchemaNormalizer.Normalize(desired) - ); - } - catch (Exception ex) - { - Console.WriteLine($"Error: SQL Server connection/migration failed: {ex}"); - return 1; - } - } - /// /// Inspect → diff → filter → apply pipeline shared between SQLite and /// Postgres. filters operations so callers can diff --git a/Migration/Nimblesite.DataProvider.Migration.Tests/NativeAotMigrateSmokeTests.cs b/Migration/Nimblesite.DataProvider.Migration.Tests/NativeAotMigrateSmokeTests.cs index 6f3f32bf..ac284b1e 100644 --- a/Migration/Nimblesite.DataProvider.Migration.Tests/NativeAotMigrateSmokeTests.cs +++ b/Migration/Nimblesite.DataProvider.Migration.Tests/NativeAotMigrateSmokeTests.cs @@ -88,6 +88,26 @@ public void NativeBinary_ExportCommand_ReportsUnsupported() Assert.Contains("not available in the native", result.Output, StringComparison.Ordinal); } + [SkippableFact] + public void NativeBinary_SqlServerProvider_ReportsUnsupported() + { + var bin = RequireNativeBinary(); + var (schemaPath, _) = WriteFixtures(); + + // Implements [MIG-AOT-SQLSERVER]: the SQL Server provider is compiled out of the native build. + var result = RunRaw( + bin, + ["migrate", "--schema", schemaPath, "--output", "Server=x", "--provider", "sqlserver"] + ); + + Assert.True(result.ExitCode != 0, userMessage: result.Output); + Assert.Contains( + "'sqlserver' provider is not available in the native", + result.Output, + StringComparison.Ordinal + ); + } + private static string RequireNativeBinary() { var bin = NativeBinaryPath(); diff --git a/docs/plans/dataprovider-migrate-aot.md b/docs/plans/dataprovider-migrate-aot.md index eb4af1ea..374baa7d 100644 --- a/docs/plans/dataprovider-migrate-aot.md +++ b/docs/plans/dataprovider-migrate-aot.md @@ -67,6 +67,17 @@ binary (cannot load arbitrary external managed DLLs). Decision required: - Option B: drop `export` from the CLI entirely. Default to **A** unless the user says otherwise. +### [MIG-AOT-SQLSERVER] Microsoft.Data.SqlClient is not AOT compatible +`Microsoft.Data.SqlClient` (6.0.1 and 7.1.1) produces `IL2104`/`IL3053` trim and AOT +errors, and pulls in `System.Data` XML serialization and `ConfigurationManager`. +- **DONE.** As with `[MIG-AOT-EXPORT]`, the native binary compiles out the SQL Server + provider: `DataProviderMigrate.csproj` references `Migration.SqlServer` only when + `PublishAot` is not set, and `--provider sqlserver` reports that the provider is + unavailable in the native build. SQL Server migrations remain in the managed + `dotnet tool` and the NuGet packages. `NativeAotMigrateSmokeTests` asserts the message. +- `[MIG-AOT-CI]`: `ci.yml` runs `make aot` and the native smoke tests on every PR, so + an AOT regression fails the PR instead of the release. + ### [MIG-AOT-NPGSQL] Npgsql trim/AOT cleanliness Npgsql 9 is largely AOT-friendly but may emit trim warnings for type mapping. Capture and resolve any `IL2xxx`/`IL3xxx` from Npgsql; add a trim feature switch or