-
Notifications
You must be signed in to change notification settings - Fork 0
Submodule Integration
This guide covers three integration paths:
- Git submodule + ProjectReference - consume library source directly during development
- NuGet packages - stable production reference (see README for package names)
-
Local
flagctlfrom source - build and wire the validation CLI from the submodule
| Requirement | Version |
|---|---|
| .NET SDK | 10.0.204 or later (see note on global.json) |
| Git | any |
| PowerShell | 7+ (for build.ps1) |
git submodule add https://github.com/sharpninja/FeatureFlags.git submodules/FeatureFlags
git submodule update --init --recursiveOr, to pin to the v1.0.0 release:
git submodule add --branch v1.0.0 https://github.com/sharpninja/FeatureFlags.git submodules/FeatureFlagsgit submodule update --remote submodules/FeatureFlags
git add submodules/FeatureFlags
git commit -m "chore: update FeatureFlags submodule"git clone --recurse-submodules https://your-repo.git
# or, after a plain clone:
git submodule update --init --recursiveAdd only the projects your application needs. The minimal set for an application that evaluates flags and uses the Build integration:
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Abstractions/SharpNinja.FeatureFlags.Abstractions.csproj
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Evaluation/SharpNinja.FeatureFlags.Evaluation.csproj
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Manifest/SharpNinja.FeatureFlags.Manifest.csproj
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags/SharpNinja.FeatureFlags.csproj
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Build/SharpNinja.FeatureFlags.Build.csprojFor server-side hosting, also add:
# Admin service (authoring plane)
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Admin/SharpNinja.FeatureFlags.Admin.csproj
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Admin.Data/SharpNinja.FeatureFlags.Admin.Data.csproj
# Choose one data provider:
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Admin.Data.Postgres/SharpNinja.FeatureFlags.Admin.Data.Postgres.csproj
# OR
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Admin.Data.SqlServer/SharpNinja.FeatureFlags.Admin.Data.SqlServer.csproj
# Distribution service
dotnet sln add submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Distribution/SharpNinja.FeatureFlags.Distribution.csproj<!-- YourApp.csproj -->
<ItemGroup>
<!-- Public contract types -->
<ProjectReference Include="..\submodules\FeatureFlags\src\SharpNinja.FeatureFlags.Abstractions\SharpNinja.FeatureFlags.Abstractions.csproj" />
<!-- Runtime SDK (evaluation, caching, refresh, exposure) -->
<ProjectReference Include="..\submodules\FeatureFlags\src\SharpNinja.FeatureFlags\SharpNinja.FeatureFlags.csproj" />
<!-- Build integration - see section 2.3 for the required extra step -->
<ProjectReference Include="..\submodules\FeatureFlags\src\SharpNinja.FeatureFlags.Build\SharpNinja.FeatureFlags.Build.csproj" />
</ItemGroup>
<PropertyGroup>
<!-- Required when a manifest file is present -->
<ProductId>truckmate</ProductId>
<ReleaseId>truckmate-1.2.0-stable-0</ReleaseId>
</PropertyGroup>Important: The MSBuild
buildTransitivemechanism only auto-imports targets when the project is consumed as a NuGet package. When usingProjectReference, you must import the targets file explicitly.
Add this to your app project file (or to a shared Directory.Build.targets in your repo root):
<!-- YourApp.csproj or Directory.Build.targets -->
<Import Project="..\submodules\FeatureFlags\src\SharpNinja.FeatureFlags.Build\buildTransitive\SharpNinja.FeatureFlags.Build.targets"
Condition="Exists('..\submodules\FeatureFlags\src\SharpNinja.FeatureFlags.Build\buildTransitive\SharpNinja.FeatureFlags.Build.targets')" />Adjust the relative path to match where your .csproj lives relative to the submodule root.
If you have multiple projects that consume the Build integration, put the <Import> in Directory.Build.targets once:
<!-- Directory.Build.targets at your repo root -->
<Project>
<Import Project="$(MSBuildThisFileDirectory)submodules\FeatureFlags\src\SharpNinja.FeatureFlags.Build\buildTransitive\SharpNinja.FeatureFlags.Build.targets"
Condition="Exists('$(MSBuildThisFileDirectory)submodules\FeatureFlags\src\SharpNinja.FeatureFlags.Build\buildTransitive\SharpNinja.FeatureFlags.Build.targets')
And '$(SharpNinjaFeatureFlagsManifest)' != ''" />
</Project>The SharpNinjaFeatureFlagsManifest condition prevents the targets from activating on projects that have no manifest.
YourApp
SharpNinja.FeatureFlags <- SDK entry point
SharpNinja.FeatureFlags.Abstractions
SharpNinja.FeatureFlags.Evaluation
SharpNinja.FeatureFlags.Manifest <- signing verification
SharpNinja.FeatureFlags.Abstractions
SharpNinja.FeatureFlags.Evaluation
SharpNinja.FeatureFlags.Build <- MSBuild integration (build-time only)
SharpNinja.FeatureFlags.Abstractions
You do not need to reference Evaluation or Manifest directly; they are transitive through SharpNinja.FeatureFlags.
FeatureFlags uses ManagePackageVersionsCentrally=true with its own Directory.Packages.props. If your repo also uses central package management, MSBuild will merge both files, which can cause version conflicts.
Option A - Isolate with a nested Directory.Build.props
Create submodules/FeatureFlags/Directory.Build.props to shadow the root:
<!-- submodules/FeatureFlags/Directory.Build.props -->
<Project>
<!-- Stops your repo's root Directory.Build.props from reaching into the submodule -->
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>Git will show this file as an untracked change inside the submodule. Add it to your outer repo's
.gitignorepattern or commit it to your repo's tracked state via a patch/overlay approach.
Option B - Merge package versions
Copy the package versions from submodules/FeatureFlags/Directory.Packages.props into your own Directory.Packages.props. When the submodule updates, re-sync versions. This is more maintenance work but gives full control.
Option C - Disable central versioning for the submodule projects
In submodules/FeatureFlags/Directory.Build.props (same shadow file as Option A):
<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>This allows the submodule's .csproj files to carry their own versions. Requires the submodule projects to have Version attributes on all PackageReference items (they currently do not - they rely on central versioning). Not recommended unless you are patching the submodule.
FeatureFlags pins .NET SDK 10.0.204 with rollForward: latestFeature. If your repo has its own global.json, the SDK resolution uses the file closest to the project being built.
- Your root
global.jsongoverns your projects. -
submodules/FeatureFlags/global.jsongoverns FeatureFlags projects when built from within the submodule directory. - When both are in the same solution, the SDK selected is the one from your repo root. Ensure it satisfies
>= 10.0.204.
FeatureFlags sets TreatWarningsAsErrors=true globally via its Directory.Build.props. This propagates to all projects in the solution when MSBuild traverses upward.
To prevent this from affecting your own projects, reset it in your Directory.Build.props:
<!-- Your repo's Directory.Build.props -->
<Project>
<!-- Import FeatureFlags props first if you want to inherit other settings -->
<Import Project="submodules\FeatureFlags\Directory.Build.props" Condition="Exists('submodules\FeatureFlags\Directory.Build.props')" />
<PropertyGroup>
<!-- Re-apply your repo's preference, overriding the submodule's setting -->
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
</Project>flagctl is the manifest validation CLI. It is a plain .NET console exe (not a dotnet tool package). Installing it from source means building the Cli project and making the output available on PATH or pointing the MSBuild property at it.
# From your repo root
dotnet build submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Cli/SharpNinja.FeatureFlags.Cli.csproj `
--configuration Release `
--output submodules/FeatureFlags/tools/flagctlThe output directory will contain flagctl.exe (Windows) or flagctl (Linux/macOS).
dotnet publish submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Cli/SharpNinja.FeatureFlags.Cli.csproj `
--configuration Release `
--runtime win-x64 `
--self-contained true `
-p:PublishSingleFile=true `
--output tools/flagctlReplace win-x64 with linux-x64 or osx-x64 as appropriate.
Windows PowerShell:
$env:PATH += ";$PWD\tools\flagctl"Linux/macOS:
export PATH="$PATH:$(pwd)/tools/flagctl"To make this permanent, add the directory to your system or user PATH environment variable.
Set SharpNinjaFeatureFlagsCliCommand to the full path so the Build target can invoke it without requiring it to be on PATH:
<!-- YourApp.csproj or Directory.Build.props -->
<PropertyGroup>
<SharpNinjaFeatureFlagsCliCommand>$(MSBuildThisFileDirectory)..\tools\flagctl\flagctl</SharpNinjaFeatureFlagsCliCommand>
</PropertyGroup>Windows with .exe suffix:
<SharpNinjaFeatureFlagsCliCommand>$(MSBuildThisFileDirectory)..\tools\flagctl\flagctl.exe</SharpNinjaFeatureFlagsCliCommand>Or compute it conditionally:
<PropertyGroup>
<_FlagctlName Condition="$([MSBuild]::IsOSPlatform('Windows'))">flagctl.exe</_FlagctlName>
<_FlagctlName Condition="!$([MSBuild]::IsOSPlatform('Windows'))">flagctl</_FlagctlName>
<SharpNinjaFeatureFlagsCliCommand>$(MSBuildThisFileDirectory)..\tools\flagctl\$(_FlagctlName)</SharpNinjaFeatureFlagsCliCommand>
</PropertyGroup># Validate a manifest
flagctl validate flags/flags.json \
--product-id truckmate \
--release-id truckmate-1.2.0-stable-0 \
--public-key flags/public-key.ed25519
# Show help
flagctl --help
flagctl validate --helpExit codes: 0 = valid, 1 = validation errors (stderr lists them), 2 = usage error or file not found.
Add a build step before your first compile:
# build.ps1 or equivalent
$flagctlProject = "submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Cli/SharpNinja.FeatureFlags.Cli.csproj"
$flagctlOutput = "tools/flagctl"
dotnet publish $flagctlProject `
--configuration Release `
--output $flagctlOutput `
--nologo
$env:PATH += ";$PWD/$flagctlOutput"If your CI builds without flagctl available and you want to validate separately, disable the validation target:
<PropertyGroup>
<SharpNinjaFeatureFlagsValidateOnBuild>false</SharpNinjaFeatureFlagsValidateOnBuild>
</PropertyGroup>Then run validation as a dedicated CI step:
flagctl validate flags/flags.json \
--product-id $(ProductId) \
--release-id $(ReleaseId) \
--public-key flags/public-key.ed25519YourRepo/
.gitmodules
Directory.Build.props <- merge/override submodule settings
Directory.Build.targets <- Import SharpNinja.FeatureFlags.Build.targets
Directory.Packages.props <- merged or isolated package versions
global.json <- SDK >= 10.0.204
tools/
flagctl/
flagctl.exe <- built from source (gitignored)
submodules/
FeatureFlags/ <- git submodule
src/
...
src/
YourApp/
YourApp.csproj
flags/
flags.json <- signed manifest
public-key.ed25519 <- Ed25519 public key
YourRepo.sln
# Built flagctl binary
tools/flagctl/
# Shadow files you create inside the submodule
submodules/FeatureFlags/Directory.Build.props<Project>
<PropertyGroup>
<!-- SDK identity for all apps in this repo -->
<ProductId>truckmate</ProductId>
<ReleaseId>truckmate-1.2.0-stable-0</ReleaseId>
<!-- Point to the locally built flagctl -->
<_FlagctlExe Condition="$([MSBuild]::IsOSPlatform('Windows'))">flagctl.exe</_FlagctlExe>
<_FlagctlExe Condition="!$([MSBuild]::IsOSPlatform('Windows'))">flagctl</_FlagctlExe>
<SharpNinjaFeatureFlagsCliCommand>$(MSBuildThisFileDirectory)tools\flagctl\$(_FlagctlExe)</SharpNinjaFeatureFlagsCliCommand>
</PropertyGroup>
</Project><Project>
<Import Project="$(MSBuildThisFileDirectory)submodules\FeatureFlags\src\SharpNinja.FeatureFlags.Build\buildTransitive\SharpNinja.FeatureFlags.Build.targets"
Condition="Exists('$(MSBuildThisFileDirectory)submodules\FeatureFlags\src\SharpNinja.FeatureFlags.Build\buildTransitive\SharpNinja.FeatureFlags.Build.targets')
And '$(SharpNinjaFeatureFlagsManifest)' != ''" />
</Project>| Task | Command |
|---|---|
| Add submodule | git submodule add https://github.com/sharpninja/FeatureFlags.git submodules/FeatureFlags |
| Init after clone | git submodule update --init --recursive |
| Update submodule | git submodule update --remote submodules/FeatureFlags |
| Build flagctl | dotnet publish submodules/FeatureFlags/src/SharpNinja.FeatureFlags.Cli/... -o tools/flagctl |
| Validate manifest | flagctl validate flags/flags.json --product-id <id> --release-id <id> |
| Disable build validation | Set <SharpNinjaFeatureFlagsValidateOnBuild>false</SharpNinjaFeatureFlagsValidateOnBuild>
|
| Skip public key check | Set <SharpNinjaFeatureFlagsRequirePublicKey>false</SharpNinjaFeatureFlagsRequirePublicKey>
|