diff --git a/AGENTS.md b/AGENTS.md index 49c3de6..4e4bb69 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,4 +27,4 @@ This public repository owns its current behavior, terminology, and accepted deci | Release changes | [Changelog](CHANGELOG.md) | | Security disclosure or support window | [Security policy](.github/SECURITY.md) | -Current architecture documents describe `master`; a draft source-generator branch does not establish Native AOT support here. +The [platform boundary](docs/architecture/platform-and-packaging.md#native-aot-and-trimming) documents Native AOT support, qualified by the [Native AOT test lane](docs/engineering/quality-gates.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index d23a0ad..2363240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,10 @@ Planned as **3.0.0** for both packages; not yet published. ### Added -- Added `net10.0` and `net11.0` assets to Core (`LocalStack.Client`) and Extensions (`LocalStack.Client.Extensions`). .NET 11 is not generally available yet; the `net11.0` assets are built with a .NET 11 preview SDK. +- Added `net10.0` and `net11.0` assets to Core (`LocalStack.Client`) and Extensions (`LocalStack.Client.Extensions`). .NET 11 is not generally available yet; the `net11.0` assets are built with the .NET 11 RC2 SDK pinned in `global.json`. - Both packages support Native AOT and trimming on these targets with zero trim/AOT warnings, no suppressions, and no consumer code changes. This includes `AddLocalStack(configuration)`, all Add/TryAdd/alias registration overloads, Core `Session` / `SessionStandalone`, and the `Type`-based APIs. -- On `net10.0` and `net11.0`, both packages use no runtime reflection for AWS SDK service clients and configs, or for custom clients and configs that follow the SDK's shape (public or internal, concrete, non-generic, with the standard `(AWSCredentials, XConfig)` constructors). The generic APIs (`AddAwsService` and every overload, `CreateClientByInterface()`) use the AWS SDK v4 static factories and do not depend on source generation, so they also work in F# and Visual Basic. The `Type`-based APIs use a build-time source generator that ships in Core and turns itself on for C# projects targeting `net10.0` or later, with no setup or extra package. Other client shapes (private or generic custom clients, F# and Visual Basic projects for the `Type`-based APIs, assemblies loaded at run time) keep the previous behavior through a reflection fallback that runs only under JIT; trimmed and Native AOT apps do not include it. These claims cover LocalStack.NET's own code; the AWS SDK, dependency injection and BCL internals have their own AOT status, and LocalStack determines its own service coverage. +- On `net10.0` and `net11.0`, both packages use no runtime reflection for AWS SDK service clients and configs, or for accessible, concrete, non-generic custom clients/configs with constructors and properties eligible for the [generated registry](docs/architecture/platform-and-packaging.md#native-aot-and-trimming). The generic interface APIs (`AddAwsService` and every overload, `CreateClientByInterface()`) use the AWS SDK v4 static factories when the injected helper is exactly the default `SessionReflection` and do not depend on source generation, so they also work in F# and Visual Basic; custom or mocked helpers retain the previous calls and ordering. The `Type`-based APIs and generic implementation API use a build-time source generator that ships in Core and turns itself on for net10-compatible C# projects, with no setup or extra package. Other client shapes (private or generic custom clients, F# and Visual Basic projects for the `Type`-based APIs, assemblies loaded at run time, unusual constructors or properties) keep the previous behavior through a reflection fallback that runs only under JIT; trimmed and Native AOT apps do not include it. These claims cover LocalStack.NET's own code; the AWS SDK, dependency injection and BCL internals have their own AOT status, and LocalStack determines its own service coverage. +- Both packages declare `IsAotCompatible` on `net10.0` and `net11.0`. ### Changed @@ -37,6 +38,8 @@ Planned as **3.0.0** for both packages; not yet published. ### Build, tests and CI - The repository now builds with the .NET 11 SDK pinned in `global.json`; test projects and CI cover `net10.0` and `net11.0`. +- Added the xUnit v3 `LocalStack.Client.NativeAot.Tests` lane and the `native-aot-tests` CI job, which publish with `PublishAot` and run native executables on Windows and Linux for both modern targets; Linux also runs live S3/SQS round trips against LocalStack 4.14.0. Cake skips this project. +- With the .NET 11 SDK, .NET Framework (`net472`) tests run on Windows only; Cake skips them elsewhere with a warning, and CI no longer installs Mono on macOS. - Refreshed build, test, and analyzer dependencies, including Cake.Frosting 6.3.0, NuGet.Versioning 7.9.0, SourceLink 10.0.402, Testcontainers and Testcontainers.LocalStack 4.16.0, and `System.Security.Cryptography.Pkcs` 10.0.13. Restore resolves Microsoft.Build.Tasks.Git 10.0.402 and SSH.NET 2026.0.0; repository-wide `NuGetAuditMode=all` remains enabled. - Test and sandbox consumers use centrally managed Microsoft.Extensions 10.0.13. Refreshed 112 AWS service SDK pins and retained six already-latest entries, with no catalog additions or removals. Core-only projects resolve 4.0.102.6; Extensions and service-package consumers resolve 4.0.102.11 under their dependency ranges. - Cake no longer changes global Git `core.autocrlf` on Unix; `.gitattributes` normalizes line endings. The test task no longer deletes every Docker container; Testcontainers removes the containers each run starts. Removed `Cake.Docker`. diff --git a/Directory.Build.props b/Directory.Build.props index eb3db50..73428ad 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,8 +5,8 @@ LocalStack.NET https://github.com/localstack-dotnet/localstack-dotnet-client localstack-dotnet-square.png - 2.0.0 - 2.0.1 + 3.0.0 + 3.0.0 true snupkg 13.0 diff --git a/GLOSSARY.md b/GLOSSARY.md index a71a28e..fb5d05e 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -25,5 +25,5 @@ The application-level choice between creating an AWS service client through this _Avoid_: LocalStack mode for the separate choice between an explicit service URL and a region-based client configuration. **LocalStack.NET version line**: -A major-version family of this library's packages: v1.x targets AWS SDK v3, and v2.x targets AWS SDK v4. It is separate from the LocalStack emulator image version and the version of an individual AWS SDK package. +A major-version family of this library's packages: v1.x targets AWS SDK v3; v2.x and v3.x target AWS SDK v4. The current line is v3.x; v2.x is superseded and v1.x is unmaintained. It is separate from the LocalStack emulator image version and the version of an individual AWS SDK package. _Avoid_: An unqualified “LocalStack version” when referring to the library. diff --git a/docs/adr/0001-aws-sdk-v4-and-v1-maintenance.md b/docs/adr/0001-aws-sdk-v4-and-v1-maintenance.md index e8c0469..7cb0e37 100644 --- a/docs/adr/0001-aws-sdk-v4-and-v1-maintenance.md +++ b/docs/adr/0001-aws-sdk-v4-and-v1-maintenance.md @@ -1,7 +1,7 @@ -# Use AWS SDK v4 on the supported v2 package line +# Use AWS SDK v4 on the supported package line -The supported `LocalStack.Client` and `LocalStack.Client.Extensions` v2 line uses AWS SDK v4 on `master`. SDK v4 changes the integration surface beyond a single client-factory fix, so it is a major-version integration change rather than a patch to the SDK v3-based v1 line. The existing package identities are retained, with major versions separating SDK compatibility. +Since v2, `LocalStack.Client` and `LocalStack.Client.Extensions` use AWS SDK v4 on `master`; the current line is 3.x. SDK v4 changes the integration surface beyond a single client-factory fix, so it is a major-version integration change rather than a patch to the SDK v3-based v1 line. The existing package identities are retained, with major versions separating SDK compatibility. -The `sdkv3-lts` branch preserves the v1/SDK v3 code for existing consumers, but that line is no longer maintained. Consumers needing supported development or security updates must use v2; the [security policy](../../.github/SECURITY.md) owns the support window, and the [platform boundary](../architecture/platform-and-packaging.md) owns target-framework details. +The `sdkv3-lts` branch preserves the v1/SDK v3 code for existing consumers, but that line is no longer maintained. Consumers needing supported development or security updates must use the current line; the [security policy](../../.github/SECURITY.md) owns the support window, and the [platform boundary](../architecture/platform-and-packaging.md) owns target-framework details. **Rationale:** [Maintainer explanation](https://github.com/orgs/localstack-dotnet/discussions/45) and [AWS SDK v4 integration](https://github.com/localstack-dotnet/localstack-dotnet-client/pull/46). diff --git a/docs/adr/0004-native-aot-preserves-existing-consumer-code.md b/docs/adr/0004-native-aot-preserves-existing-consumer-code.md index a884450..4532b14 100644 --- a/docs/adr/0004-native-aot-preserves-existing-consumer-code.md +++ b/docs/adr/0004-native-aot-preserves-existing-consumer-code.md @@ -1,7 +1,7 @@ # Native AOT support preserves existing consumer code -Native AOT support for this library means that an application using the existing public entry points publishes and runs under strict trimming and Native AOT without changing those calls. Examples of such entry points are `services.AddLocalStack(configuration)` with `services.AddAwsService()`, or a Core `Session`. Configuration, lifetime, injection, and exception behavior must stay intact. A separate AOT-only registration or factory path that consumers must migrate to does not meet this goal on its own, because it moves the cost to every consumer and leaves the documented entry points unsupported. +Native AOT support preserves existing consumer calls, including `AddLocalStack(configuration)`, `AddAwsService()`, and Core `Session` APIs. The generic interface path uses AWS SDK v4 static factories; a slim build-time generator supplies direct operations for the `Type` APIs, and a guarded reflection fallback preserves JIT behavior for shapes the compiler cannot see. This keeps upgrades source-compatible while removing runtime reflection from LocalStack.NET's qualified Native AOT paths. -An additive opt-in API, such as typed options or an explicit factory, is easier to make trim-safe while the existing calls stay reflection-based. It is not the chosen direction. Techniques that keep the existing calls remain in scope, and no single technique is chosen yet. Two examples are build-time generation, explored in [draft PR #49](https://github.com/localstack-dotnet/localstack-dotnet-client/pull/49), and the SDK's public factories. +Rooting the existing reflection with generated code was AOT-compatible, but retained reflection and imposed heavy consumer build cost. Marking the `Type` APIs AOT-unsupported would break the goal that existing calls work. Removing the fallback would break existing JIT consumers such as F#/VB projects using `Type` APIs and runtime-loaded clients, so trimming/Native AOT removes it while JIT retains it. -If a change to consumer code or the public API proves unavoidable, the maintainer must decide it before implementation starts. The proposal names the exact consumer call that has to change and why. Acceptance evidence is an unchanged consumer of the existing calls, built with strict trimming and Native AOT and executed, not a new consumer of a new path. The [platform boundary](../architecture/platform-and-packaging.md#native-aot-and-trimming) owns the current support status. +The [platform boundary](../architecture/platform-and-packaging.md#native-aot-and-trimming) owns exact shape/compiler limits and the Native AOT test lane that qualifies unchanged calls by publishing and running them; the [implementation](../../src/LocalStack.Client/Session.cs) and [generator](../../src/LocalStack.Client.Generators/AwsRegistryGenerator.cs) define the two paths. diff --git a/docs/architecture/client-runtime.md b/docs/architecture/client-runtime.md index 5a676f1..b80a42b 100644 --- a/docs/architecture/client-runtime.md +++ b/docs/architecture/client-runtime.md @@ -1,6 +1,6 @@ # Client runtime and service mapping -Date: 2026-10-22 +Date: 2026-10-23 The core package creates AWS SDK service clients configured for LocalStack. The [glossary](../../GLOSSARY.md) distinguishes an AWS service, an SDK client, and a configured LocalStack endpoint. @@ -12,10 +12,12 @@ The catalog is a mapping in **this library**, not a list of all AWS operations a ## Client creation -[`Session`](../../src/LocalStack.Client/Session.cs) retains its three-argument constructor accepting `ISessionOptions`, `IConfig`, and an injected `ISessionReflection`. Its interface-based path locates the AWS SDK implementation type by convention; its implementation-based path starts from the concrete type. Both call the injected helper to obtain service metadata and construct the corresponding `ClientConfig`, then create an SDK client with the configured credentials. The default [`SessionReflection`](../../src/LocalStack.Client/Utils/SessionReflection.cs) inspects the SDK's private `serviceMetadata`. +[`Session`](../../src/LocalStack.Client/Session.cs) retains its three-argument constructor accepting `ISessionOptions`, `IConfig`, and an injected `ISessionReflection`. On `net10.0`/`net11.0`, `CreateClientByInterface()` uses AWS SDK v4 static factories when the helper is exactly the default [`SessionReflection`](../../src/LocalStack.Client/Utils/SessionReflection.cs): `TClient.CreateDefaultClientConfig()` supplies the config and its `ServiceId` identifies the endpoint; `TClient.CreateDefaultServiceClient(...)` creates the client with the configured credentials. This path needs no source generation and also works in F#/VB. A custom or mocked helper retains the previous helper calls and ordering. The `Type` interface path locates the implementation by convention; both implementation overloads start from a concrete type and use the helper. -The client configuration sets `UseHttp`, `ProxyHost`, and `ProxyPort` from the LocalStack settings. Where the configuration exposes `ForcePathStyle`, the reflection helper sets it for S3-compatible addressing. With `useServiceUrl: true`, `Session` sets the explicit `ServiceURL`; otherwise it requires a region name and sets `RegionEndpoint`. These are two configuration paths for a LocalStack-created client, **not** the application-level LocalStack/AWS mode switch. They do not by themselves prove where every later request travels. +The client configuration sets `UseHttp`, `ProxyHost`, and `ProxyPort` from the LocalStack settings. The modern generic interface path sets S3 `ForcePathStyle` through a [string-named `UnsafeAccessor`](../../src/LocalStack.Client/Utils/S3ForcePathStyle.cs); helper paths use that accessor, a generated setter, or the JIT reflection fallback where applicable. With `useServiceUrl: true`, `Session` sets the explicit `ServiceURL`; otherwise it requires a region name and sets `RegionEndpoint`. These are two configuration paths for a LocalStack-created client, **not** the application-level LocalStack/AWS mode switch. They do not by themselves prove where every later request travels. -On modern .NET assets, the unreleased implementation automatically generates rooted reflection operations for client and configuration types discovered in C# source and references, including closed types found in declaration signatures. An internal registry uses actual runtime type identity. Initialization stores delegates only: metadata access, configuration creation, property mutation, and client construction remain lazy at their original call sites. Generated hits are used on JIT too; dynamic-code-supported misses and legacy assets retain reflection fallbacks. This preserves injected helper calls, their ordering, metadata/configuration identity, and reflection constructor/property behavior rather than bypassing them with a separate factory path. It does not enable arbitrary unseen plugins to be loaded under Native AOT. +For the modern `Type` APIs and default helper, the [slim generator](../../src/LocalStack.Client.Generators/AwsRegistryGenerator.cs) discovers source class declarations with bases and public types in references that contain or directly reference the AWS client base assembly. It excludes generic, file-local, and inaccessible types; eligible clients are concrete, with supported constructors/configs. It emits direct C# operations: `UnsafeAccessor` for `serviceMetadata`, `new TConfig()`, direct client constructors, a setter for the simple public bool `ForcePathStyle` shape, and a plain-`RegionEndpoint` flag. Constructor and property eligibility rules stay in the generator. The registry keys by runtime type identity and stores delegates; operations remain lazy at their helper/session call sites, including on JIT. -[`SessionStandalone`](../../src/LocalStack.Client/SessionStandalone.cs) creates this session without dependency injection. The optional Extensions package owns the application-level mode switch in [Extensions and composition](extensions-and-composition.md). See [platform and packaging](platform-and-packaging.md#native-aot-and-trimming) for the unreleased Native AOT validation boundary and automatic compiler-asset packaging. +Registry misses and unusual shapes use the previous reflection code only while [`ReflectionFallback.IsSupported`](../../src/LocalStack.Client/Utils/ReflectionFallback.cs) is true. Its feature guard removes that fallback under trimming/Native AOT; the [platform boundary](platform-and-packaging.md#native-aot-and-trimming) lists miss behavior and compiler/type limits. The `netstandard2.0`/`net472` assets keep their previous reflection paths. + +[`SessionStandalone`](../../src/LocalStack.Client/SessionStandalone.cs) creates this session without dependency injection. The optional Extensions package owns the application-level mode switch in [Extensions and composition](extensions-and-composition.md). See [platform and packaging](platform-and-packaging.md#native-aot-and-trimming) for the unreleased 3.0.0 Native AOT qualification lane and compiler-asset packaging. diff --git a/docs/architecture/extensions-and-composition.md b/docs/architecture/extensions-and-composition.md index 1218d55..67e6549 100644 --- a/docs/architecture/extensions-and-composition.md +++ b/docs/architecture/extensions-and-composition.md @@ -1,15 +1,15 @@ # Extensions and application composition -Date: 2026-10-22 +Date: 2026-10-23 `LocalStack.Client.Extensions` integrates the core package with .NET configuration and dependency injection. The [client runtime](client-runtime.md) owns endpoint mapping and LocalStack client creation; this document owns application-level composition. [`AddLocalStack(IConfiguration)`](../../src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs) binds the `LocalStack` section to `LocalStackOptions`, `SessionOptions`, and `ConfigOptions`. The unreleased implementation uses [fixed-schema typed mutation](../../src/LocalStack.Client.Extensions/LocalStackOptionsBinder.cs), not general reflective configuration binding. Private setters and defaults are retained. Binding stays lazy inside the existing three `Configure` registrations, preserving conversion behavior and the framework's configure/post-configure, validation, and caching pipeline. The aggregate's nested session/configuration objects and the separately registered options remain independent instances. -It registers `IConfig` as transient and `ISessionReflection`, `ISession`, and `IAwsClientFactoryWrapper` as singletons. Registration prepares both client-creation paths; it does not itself enable LocalStack mode. Existing C# registrations receive Core's source generation automatically, including when only the Extensions package is referenced; no consumer registration change is needed. The [platform boundary](platform-and-packaging.md#native-aot-and-trimming) owns the unreleased Native AOT scope. +It registers `IConfig` as transient and `ISessionReflection`, `ISession`, and `IAwsClientFactoryWrapper` as singletons. Registration prepares both client-creation paths; it does not itself enable LocalStack mode. On `net10.0`/`net11.0`, every Add/TryAdd/alias overload uses the generic interface session path in LocalStack mode: with exactly the default `SessionReflection`, AWS SDK v4 static factories create the config/client without runtime reflection or source generation, including in F#/VB. Custom or mocked helpers retain the previous calls and ordering. Core's slim generator serves the `Type` APIs automatically in net10-compatible C# package consumers, including Extensions-only references. The [platform boundary](platform-and-packaging.md#native-aot-and-trimming) owns the unreleased 3.0.0 scope and JIT-only fallback limits. `AddAwsService` registers an AWS service interface (singleton by default). When the configured `UseLocalStack` value is `true`, resolution calls `Session.CreateClientByInterface` and uses the LocalStack catalog. When it is `false`—the options default—resolution calls [`AwsClientFactoryWrapper`](../../src/LocalStack.Client.Extensions/AwsClientFactoryWrapper.cs) to create an ordinary AWS SDK client. `AddAWSServiceLocalStack` is an alias for the registration method, **not** an override of the mode switch. `TryAddAwsService` only adds a registration when one is absent. The AWS-mode wrapper obtains the client factory from the SDK's public `AddAWSService` registration and invokes it with the original `IServiceProvider`. This preserves SDK options fallback and logger access without reflecting over private SDK members or constructing a second provider. Explicit options still override provider defaults, the outer registration owns the client lifetime, and factory exceptions retain the `TargetInvocationException` boundary. The public wrapper and interface remain available. The [decision record](../adr/0002-aws-setup-constructor-compatibility.md) explains the provider-aware public factory choice. -**Current overload caveat:** the shorter `TryAddAwsService(lifetime, useServiceUrl)` overload does not forward its `useServiceUrl` argument. The overload that accepts `AWSOptions?` does forward it. Do not describe URL selection as reliable across both overloads until the implementation changes. The `useServiceUrl` switch itself is distinct from `UseLocalStack`: it selects a client configuration path only after the LocalStack session is chosen. +Both `TryAddAwsService` overloads forward `useServiceUrl`, including the shorter `(lifetime, useServiceUrl)` overload fixed for #63; the switch selects a client configuration path after LocalStack mode is chosen and is distinct from `UseLocalStack`. diff --git a/docs/architecture/platform-and-packaging.md b/docs/architecture/platform-and-packaging.md index 8a00062..dfc79a8 100644 --- a/docs/architecture/platform-and-packaging.md +++ b/docs/architecture/platform-and-packaging.md @@ -1,6 +1,6 @@ # Platform and packaging boundary -Date: 2026-10-22 +Date: 2026-10-23 The source of package versions, target frameworks, and dependencies is the project configuration, not a prose copy of their values: @@ -8,38 +8,40 @@ The source of package versions, target frameworks, and dependencies is the proje | --- | --- | | Core and Extensions package versions | [`Directory.Build.props`](../../Directory.Build.props) | | AWS SDK and other package versions; pinned versus floating canary track | [`Directory.Packages.props`](../../Directory.Packages.props) | -| Core target frameworks | [`LocalStack.Client.csproj`](../../src/LocalStack.Client/LocalStack.Client.csproj) | -| Extensions target frameworks and dependency on Core | [`LocalStack.Client.Extensions.csproj`](../../src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj) | +| Target frameworks (`LocalStackModernTargetFrameworks`, `LocalStackModernMinimumTargetFramework`, the per-package lists) and the `IsLocalStackModernTarget` gate | [`Directory.Build.props`](../../Directory.Build.props) | +| Extensions dependency on Core | [`LocalStack.Client.Extensions.csproj`](../../src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj) | | Development SDK selection | [`global.json`](../../global.json) | | Publication triggers and credentials mechanism | [GitHub Actions workflows](../../.github/workflows/) | -For Extensions' four target frameworks (`netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`), the conditional references and `VersionOverride` values in [LocalStack.Client.Extensions.csproj](../../src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj), together with the central `Microsoft.Extensions.Options` version, define these consumer minimums: +For Extensions' three target frameworks (`netstandard2.0`, `net10.0`, and `net11.0`), the conditional references and `VersionOverride` values in [LocalStack.Client.Extensions.csproj](../../src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj), together with the central `Microsoft.Extensions.Options` version, define these consumer minimums: | Package | Declared minimum | Target frameworks | | --- | --- | --- | -| `Microsoft.Extensions.Configuration.Abstractions` | 3.1.32 | All four | -| `Microsoft.Extensions.Configuration.Binder` | 3.1.32 | All four | -| `Microsoft.Extensions.Logging.Abstractions` | 3.1.32 | All four | -| `Microsoft.Extensions.Options.ConfigurationExtensions` | 3.1.32 | All four | +| `Microsoft.Extensions.Configuration.Abstractions` | 3.1.32 | All three | +| `Microsoft.Extensions.Configuration.Binder` | 3.1.32 | All three | +| `Microsoft.Extensions.Logging.Abstractions` | 3.1.32 | All three | +| `Microsoft.Extensions.Options.ConfigurationExtensions` | 3.1.32 | All three | | `Microsoft.Extensions.DependencyInjection.Abstractions` | 3.1.32 | `netstandard2.0` | -| `Microsoft.Extensions.DependencyInjection.Abstractions` | 8.0.0 | `net8.0`, `net9.0`, `net10.0` | -| `Microsoft.Extensions.Options` | 8.0.2 | `net8.0`, `net9.0`, `net10.0` | +| `Microsoft.Extensions.DependencyInjection.Abstractions` | 8.0.0 | `net10.0`, `net11.0` | +| `Microsoft.Extensions.Options` | 8.0.2 | `net10.0`, `net11.0` | These are this package's consumer minimums, not a copy of the selected [`AWSSDK.Extensions.NETCore.Setup` 4.0.102.4 package ranges](https://www.nuget.org/packages/AWSSDK.Extensions.NETCore.Setup/4.0.102.4). Setup 4.0.102.4 permits older 2.x configuration, logging, and legacy dependency-injection packages, and a `net8.0` dependency-injection package at 8.0.0; those permissive ranges do not lower or define Extensions' own contract. Central versions in [Directory.Packages.props](../../Directory.Packages.props) govern test and sandbox consumers separately. A consumer's resolved graph can select a higher version when another dependency requires it. The modern-only Options 8.0.2 reference is an unreleased Native AOT fix, not a blanket dependency refresh. It also raises the transitive `Microsoft.Extensions.Primitives` minimum to 8.0.0 on those assets. The `netstandard2.0` dependency floor is unchanged. The centrally managed 10.0.13 versions of other Microsoft.Extensions packages used by tests and sandboxes are not consumer minimums; successful execution with a higher resolved graph does not qualify every declared minimum-version combination. -At this revision Core explicitly targets `netstandard2.0`, `net472`, `net8.0`, `net9.0`, and `net10.0`; Extensions targets `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`. The AWS SDK v4 migration changed the explicit .NET Framework target from 4.6.2 to 4.7.2. The two packages can have different versions: the [changelog](../../CHANGELOG.md) records the Extensions-only 2.0.1 release. For an installation instruction, consult the actual package feed as well as the declared project version. +Core targets `netstandard2.0`, `net472`, and the modern targets (`net10.0`, `net11.0`); Extensions targets `netstandard2.0` and the modern targets. Adding a .NET version means adding it to `LocalStackModernTargetFrameworks`; the projects, the packaged generator gate (generated into `buildTransitive/LocalStack.Client.props` at pack time), Cake and the CI Native AOT steps all read it. .NET 8/9 apps select `netstandard2.0` under JIT, without Native AOT; both runtimes reach end of support on 2026-11-10. The .NET 11 RC2 SDK pinned in [global.json](../../global.json) builds `net11.0` until .NET 11 GA on 2026-11-10. Both packages are planned as **3.0.0, unreleased**. Earlier versions are not supported; the [security policy](../../.github/SECURITY.md) owns support windows. Consult the actual package feed before claiming publication. ## Native AOT and trimming -The **unreleased implementation** preserves existing C# entry points through automatic source generation and fixed-schema options binding. Core packages a compiler-only generator and a [`buildTransitive` target](../../src/LocalStack.Client/buildTransitive/LocalStack.Client.targets) that enables it for .NET 8-compatible targets. It flows to Core-only and Extensions-only package consumers without manual generator enrollment and introduces no runtime compiler dependency. Generated operations root the existing metadata, constructor, and property reflection; this is not a reflection-free or parallel consumer API. The [client runtime](client-runtime.md#client-creation) and [composition](extensions-and-composition.md) documents own those behaviors. +The **unreleased 3.0.0 implementation** supports Native AOT and trimming through the existing entry points on `net10.0` and `net11.0`; both package projects set `IsAotCompatible` from `IsLocalStackModernTarget`. LocalStack.NET's own supported client/configuration paths use direct operations without runtime reflection, and Extensions uses fixed-schema options binding. The AWS SDK, DI, and BCL internals have their own status; these statements do not qualify those internals or LocalStack's service coverage. -Local-package validation covers .NET 10 Native AOT with its mandatory trimming on `win-x64` and `linux-x64`: unchanged `AddLocalStack(configuration)` / `AddAwsService()` resolution and `ListBucketsAsync`, the eight existing S3 Add/TryAdd/alias registration overloads, and Core `Session` generic/`Type` paths and `SessionStandalone`. Core-only, Extensions-only, and combined consumers publish without trim/AOT warnings or authored suppressions and run against LocalStack. Matching JIT runs verify endpoint, region, credentials, and configured-value parity, while retaining the documented shorter TryAdd overload caveat. +`Session.CreateClientByInterface()`, used by all eight Add/TryAdd/alias overloads in LocalStack mode, uses AWS SDK v4 static factories when the injected helper is exactly the default `SessionReflection`. It creates configuration with `TClient.CreateDefaultClientConfig()`, looks up the endpoint by `ClientConfig.ServiceId`, sets S3 `ForcePathStyle` through a [string-named `UnsafeAccessor`](../../src/LocalStack.Client/Utils/S3ForcePathStyle.cs), and creates the client with `TClient.CreateDefaultServiceClient(...)`. This path needs no generator and also works in F#/VB. Custom or mocked `ISessionReflection` implementations retain the previous helper calls and ordering. -This is not a claim for every cataloged service or operation, other native RIDs or frameworks, non-C# consumer compilers, or arbitrary runtime-loaded types absent from the compiled graph. Modern assets contain the implementation, but no separate trimmed-JIT deployment promise or `IsAotCompatible` declaration is made. Do not suppress trim/AOT warnings to obtain a successful publication. +The `Type` APIs, generic implementation API, and default `ISessionReflection` operations use a registry populated by the [slim generator](../../src/LocalStack.Client.Generators/AwsRegistryGenerator.cs) shipped in Core. Its [`buildTransitive` target](../../src/LocalStack.Client/buildTransitive/LocalStack.Client.targets) enables generation for net10-compatible C# consumers, including Extensions-only package consumers, without enrollment or a runtime compiler dependency. For discoverable, accessible, concrete, non-generic client shapes, it emits `UnsafeAccessor` metadata access, `new TConfig()`, direct constructors, a direct `ForcePathStyle` setter for the simple public bool property shape, and a plain-`RegionEndpoint` flag. Exact discovery and constructor/property eligibility rules live in the generator; [client runtime](client-runtime.md#client-creation) describes their use. -This capability is **not part of the published Core 2.0.0 / Extensions 2.0.1 releases**; no release version or delivery date is assigned here. The [Native AOT decision](../adr/0004-native-aot-preserves-existing-consumer-code.md) defines the unchanged-consumer contract. Earlier draft generator work is not evidence that a released package meets it. +F#/VB projects using the `Type` APIs, runtime-loaded assemblies, private/generic custom clients, and unusual constructor/property shapes retain the previous reflection behavior under JIT. [`ReflectionFallback.IsSupported`](../../src/LocalStack.Client/Utils/ReflectionFallback.cs) is an auto-property with `FeatureGuard(typeof(RequiresUnreferencedCodeAttribute))`; ILLink/ILC treat it as false, removing the fallback from trimmed and Native AOT apps without warnings or suppressions. On a miss without fallback, metadata throws `InvalidOperationException("Invalid service type ...")`, config creation throws `InvalidOperationException("Sequence contains no matching element")`, style returns `false` when neither the S3 accessor nor a generated setter applies, region sets `ClientConfig.RegionEndpoint` directly, client-type lookup returns not found, and implementation creation throws `NotSupportedException`. The `netstandard2.0`/`net472` assets retain the previous reflection paths. + +The qualification mechanism is [`LocalStack.Client.NativeAot.Tests`](../../tests/LocalStack.Client.NativeAot.Tests/), an xUnit v3 AOT executable published with `PublishAot` for `net10.0`/`net11.0` and run by CI's `native-aot-tests` job on `win-x64` (`windows-latest`) and `linux-x64` (`ubuntu-22.04`). It checks unchanged `AddLocalStack` + `AddAwsService`, all eight registration overloads, Core generic/`Type` paths, and `SessionStandalone`; Linux also exercises live S3/SQS round trips against LocalStack 4.14.0 via `LOCALSTACK_NATIVE_AOT_HOST`. The gate requires no trim/AOT warnings or suppressions. This is not an all-service, all-operation, arbitrary-type, or other-RID claim. [Quality gates](../engineering/quality-gates.md) owns execution details; [ADR 0004](../adr/0004-native-aot-preserves-existing-consumer-code.md) owns the decision. A build alone does not establish that this lane passed or that 3.0.0 was published. ## Consumer README and release channels diff --git a/docs/engineering/maintenance-playbook.md b/docs/engineering/maintenance-playbook.md index 569fd33..45b7da7 100644 --- a/docs/engineering/maintenance-playbook.md +++ b/docs/engineering/maintenance-playbook.md @@ -58,9 +58,10 @@ LocalStack platform authentication activates the emulator. [AWS credentials](htt Read [quality gates](quality-gates.md) before selecting any build, test, Docker, or packaging command. That document owns task side effects and safe execution prerequisites. Inspect the relevant [Cake tasks](../../build/LocalStack.Build/CakeTasks/) and [workflows](../../.github/workflows/) for the current commands and defaults. -- Validate the changed behavior at the appropriate layers: unit behavior, both SDK client-construction paths, and selected emulator operations. Include relevant Extensions/AWS-mode regression coverage when dependency changes affect that factory. +- Validate the changed behavior at the appropriate layers: unit behavior, both SDK client-construction paths, and selected emulator operations. Include relevant Extensions/AWS-mode regression coverage when dependency changes affect that factory, and the separate Native AOT publish/run lane when changes affect its qualified paths; [quality gates](quality-gates.md) owns execution details. - Report actual commands, revision, frameworks, resolved dependencies, image/architecture/scenarios, outcomes, failures, and omissions. A default run that skips functional tests is not evidence that they passed. Neither badges nor fixed test counts substitute for a run record. - Select Core and Extensions versions independently in [Directory.Build.props](../../Directory.Build.props), accounting for the actual Extensions-to-Core dependency and change scope. A focused Extensions repair need not include a catalog or image refresh. +- Extensions calls Core option members such as `ApplyConfiguration` through [`InternalsVisibleTo`](../../src/LocalStack.Client/Properties/AssemblyInfo.cs). Those internal members keep their signatures within a major version; a breaking change requires a new major of **both packages released together**. The Extensions-to-Core package dependency remains a minimum version; do not change it to a bounded range. - Update [CHANGELOG.md](../../CHANGELOG.md) for the release's actual changes. If consumer guidance changes, edit the [root README](../../README.md) and keep [Core's README](../../src/LocalStack.Client/README.md) and [Extensions' README](../../src/LocalStack.Client.Extensions/README.md) byte-identical, following [platform and packaging](../architecture/platform-and-packaging.md). - Check the actual [Core feed](https://www.nuget.org/packages/LocalStack.Client) and [Extensions feed](https://www.nuget.org/packages/LocalStack.Client.Extensions) before claiming a version is published or asking consumers to install it. Declared versions, tags, workflow configuration, and successful tests do not establish upload success. Preserve stable versus prerelease/development channel distinctions. @@ -95,4 +96,4 @@ These are sampled accepted commits, not an exhaustive audit or proof of local ed | [05ffff1795f92171b744bcea63fd0b3d2de14848](https://github.com/localstack-dotnet/localstack-dotnet-client/commit/05ffff1795f92171b744bcea63fd0b3d2de14848) | A 3.8.0 message/fixture label accompanies a 3.7.1 image constant: owning inputs matter. | | [f539bf439cfccbff6992643c13d042da085a54b5](https://github.com/localstack-dotnet/localstack-dotnet-client/commit/f539bf439cfccbff6992643c13d042da085a54b5) | Extensions-only repair release preparation with Core's version unchanged. | -Native AOT friendliness remains a separate candidate priority for discussion. It is not a delivered capability or a mandatory addition to every routine release. Consult [platform and packaging](../architecture/platform-and-packaging.md) and its linked projects for the actual target frameworks and Native AOT boundary; the target list does not by itself establish package publication. +The unreleased 3.0.0 implementation includes Native AOT support on `net10.0`/`net11.0`, qualified by the separate native test lane. Consult [platform and packaging](../architecture/platform-and-packaging.md) and its linked projects for the exact boundary, legacy JIT assets, and SDK pin; a target list or successful build does not establish native execution or package publication. diff --git a/docs/engineering/quality-gates.md b/docs/engineering/quality-gates.md index e550adf..6b5b8ed 100644 --- a/docs/engineering/quality-gates.md +++ b/docs/engineering/quality-gates.md @@ -9,10 +9,13 @@ The project uses xUnit tests and Cake Frosting build tasks. A command's success | Core and Extensions unit tests | Configuration, session and DI behavior | No LocalStack container | | Integration tests | AWS SDK client construction and configuration across many services | No LocalStack API calls in the broad client-creation checks | | Functional tests | Selected S3, DynamoDB, SQS, SNS, and CloudFormation operations | Testcontainers using two LocalStack images pinned in [`TestConstants`](../../tests/LocalStack.Client.Functional.Tests/TestConstants.cs): the last release that starts without an auth token, and a current release that needs one ([image policy](maintenance-playbook.md#3-select-the-image-and-authentication-matrix)) | +| Native AOT tests | `AddLocalStack` + `AddAwsService`, all eight registration overloads, Core generic/`Type` paths and `SessionStandalone` | [xUnit v3 AOT executable](../../tests/LocalStack.Client.NativeAot.Tests/), `net10.0`/`net11.0`; live S3/SQS via `LOCALSTACK_NATIVE_AOT_HOST` | ## Before running a task - The Cake default target depends on `tests`, and `BuildContext.SkipFunctionalTest` defaults to `true`. The usual build/test scripts therefore **do not run the functional suite** unless explicitly opted in; do not label an unqualified run “all tests.” +- With the .NET 11 SDK, VSTest runs `net472` tests on Windows only; [Cake](../../build/LocalStack.Build/CakeTasks/TestTask.cs) warns and skips them elsewhere. CI no longer installs macOS Mono. +- Cake skips test projects that set `LocalStackTestRunsAsPublishedExecutable` (the Native AOT lane and the package consumer); `dotnet test` does not run them. Use CI's `dotnet publish` (`PublishAot`), then execute the published binary. Cake reads target frameworks through MSBuild, so the shared properties in `Directory.Build.props` apply to it too. - Functional tests start LocalStack containers through Testcontainers, so they need a reachable Docker engine. [`TestContainers`](../../tests/LocalStack.Client.Functional.Tests/TestContainers.cs) gives each container a unique name and enables Testcontainers cleanup, so its resource reaper removes the containers a run started. Neither the Cake path nor a direct test run removes other containers. - The current-image scenario classes carry the xUnit trait `LocalStackAuth=Required`. Their collection fixture reads `LOCALSTACK_AUTH_TOKEN` from the environment and passes it to the container. When the variable is missing or blank, the fixture throws a message that names the variable, so those tests fail; they never pass without a token. To run without a token, exclude them: `--filter "LocalStackAuth!=Required"` on `dotnet test`, or `--skipAuthenticatedFunctionalTest true` on the Cake `tests` target, which applies the same filter and prints a warning. Report excluded tests as not run. - Set the token only in the environment of the run. Never put it in a tracked file, a command-line argument, or a log. @@ -26,4 +29,6 @@ The [main CI workflow](../../.github/workflows/ci-cd.yml) ignores Markdown-only In the main CI workflow only the Linux leg runs functional tests. Its test step receives the Actions secret `LOCALSTACK_AUTH_TOKEN`, an organization secret shared with this repository. A pull-request run that cannot read that secret (a fork, a Dependabot run without its own copy, or any run before the secret exists) excludes the token-gated tests through the Cake flag and writes a workflow warning. A push run without the secret still runs them, so it fails at the fixture instead of passing. LocalStack expects a workspace CI Auth Token in CI, not a developer's personal token. +CI's separate `native-aot-tests` job publishes/runs both targets on `windows-latest` (`win-x64`) and `ubuntu-22.04` (`linux-x64`). Linux waits for LocalStack 4.14.0 health and sets `LOCALSTACK_NATIVE_AOT_HOST`; live cases otherwise skip. The [project](../../tests/LocalStack.Client.NativeAot.Tests/LocalStack.Client.NativeAot.Tests.csproj) and pinned xUnit v3 AOT package define the runner; this lane qualifies the [Native AOT boundary](../architecture/platform-and-packaging.md#native-aot-and-trimming), not every service/deployment. + The badge-update step is `continue-on-error`, and its action logs a non-200 Gist response without turning that response into a failed step. Neither a badge nor a fixed test count in prose proves that the current revision passed. Report the actual command, target frameworks, result, and any tests deliberately not run. Do not infer NuGet publication, Native AOT support, or full LocalStack service behavior from a successful build or client-construction test. diff --git a/tests/sandboxes/LocalStack.Client.Sandbox/Program.cs b/tests/sandboxes/LocalStack.Client.Sandbox/Program.cs index ca34052..f284707 100644 --- a/tests/sandboxes/LocalStack.Client.Sandbox/Program.cs +++ b/tests/sandboxes/LocalStack.Client.Sandbox/Program.cs @@ -12,7 +12,7 @@ /* * ==== Default Values ==== - * LocalStackHost: localhost + * LocalStackHost: localhost.localstack.cloud * UseSsl: false * UseLegacyPorts: false (Set true if your LocalStack version is 0.11.4 or below) * EdgePort: 4566 (It doesn't matter if use legacy ports)