Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
241 changes: 241 additions & 0 deletions .github/workflows/aot-compatibility.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
name: AOT Compatibility

on:
push:
branches: [main, askpt/440-feature-investigate-nativeaot]
pull_request:
branches: [main]
merge_group:
workflow_dispatch:

jobs:
aot-compatibility:
name: AOT Test (${{ matrix.os }}, ${{ matrix.arch }})
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
submodules: recursive

- name: Setup .NET SDK
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
env:
NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
global-json-file: global.json
source-url: https://nuget.pkg.github.com/open-feature/index.json

- name: Cache NuGet packages
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
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
run: dotnet restore

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

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

- name: Publish AOT compatibility test (cross-platform)
if: matrix.arch != 'arm64' || runner.arch == 'ARM64'
run: |
dotnet publish test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj \
-c Release \
-r ${{ matrix.runtime }} \
--self-contained true \
-p:PublishAot=true \
-p:IsAotCompatible=true \
-p:InvariantGlobalization=true \
-p:TrimMode=full \
-o ./aot-output

- name: Run AOT compatibility test
if: matrix.arch != 'arm64' || runner.arch == 'ARM64'
shell: bash
run: |
if [[ "${{ runner.os }}" == "Windows" ]]; then
./aot-output/OpenFeature.AotCompatibility.exe
else
chmod +x ./aot-output/OpenFeature.AotCompatibility
./aot-output/OpenFeature.AotCompatibility
fi

# For ARM64 cross-compilation on non-ARM64 runners, we only test the build
- name: Test AOT cross-compilation build only (ARM64)
if: matrix.arch == 'arm64' && runner.arch != 'ARM64'
run: |
dotnet publish test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj \
-c Release \
-r ${{ matrix.runtime }} \
--self-contained true \
-p:PublishAot=true \
-p:IsAotCompatible=true \
-p:InvariantGlobalization=true \
-p:TrimMode=full \
-o ./aot-output-cross

- name: Test AspNetCore sample AOT compilation
if: matrix.arch != 'arm64' || runner.arch == 'ARM64'
run: |
dotnet publish samples/AspNetCore/Samples.AspNetCore.csproj \
-c Release \
-r ${{ matrix.runtime }} \
--self-contained true \
-p:PublishAot=true \
-o ./aspnetcore-aot-output

- name: Verify AOT output size and characteristics
if: matrix.arch != 'arm64' || runner.arch == 'ARM64'
shell: bash
run: |
echo "=== AOT Compatibility Test Binary ==="
if [[ "${{ runner.os }}" == "Windows" ]]; then
ls -la ./aot-output/OpenFeature.AotCompatibility.exe
echo "Binary size: $(stat -c %s ./aot-output/OpenFeature.AotCompatibility.exe) bytes"
else
ls -la ./aot-output/OpenFeature.AotCompatibility
echo "Binary size: $(stat -c %s ./aot-output/OpenFeature.AotCompatibility) bytes"
fi

echo ""
echo "=== AspNetCore Sample Binary ==="
if [[ "${{ runner.os }}" == "Windows" ]]; then
ls -la ./aspnetcore-aot-output/Samples.AspNetCore.exe
echo "Binary size: $(stat -c %s ./aspnetcore-aot-output/Samples.AspNetCore.exe) bytes"
else
ls -la ./aspnetcore-aot-output/Samples.AspNetCore
echo "Binary size: $(stat -c %s ./aspnetcore-aot-output/Samples.AspNetCore) bytes"
fi

- name: Upload AOT artifacts
if: always() && (matrix.arch != 'arm64' || runner.arch == 'ARM64')
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
with:
name: aot-binaries-${{ matrix.os }}-${{ matrix.arch }}
path: |
aot-output/
aspnetcore-aot-output/
retention-days: 7

aot-size-comparison:
name: AOT Size Comparison
needs: aot-compatibility
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'

steps:
- name: Download all AOT artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
pattern: aot-binaries-*
path: ./artifacts

