Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c6c700a
feat: enhance ValueJsonConverter for AOT compatibility with manual JS…
askpt Aug 15, 2025
bdb394b
feat: refactor EnumExtensions to improve AOT compatibility and remove…
askpt Aug 15, 2025
c41cab7
feat: add OpenFeatureJsonSerializerContext for AOT compilation support
askpt Aug 15, 2025
56608c0
feat: add AOT and trimming support for net8.0 and net9.0
askpt Aug 15, 2025
ed39652
feat: add NativeAOT compatibility tests and project configuration
askpt Aug 15, 2025
6f5390a
feat: update project structure for AOT compatibility and add MultiPro…
askpt Aug 15, 2025
5c794e4
fix: remove unnecessary Type attribute project in solution file
askpt Aug 15, 2025
5cd179e
feat: add unit tests for EnumExtensions.GetDescription method
askpt Aug 15, 2025
1c54a7f
fix: remove trimming support properties for net8.0 and net9.0
askpt Aug 15, 2025
f62a88d
feat: add AOT compatibility workflow with cross-platform testing and …
askpt Aug 15, 2025
f8f90fc
fix: simplify AOT compatibility workflow by removing unnecessary prop…
askpt Aug 15, 2025
df63171
fix: update AOT compatibility workflow to include runtime in publish …
askpt Aug 16, 2025
ea053a3
fix: update AOT compatibility workflow to streamline ARM64 handling a…
askpt Aug 16, 2025
4cc832e
fix: standardize shell usage and update publish command syntax in AOT…
askpt Aug 16, 2025
f462b4e
fix: update AOT size comparison report to remove AspNetCore sample co…
askpt Aug 16, 2025
71c2bc4
fix: remove AOT size comparison job and artifact upload steps from wo…
askpt Aug 18, 2025
2895325
fix: update AOT compatibility workflow permissions and enhance docume…
askpt Aug 18, 2025
1086914
fix: streamline AOT compatibility documentation by removing redundant…
askpt Aug 18, 2025
f005f9e
fix: update actions/checkout and actions/cache versions in AOT compat…
askpt Aug 18, 2025
f36d20f
Apply suggestions from code review
askpt Aug 19, 2025
d93dd2d
Update .github/workflows/aot-compatibility.yml
askpt Aug 19, 2025
d05fa4e
fix: remove unnecessary properties from AOT project configuration
askpt Aug 19, 2025
5994f99
docs: update README to clarify NativeAOT compatibility for contrib an…
askpt Aug 19, 2025
14a881e
Apply suggestions from code review
askpt Aug 20, 2025
92339a6
fix: add descriptions to ErrorType enum values for better clarity
askpt Aug 22, 2025
67559c9
fix: remove AOT compatibility references and enhance error handling t…
askpt Aug 22, 2025
2cadd2a
fix: update System.Text.Json package reference in project files
askpt Aug 22, 2025
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
95 changes: 95 additions & 0 deletions .github/workflows/aot-compatibility.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: AOT Compatibility

on:
push:
branches: [main]
pull_request:
branches: [main]
merge_group:
workflow_dispatch:

jobs:
aot-compatibility:
name: AOT Test (${{ matrix.os }}, ${{ matrix.arch }})
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
# Linux x64
- os: ubuntu-latest
arch: x64
runtime: linux-x64
# Linux ARM64
- os: ubuntu-24.04-arm
arch: arm64
runtime: linux-arm64
# Windows x64
- os: windows-latest
arch: x64
runtime: win-x64
# Windows ARM64
- os: windows-11-arm
arch: arm64
runtime: win-arm64
# macOS x64
- os: macos-13
arch: x64
runtime: osx-x64
# macOS ARM64 (Apple Silicon)
- os: macos-latest
arch: arm64
runtime: osx-arm64

runs-on: ${{ matrix.os }}

steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
submodules: recursive

- name: Setup .NET SDK
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
global-json-file: global.json

