mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-07-20 17:17:58 -04:00
484f3b579e
# Description of changes Builds on #4741 to clean up and refine the NativeAOT-LLVM integration. The major changes: - **Standardized the repo on .NET 10 SDK** (`global.json` → `10.0.100`). The team is migrating to .NET 10 ahead of .NET 8 EOL. End users are unaffected as their SDK version is controlled by their own `global.json` emitted by `spacetime init`. - **Multi-target NuGet packages** instead of conditional single-target: - `SpacetimeDB.Runtime` now unconditionally targets `net8.0;net10.0` - `SpacetimeDB.BSATN.Runtime` now unconditionally targets `netstandard2.1;net8.0;net10.0` - NuGet packages ship both TFMs in a single `.nupkg`. No `EXPERIMENTAL_WASM_AOT` env var needed at pack time - `EXPERIMENTAL_WASM_AOT` define constant is set automatically when `TargetFramework == net10.0` - `ILCompiler.LLVM` package references conditioned on `TargetFramework == net10.0` (not env var) - **Centralized NativeAOT-LLVM build logic** in `SpacetimeDB.Runtime.props` and `SpacetimeDB.Runtime.targets`: - Moved `PublishTrimmed`, `SelfContained`, `WasmEnableThreads` into `.props` (previously duplicated across 4+ `.csproj` files) - Moved the `.wit`-stripping `UseWasiRuntimeOverlayWithoutComponentWit` target into `.targets` (was duplicated in every server `.csproj`) - Added `IlcLlvmTarget=wasm32-unknown-wasip1` override after ILCompiler.LLVM.targets import (previously passed as `/p:` arg) - Added `_InitializeWasiSdk` to `ObtainWasiSdk` BeforeTargets for .NET 10 compatibility - Consumer `.csproj` files no longer need any AOT boilerplate - **Refactored `csharp.rs` build logic** to support three build paths via `CsharpBuildPath` enum: - `Net8Jit` : stable .NET 8 path using `wasi-experimental` workload (Mono WASM) - `Net8Aot` : .NET 8 NativeAOT-LLVM (opt-in via `--native-aot` or `EXPERIMENTAL_WASM_AOT=1`) - `Net10Aot` : .NET 10 NativeAOT-LLVM (auto-detected when .NET 10 SDK is active) - Auto-detects .NET SDK version from `dotnet --version` (respects `global.json`) - Automatically sets `EXPERIMENTAL_WASM_AOT=1` for AOT paths so MSBuild conditionals activate - Unified `dotnet publish` command for all paths. Build-specific config handled by props/targets - Removed redundant `/p:IlcLlvmTarget` and `/p:WasmEnableThreads` CLI args (now in `.targets`) - **Refactored `init.rs`** for .NET version-aware scaffolding: - Added `--dotnet-version` CLI arg to explicitly select .NET 8 or 10 - Added `resolve_dotnet_major()` which auto-detects or prompts interactively when multiple SDKs are installed - .NET 10 auto-enables NativeAOT-LLVM (`--native-aot` not required) - .NET 10 template emits `<TargetFramework>net10.0</TargetFramework>` directly (no conditional) - .NET 8 AOT template adds `ILCompiler.LLVM 8.0.0-*` package refs gated on `EXPERIMENTAL_WASM_AOT=1` - Emits correct `global.json` per SDK version (`8.0.100` or `10.0.100`) - **Added `_initialize` call in `wasmtime_module.rs`**: NativeAOT-LLVM modules are WASI reactors that export `_initialize` to bootstrap the native runtime. This is called before preinit functions. Traditional .NET 8 WASI modules export `_start` instead and are unaffected. - **Updated `FFI.cs` `WasmImportLinkageAttribute` guard** to `#if EXPERIMENTAL_WASM_AOT && NET10_0_OR_GREATER`, ensuring the real attribute is only used when both the AOT flag and .NET 10+ TFM are active. The dummy shim is used for all other builds. - **Updated `csharp_aot_module.rs` smoketest**: - Auto-detects .NET SDK version and adjusts TFM accordingly - .NET 8 AOT: Windows-only (ILCompiler.LLVM 8.0.0-* not published for Linux) - .NET 10 AOT: Windows and Linux - Removed emscripten dependency (NativeAOT-LLVM uses WASI SDK, not emscripten) - **Updated CLI reference docs** to reflect `--native-aot` behavior change (not needed for .NET 10) and new `--dotnet-version` arg. This PR addresses several issues from #4741 review: 1. **NativeAOT-LLVM build logic was duplicated** in every server `.csproj` (4+ files had identical `PropertyGroup` and `Target` blocks). This is now centralized in the shipped `.props`/`.targets`. 2. **NuGet packages only shipped a single TFM**. The env var controlled which TFM was built, meaning you needed `EXPERIMENTAL_WASM_AOT=1` at pack time to get the `net10.0` DLL. Now both TFMs are always included. 3. **`spacetime init` emitted unnecessary conditional wrappers** in the generated `.csproj` for .NET 10 projects. Since the project is definitively targeting .NET 10, the TFM should be unconditional. 4. **`spacetime publish` passed MSBuild properties via `/p:` args** that are now handled by centralized `.targets`, simplifying the CLI code. # API and ABI breaking changes - `global.json` now requires .NET 10 SDK for repo development. All developers and CI agents need .NET SDK 10.0+ installed. - End user module projects are **not affected**. They continue to use whichever SDK their local `global.json` specifies. - .NET 8 JIT and .NET 8 AOT paths remain functional for end users until .NET 8 EOL. # Expected complexity level and risk 2 - The core risk is the same as #4741: future changes in the upstream NativeAOT-LLVM/WASI pipeline. Centralizing the build logic in `.props`/`.targets` reduces the surface area for breaking changes (one place to update vs. N consumer projects). The `.wit`-stripping workaround remains necessary until [dotnet/runtimelab#3144](https://github.com/dotnet/runtimelab/issues/3144) is resolved. # Testing - Ran C# module publish + client connection tests for all three build paths: - .NET 8 JIT (`spacetime publish` without `--native-aot`) - .NET 8 AOT (`spacetime publish` with `EXPERIMENTAL_WASM_AOT=1` and .NET 8 SDK) - .NET 10 AOT (`spacetime publish` with .NET 10 SDK, auto-detected) - Verified `spacetime init --lang csharp` generates correct project structure for both .NET 8 and .NET 10 - Verified `spacetime init --lang csharp --dotnet-version 10` emits `net10.0` TFM and `10.0.100` in `global.json` - Verified C# regression tests complete successfully when ran under .NET 8 JIT, .NET 8 AOT, .NET 10 AOT - This required making local customizations to the repo versions of the regression tests, in order to conform to the configuration output by `spacetime init`, but the tests themselves remained identical. - Verified NuGet packages contain both `lib/net8.0/` and `lib/net10.0/` DLLs - Updated smoketest `test_build_csharp_module_aot` to work with both .NET 8 and .NET 10 SDK --------- Signed-off-by: Ryan <r.ekhoff@clockworklabs.io> Co-authored-by: Jason Larabie <jason@clockworklabs.io> Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com> Co-authored-by: Lisandro Crespo <lisandroct@gmail.com> Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
118 lines
3.8 KiB
C#
118 lines
3.8 KiB
C#
namespace SpacetimeDB;
|
|
|
|
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
#pragma warning disable STDB_UNSTABLE
|
|
public abstract class HandlerContextBase
|
|
{
|
|
public Random Rng => txState.Rng;
|
|
public Timestamp Timestamp => txState.Timestamp;
|
|
|
|
// NOTE: The host rejects procedure HTTP requests while a mut transaction is open
|
|
// (WOULD_BLOCK_TRANSACTION). Avoid calling `Http.*` inside WithTx.
|
|
public HttpClient Http { get; } = new();
|
|
|
|
// **Note:** must be 0..=u32::MAX
|
|
protected int CounterUuid = 0;
|
|
private readonly TransactionalContextState<HandlerTxContextBase> txState;
|
|
|
|
protected HandlerContextBase(Random random, Timestamp time)
|
|
{
|
|
txState = new(
|
|
random,
|
|
time,
|
|
timestamp => new Internal.TxContext(
|
|
CreateLocal(),
|
|
default,
|
|
null,
|
|
timestamp,
|
|
AuthCtx.BuildFromSystemTables(null, default),
|
|
random
|
|
),
|
|
inner => CreateTxContext(inner)
|
|
);
|
|
}
|
|
|
|
protected abstract HandlerTxContextBase CreateTxContext(Internal.TxContext inner);
|
|
protected abstract LocalBase CreateLocal();
|
|
|
|
public Internal.TxContext EnterTxContext(long timestampMicros) =>
|
|
txState.EnterTxContext(timestampMicros);
|
|
|
|
public void ExitTxContext() => txState.ExitTxContext();
|
|
|
|
public readonly struct TxOutcome<TResult>(bool isSuccess, TResult? value, Exception? error)
|
|
{
|
|
public bool IsSuccess { get; } = isSuccess;
|
|
public TResult? Value { get; } = value;
|
|
public Exception? Error { get; } = error;
|
|
|
|
public static TxOutcome<TResult> Success(TResult value) => new(true, value, null);
|
|
|
|
public static TxOutcome<TResult> Failure(Exception error) => new(false, default, error);
|
|
|
|
public TResult UnwrapOrThrow() =>
|
|
IsSuccess
|
|
? Value!
|
|
: throw (
|
|
Error
|
|
?? new InvalidOperationException("Transaction failed without an error object.")
|
|
);
|
|
}
|
|
|
|
[Experimental("STDB_UNSTABLE")]
|
|
public TResult WithTx<TResult>(Func<HandlerTxContextBase, TResult> body) =>
|
|
txState.WithTx(body);
|
|
|
|
[Experimental("STDB_UNSTABLE")]
|
|
public TxOutcome<TResult> TryWithTx<TResult, TError>(
|
|
Func<HandlerTxContextBase, Result<TResult, TError>> body
|
|
)
|
|
where TError : Exception
|
|
{
|
|
var outcome = txState.TryWithTx(body);
|
|
return outcome.IsSuccess
|
|
? TxOutcome<TResult>.Success(outcome.Value!)
|
|
: TxOutcome<TResult>.Failure(
|
|
outcome.Error
|
|
?? new InvalidOperationException("Transaction failed without an error object.")
|
|
);
|
|
}
|
|
}
|
|
|
|
public abstract class HandlerTxContextBase(Internal.TxContext inner) : IRefreshableTxContext
|
|
{
|
|
internal Internal.TxContext Inner { get; private set; } = inner;
|
|
|
|
internal void Refresh(Internal.TxContext inner) => Inner = inner;
|
|
|
|
void IRefreshableTxContext.Refresh(Internal.TxContext inner) => Refresh(inner);
|
|
|
|
public LocalBase Db => (LocalBase)Inner.Db;
|
|
public Timestamp Timestamp => Inner.Timestamp;
|
|
public Random Rng => Inner.Rng;
|
|
}
|
|
|
|
internal sealed partial class RuntimeHandlerContext(Random random, Timestamp timestamp)
|
|
: HandlerContextBase(random, timestamp)
|
|
{
|
|
private readonly RuntimeLocal _db = new();
|
|
|
|
protected override LocalBase CreateLocal() => _db;
|
|
|
|
protected override HandlerTxContextBase CreateTxContext(Internal.TxContext inner) =>
|
|
_cached ??= new RuntimeHandlerTxContext(inner);
|
|
|
|
private RuntimeHandlerTxContext? _cached;
|
|
}
|
|
|
|
internal sealed class RuntimeHandlerTxContext : HandlerTxContextBase
|
|
{
|
|
internal RuntimeHandlerTxContext(Internal.TxContext inner)
|
|
: base(inner) { }
|
|
|
|
public new RuntimeLocal Db => (RuntimeLocal)base.Db;
|
|
}
|
|
#pragma warning restore STDB_UNSTABLE
|