- name: Generate size comparison report
shell: bash
run: |
echo "# AOT Binary Size Report" > size_report.md
echo "" >> size_report.md
echo "| Platform | Architecture | AOT Test Binary | AspNetCore Sample |" >> size_report.md
echo "|----------|--------------|-----------------|-------------------|" >> size_report.md

for dir in ./artifacts/aot-binaries-*; do
if [[ -d "$dir" ]]; then
platform=$(basename "$dir" | sed 's/aot-binaries-//' | sed 's/-[^-]*$//')
arch=$(basename "$dir" | sed 's/.*-//')

# Find the AOT test binary
if [[ -f "$dir/aot-output/OpenFeature.AotCompatibility.exe" ]]; then
aot_size=$(stat -c %s "$dir/aot-output/OpenFeature.AotCompatibility.exe")
elif [[ -f "$dir/aot-output/OpenFeature.AotCompatibility" ]]; then
aot_size=$(stat -c %s "$dir/aot-output/OpenFeature.AotCompatibility")
else
aot_size="N/A"
fi

# Find the AspNetCore sample binary
if [[ -f "$dir/aspnetcore-aot-output/Samples.AspNetCore.exe" ]]; then
aspnet_size=$(stat -c %s "$dir/aspnetcore-aot-output/Samples.AspNetCore.exe")
elif [[ -f "$dir/aspnetcore-aot-output/Samples.AspNetCore" ]]; then
aspnet_size=$(stat -c %s "$dir/aspnetcore-aot-output/Samples.AspNetCore")
else
aspnet_size="N/A"
fi

# Format sizes
if [[ "$aot_size" != "N/A" ]]; then
aot_size_mb=$(echo "scale=2; $aot_size / 1048576" | bc)
aot_display="${aot_size_mb}MB"
else
aot_display="N/A"
fi

if [[ "$aspnet_size" != "N/A" ]]; then
aspnet_size_mb=$(echo "scale=2; $aspnet_size / 1048576" | bc)
aspnet_display="${aspnet_size_mb}MB"
else
aspnet_display="N/A"
fi

echo "| $platform | $arch | $aot_display | $aspnet_display |" >> size_report.md
fi
done

echo "" >> size_report.md
echo "Generated on: $(date)" >> size_report.md

cat size_report.md

- name: Find PR Comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: 'AOT Binary Size Report'

- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: size_report.md
edit-mode: replace
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>
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="'$(TargetFramework)' == 'net8.0' or '$(TargetFramework)' == 'net9.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>
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,
}
27 changes: 23 additions & 4 deletions src/OpenFeature/Extension/EnumExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
using System.ComponentModel;
using OpenFeature.Constant;

namespace OpenFeature.Extension;

internal static class EnumExtensions
{
/// <summary>
/// Gets the description of an enum value without using reflection.
/// This is AOT-compatible and only supports specific known enum types.
/// </summary>
/// <param name="value">The enum value to get the description for</param>
/// <returns>The description string or the enum value as string if no description is available</returns>
public static string GetDescription(this Enum value)
{
var field = value.GetType().GetField(value.ToString());
var attribute = field?.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() as DescriptionAttribute;
return attribute?.Description ?? value.ToString();
return value switch
{
// ErrorType descriptions
ErrorType.None => "NONE",
ErrorType.ProviderNotReady => "PROVIDER_NOT_READY",
ErrorType.FlagNotFound => "FLAG_NOT_FOUND",
ErrorType.ParseError => "PARSE_ERROR",
ErrorType.TypeMismatch => "TYPE_MISMATCH",
ErrorType.General => "GENERAL",
ErrorType.InvalidContext => "INVALID_CONTEXT",
ErrorType.TargetingKeyMissing => "TARGETING_KEY_MISSING",
ErrorType.ProviderFatal => "PROVIDER_FATAL",

// Fallback for any other enum types
_ => value.ToString()
};
}
}
Loading
Loading