- name: Cache NuGet packages
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-${{ matrix.arch }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-${{ matrix.arch }}-nuget-
${{ runner.os }}-nuget-

- name: Restore dependencies
shell: pwsh
run: dotnet restore

- name: Build solution
shell: pwsh
run: dotnet build -c Release --no-restore

- name: Test AOT compatibility project build
shell: pwsh
run: dotnet build test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj -c Release --no-restore

- name: Publish AOT compatibility test (cross-platform)
shell: pwsh
run: |
dotnet publish test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj `
-r ${{ matrix.runtime }} `
-o ./aot-output

- name: Run AOT compatibility test
shell: pwsh
run: |
if ("${{ runner.os }}" -eq "Windows") {
./aot-output/OpenFeature.AotCompatibility.exe
} else {
chmod +x ./aot-output/OpenFeature.AotCompatibility
./aot-output/OpenFeature.AotCompatibility
}
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="9.3.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsVersion)" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
Expand Down
7 changes: 4 additions & 3 deletions OpenFeature.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<Folder Name="/src/">
<Project Path="src/OpenFeature.DependencyInjection/OpenFeature.DependencyInjection.csproj" />
<Project Path="src/OpenFeature.Hosting/OpenFeature.Hosting.csproj" />
<Project Path="src/OpenFeature.Providers.MultiProvider/OpenFeature.Providers.MultiProvider.csproj" Type="Classic C#" />
<Project Path="src/OpenFeature.Providers.MultiProvider/OpenFeature.Providers.MultiProvider.csproj" />
<Project Path="src/OpenFeature/OpenFeature.csproj" />
<File Path="src/Directory.Build.props" />
<File Path="src/Directory.Build.targets" />
Expand All @@ -64,7 +64,8 @@
<Project Path="test/OpenFeature.E2ETests/OpenFeature.E2ETests.csproj" />
<Project Path="test/OpenFeature.IntegrationTests/OpenFeature.IntegrationTests.csproj" />
<Project Path="test/OpenFeature.Tests/OpenFeature.Tests.csproj" />
<Project Path="test\OpenFeature.Providers.MultiProvider.Tests\OpenFeature.Providers.MultiProvider.Tests.csproj" Type="Classic C#" />
<Project Path="test/OpenFeature.AotCompatibility\OpenFeature.AotCompatibility.csproj" />
<Project Path="test/OpenFeature.Providers.MultiProvider.Tests/OpenFeature.Providers.MultiProvider.Tests.csproj" />
<File Path="test/Directory.Build.props" />
</Folder>
</Solution>
</Solution>
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@

Note that the packages will aim to support all current .NET versions. Refer to the currently supported versions [.NET](https://dotnet.microsoft.com/download/dotnet) and [.NET Framework](https://dotnet.microsoft.com/download/dotnet-framework) excluding .NET Framework 3.5

### NativeAOT Support

✅ **Full NativeAOT Compatibility** - The OpenFeature .NET SDK is fully compatible with .NET NativeAOT compilation for fast startup and small deployment size. See the [AOT Compatibility Guide](docs/AOT_COMPATIBILITY.md) for detailed instructions.

### Install

Use the following to initialize your project:
Expand Down
9 changes: 7 additions & 2 deletions build/Common.prod.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<Import Project=".\Common.props" />
<Import Project=".\Common.props"/>

<PropertyGroup>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
Expand All @@ -24,8 +24,13 @@
<FileVersion>$(VersionNumber)</FileVersion>
</PropertyGroup>

<!-- AOT and Trimming Support -->
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)openfeature-icon.png" Pack="true" PackagePath="\" />
<None Include="$(MSBuildThisFileDirectory)openfeature-icon.png" Pack="true" PackagePath="\"/>
</ItemGroup>

</Project>
152 changes: 152 additions & 0 deletions docs/AOT_COMPATIBILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# OpenFeature .NET SDK - NativeAOT Compatibility

The OpenFeature .NET SDK is compatible with .NET NativeAOT compilation, allowing you to create self-contained, native executables with faster startup times and lower memory usage.

## Compatibility Status

**Fully Compatible** - The SDK can be used in NativeAOT applications without any issues.

### What's AOT-Compatible

- Core API functionality (`Api.Instance`, `GetClient()`, flag evaluations)
- All built-in providers (`NoOpProvider`, etc.)
- JSON serialization of `Value`, `Structure`, and `EvaluationContext`
- Error handling and enum descriptions
- Hook system
- Event handling
- Metrics collection
- Dependency injection

## Using OpenFeature with NativeAOT

### 1. Project Configuration

To enable NativeAOT in your project, add these properties to your `.csproj` file:

```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <!-- or net9.0 -->
<OutputType>Exe</OutputType>

<!-- Enable NativeAOT -->
<PublishAot>true</PublishAot>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenFeature" Version="2.x.x" />
</ItemGroup>
</Project>
```

### 2. Basic Usage

```csharp
using OpenFeature;
using OpenFeature.Model;

// Basic OpenFeature usage - fully AOT compatible
var api = Api.Instance;
var client = api.GetClient("my-app");

// All flag evaluation methods work
var boolFlag = await client.GetBooleanValueAsync("feature-enabled", false);
var stringFlag = await client.GetStringValueAsync("welcome-message", "Hello");
var intFlag = await client.GetIntegerValueAsync("max-items", 10);
```

### 3. JSON Serialization (Recommended)

For optimal AOT performance, use the provided `JsonSerializerContext`:

```csharp
using System.Text.Json;
using OpenFeature.Model;
using OpenFeature.Serialization;

var value = new Value(Structure.Builder()
.Set("name", "test")
.Set("enabled", true)
.Build());

// Use AOT-compatible serialization
var json = JsonSerializer.Serialize(value, OpenFeatureJsonSerializerContext.Default.Value);
var deserialized = JsonSerializer.Deserialize(json, OpenFeatureJsonSerializerContext.Default.Value);
```

### 4. Publishing for NativeAOT

Build and publish your AOT application:

```bash
# Build with AOT analysis
dotnet build -c Release

# Publish as native executable
dotnet publish -c Release

# Run the native executable (example path for macOS ARM64)
./bin/Release/net9.0/osx-arm64/publish/MyApp
```

## Performance Benefits

NativeAOT compilation provides several benefits:

- **Faster Startup**: Native executables start faster than JIT-compiled applications
- **Lower Memory Usage**: Reduced memory footprint
- **Self-Contained**: No .NET runtime dependency required
- **Smaller Deployment**: Optimized for size with trimming

## Testing AOT Compatibility

The SDK includes an AOT compatibility test project at `test/OpenFeature.AotCompatibility/` that:

- Tests all core SDK functionality
- Validates JSON serialization with source generation
- Verifies error handling works correctly
- Can be compiled and run as a native executable

Run the test:

```bash
cd test/OpenFeature.AotCompatibility
dotnet publish -c Release
./bin/Release/net9.0/[runtime]/publish/OpenFeature.AotCompatibility
```

## Limitations

Currently, there are no known limitations when using OpenFeature with NativeAOT. All core functionality is fully supported.

## Provider Compatibility

When using third-party providers, ensure they are also AOT-compatible. Check the provider's documentation for AOT support.

## Troubleshooting

### Trimming Warnings

If you encounter trimming warnings, you can:

1. Use the provided `JsonSerializerContext` for JSON operations
2. Ensure your providers are AOT-compatible
3. Add appropriate `[DynamicallyAccessedMembers]` attributes if needed

### Build Issues

- Ensure you're targeting .NET 8.0 or later
- Verify all dependencies support NativeAOT
- Check that `PublishAot` is set to `true`

## Migration Guide

If migrating from a non-AOT setup:

1. **JSON Serialization**: Replace direct `JsonSerializer` calls with the provided context
2. **Reflection**: The SDK no longer uses reflection, but ensure your custom code doesn't
3. **Dynamic Loading**: Avoid dynamic assembly loading; register providers at compile time

## Example AOT Application

See the complete example in `test/OpenFeature.AotCompatibility/Program.cs` for a working AOT application that demonstrates all SDK features.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<ItemGroup>
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
<InternalsVisibleTo Include="OpenFeature.Providers.MultiProvider.Tests" />
<InternalsVisibleTo Include="OpenFeature.AotCompatibility" />
<None Include="README.md" Pack="true" PackagePath="/" />
</ItemGroup>

Expand Down
18 changes: 8 additions & 10 deletions src/OpenFeature/Constant/ErrorType.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.ComponentModel;

namespace OpenFeature.Constant;

/// <summary>
Expand All @@ -16,40 +14,40 @@ public enum ErrorType
/// <summary>
/// Provider has yet been initialized
/// </summary>
[Description("PROVIDER_NOT_READY")] ProviderNotReady,
ProviderNotReady,

/// <summary>
/// Provider was unable to find the flag
/// </summary>
[Description("FLAG_NOT_FOUND")] FlagNotFound,
FlagNotFound,

/// <summary>
/// Provider failed to parse the flag response
/// </summary>
[Description("PARSE_ERROR")] ParseError,
ParseError,

/// <summary>
/// Request type does not match the expected type
/// </summary>
[Description("TYPE_MISMATCH")] TypeMismatch,
TypeMismatch,

/// <summary>
/// Abnormal execution of the provider
/// </summary>
[Description("GENERAL")] General,
General,

/// <summary>
/// Context does not satisfy provider requirements.
/// </summary>
[Description("INVALID_CONTEXT")] InvalidContext,
InvalidContext,

/// <summary>
/// Context does not contain a targeting key and the provider requires one.
/// </summary>
[Description("TARGETING_KEY_MISSING")] TargetingKeyMissing,
TargetingKeyMissing,

/// <summary>
/// The provider has entered an irrecoverable error state.
/// </summary>
[Description("PROVIDER_FATAL")] ProviderFatal,
ProviderFatal,
}
Loading
Loading