Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` and every overload, `CreateClientByInterface<T>()`) 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<T>` and every overload, `CreateClientByInterface<T>()`) 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

Expand All @@ -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`.
Expand Down
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<Owners>LocalStack.NET</Owners>
<PackageProjectUrl>https://github.com/localstack-dotnet/localstack-dotnet-client</PackageProjectUrl>
<PackageIcon>localstack-dotnet-square.png</PackageIcon>
<PackageMainVersion>2.0.0</PackageMainVersion>
<PackageExtensionVersion>2.0.1</PackageExtensionVersion>
<PackageMainVersion>3.0.0</PackageMainVersion>
<PackageExtensionVersion>3.0.0</PackageExtensionVersion>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<LangVersion>13.0</LangVersion>
Expand Down
2 changes: 1 addition & 1 deletion GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 3 additions & 3 deletions docs/adr/0001-aws-sdk-v4-and-v1-maintenance.md
Original file line number Diff line number Diff line change
@@ -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).
6 changes: 3 additions & 3 deletions docs/adr/0004-native-aot-preserves-existing-consumer-code.md
Original file line number Diff line number Diff line change
@@ -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<IAmazonS3>()`, 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<IAmazonS3>()`, 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.
12 changes: 7 additions & 5 deletions docs/architecture/client-runtime.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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<TClient>()` 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.
Loading
Loading