Build orchestration SDK for PostSharp and Metalama repositories.
PostSharp.Engineering is an internal build orchestration framework designed for complex multi-repository .NET projects. It provides:
- Unified build workflow via
Build.ps1front-end script - Dependency management across repositories (local, feed, and build server sources)
- CI/CD integration with TeamCity project and build configuration generation
- Multi-target publishing to NuGet feeds, VSIX marketplace, AWS S3, IIS, and more
- Version management with automatic bumping and tagging
- Docker support for containerized builds
While released under an open-source license (as a dependency of Metalama), this SDK is an ad-hoc solution for PostSharp/Metalama projects, not a general-purpose build framework.
This repository produces three NuGet packages:
| Package | Purpose |
|---|---|
| PostSharp.Engineering.BuildTools | Core build SDK, added as a PackageReference from the eng/src build project |
| PostSharp.Engineering.Sdk | MSBuild SDK with .props and .targets files for project configuration |
| PostSharp.Engineering.DocFx | DocFx documentation generation extensions |
Both BuildTools and Sdk packages must be used together with matching versions.
Version management is centralized in the eng/ directory. All solutions and projects in a product share the same version.
| File | Purpose |
|---|---|
eng/MainVersion.props |
Defines the product version (required) |
eng/Versions.props |
Defines dependency versions and imports MainVersion.props |
eng/Versions.g.props |
Auto-generated overrides for local/build-server dependencies |
eng/Dependencies.props |
Manual dependency source overrides |
| Property | Description | Example |
|---|---|---|
MainVersion |
The main version number (3 components: major.minor.patch) | 2023.2.279 |
PackageVersionSuffix |
Version suffix for pre-release packages (empty for RTM) | -preview, -rc |
OverriddenPatchVersion |
Optional 4-component version for patch releases | 2023.2.279.1 |
OurPatchVersion |
Patch counter when using OverriddenPatchVersion |
1 |
The final package version is computed as:
- Standard:
{MainVersion}{PackageVersionSuffix}(e.g.,2023.2.279-preview) - RTM:
{MainVersion}with empty suffix (e.g.,2023.2.279) - Patch:
{OverriddenPatchVersion}{PackageVersionSuffix}(e.g.,2023.2.279.1)
The OverriddenPatchVersion property enables releasing patches of repo B without releasing a new build of dependent repo A:
<Project>
<PropertyGroup>
<MainVersion>2023.2.279</MainVersion>
<PackageVersionSuffix></PackageVersionSuffix>
<OverriddenPatchVersion>2023.2.279.1</OverriddenPatchVersion>
<OurPatchVersion>1</OurPatchVersion>
</PropertyGroup>
</Project>The OverriddenPatchVersion must start with the MainVersion value.
<Project>
<!-- Import the main version -->
<Import Project="MainVersion.props" />
<!-- Define product version properties -->
<PropertyGroup>
<MyProductVersion>$(MainVersion)$(PackageVersionSuffix)</MyProductVersion>
<MyProductAssemblyVersion>$(MainVersion)</MyProductAssemblyVersion>
</PropertyGroup>
<!-- Dependency versions -->
<PropertyGroup>
<SomeDependencyVersion>1.2.3</SomeDependencyVersion>
</PropertyGroup>
<!-- Import auto-generated overrides (for local/build-server dependencies) -->
<Import Project="Versions.g.props" Condition="Exists('Versions.g.props')" />
<!-- Import manual overrides -->
<Import Project="Dependencies.props" Condition="Exists('Dependencies.props')" />
<!-- Set MSBuild version properties -->
<PropertyGroup>
<AssemblyVersion>$(MyProductAssemblyVersion)</AssemblyVersion>
<Version>$(MyProductVersion)</Version>
</PropertyGroup>
</Project>The eng/AutoUpdatedVersions.props file tracks released versions of dependencies and the current product. It is automatically updated during publishing and version bumping.
<Project>
<PropertyGroup>
<!-- Released versions of dependencies (auto-updated from their repos) -->
<MetalamaCompilerVersion>2026.0.1</MetalamaCompilerVersion>
<MetalamaVersion>2026.0.15</MetalamaVersion>
<!-- This product's released version (updated after publishing) -->
<MyProductReleaseVersion>2026.0.5</MyProductReleaseVersion>
<MyProductReleaseMainVersion>2026.0.5</MyProductReleaseMainVersion>
</PropertyGroup>
</Project>This file serves two purposes:
- Dependency tracking: Stores the released versions of dependencies with
AutoUpdateVersion = true - Release tracking: Records this product's last released version (
{ProductName}ReleaseVersionand{ProductName}ReleaseMainVersion)
The DependencyDefinition.AutoUpdateVersion property (default: true) controls whether a dependency's version is automatically updated in AutoUpdatedVersions.props during bumping:
public static DependencyDefinition PostSharpEngineering { get; } = new(...)
{
// Set to false for dependencies that should not trigger auto-updates
AutoUpdateVersion = false
};Dependencies with AutoUpdateVersion = false (like PostSharp.Engineering itself) are excluded from automatic version propagation.
Products can inherit their version from a parent dependency using MainVersionDependency:
var product = new Product(...)
{
// This product's version comes from Metalama's version
MainVersionDependency = MetalamaDependencies.V2026_0.Metalama
};When set, the product doesn't maintain its own MainVersion but inherits from the specified dependency's released version.
The bump command increments the product version after a successful deployment:
Build.ps1 bump # Bump version (only on development branch)
Build.ps1 bump --force # Bump even on non-development branches
Build.ps1 bump --override # Ignore previous bump commitsBump workflow:
- Verifies we're on the development branch
- Checks for pending changes from the release branch
- Reads
AutoUpdatedVersions.propsfrom all dependencies withAutoUpdateVersion = true - If there are changes since last deployment:
- For products with their own version: increments
MainVersionpatch number (e.g.,2023.2.279→2023.2.280) - For products with
MainVersionDependency: uses the inherited version
- For products with their own version: increments
- Updates
AutoUpdatedVersions.propswith new dependency versions and this product's new version - Commits and pushes the changes (on CI)
Bump strategies (see IBumpStrategy):
DefaultBumpStrategy: Increments the patch component ofMainVersionPatchVersionBumpStrategy: IncrementsOurPatchVersionfor 4-component versioning
For implementation details, see:
MainVersionFile.cs- MainVersion.props parsingAutoUpdatedVersionsFile.cs- AutoUpdatedVersions.props managementBumpCommand.cs- Bump workflowVersionComponents.cs- Version computation
For new projects, use the template repository: https://github.com/postsharp/PostSharp.Engineering.ProductTemplate
# Show help
Build.ps1 --help
# Prepare dependencies (generates version files)
Build.ps1 prepare
# Build all packages
Build.ps1 build
# Build and run tests
Build.ps1 test
# Publish artifacts
Build.ps1 publish# List dependencies
Build.ps1 dependencies list
# Use local clone of a dependency
Build.ps1 dependencies set local <DEPENDENCY>
# Reset to default (feed/build server)
Build.ps1 dependencies reset --all
# Fetch latest from build server
Build.ps1 dependencies fetch
# Update to newest version
Build.ps1 dependencies update| Command | Description |
|---|---|
prepare |
Creates files required to build (version props, NuGet config) |
build |
Builds all packages (implies prepare) |
test |
Builds and runs all tests (implies build) |
publish |
Publishes built artifacts to configured destinations |
prepublish |
Prepares for publishing (validation, checks) |
postpublish |
Finalizes publishing (version bump, tagging) |
swap |
Swaps deployment slots (blue/green deployment) |
bump |
Bumps the product version |
verify |
Verifies public artifact dependencies are published |
clean |
Cleans build artifacts |
| Command | Description |
|---|---|
dependencies list |
Lists product dependencies |
dependencies set |
Sets dependency source (local, feed, build server) |
dependencies reset |
Resets to default configuration |
dependencies fetch |
Fetches from build server |
dependencies update |
Updates to newest available version |
dependencies update-eng |
Updates PostSharp.Engineering version |
| Command | Description |
|---|---|
codestyle format |
Formats code using ReSharper |
codestyle inspect |
Inspects code for warnings |
codestyle push |
Pushes code style changes to engineering repo |
codestyle pull |
Pulls code style from engineering repo |
| Command | Description |
|---|---|
teamcity run |
Triggers a TeamCity build |
teamcity project get |
Gets TeamCity project details |
teamcity project create |
Creates a new TeamCity project |
teamcity project create-this |
Creates project for current repo |
| Command | Description |
|---|---|
tools kill |
Kills compiler processes (useful after failed builds) |
tools dump |
Dumps process information |
tools git merge-downstream |
Merges to downstream branch |
tools git check-upstream |
Checks upstream for unmerged changes |
tools nuget rename |
Renames packages in a directory |
tools nuget verify-public |
Verifies packages reference only public packages |
generate-scripts |
Generates CI and Docker scripts |
The PostSharp.Engineering.Sdk package provides:
| File | Purpose |
|---|---|
BuildOptions.props |
Compiler settings (language version, nullability, output paths) |
SourceLink.props |
SourceLink configuration for debugging |
Coverage.props |
Code coverage with Coverlet (test projects only) |
StrongName.props |
Strong name signing configuration |
MetalamaBranding.props |
Metalama NuGet package branding |
PostSharpBranding.props |
PostSharp NuGet package branding |
SystemTypes.props |
Modern C# features for older target frameworks |
| File | Purpose |
|---|---|
AssemblyMetadata.targets |
Adds package versions to assembly metadata |
TeamCity.targets |
Build and test reporting for TeamCity |
AspNetPublish.targets |
ASP.NET Core project publishing |
WebPublish.targets |
Web project artifact publishing |
TestsPublish.targets |
Test project artifact publishing |
PackagesConfig.targets |
Legacy packages.config support |
CleanXmlDoc.targets |
XML documentation cleanup |
{
"sdk": {
"version": "9.0.100",
"rollForward": "latestPatch"
},
"msbuild-sdks": {
"PostSharp.Engineering.Sdk": "2023.2.1"
}
}<Project>
<PropertyGroup>
<Authors>Your Company</Authors>
<PackageProjectUrl>https://github.com/your/repo</PackageProjectUrl>
<PackageTags>your tags</PackageTags>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageIcon>Icon.png</PackageIcon>
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\Icon.png" Visible="false" Pack="true" PackagePath="" />
<None Include="$(MSBuildThisFileDirectory)..\LICENSE.md" Visible="false" Pack="true" PackagePath="" />
</ItemGroup>
</Project><Project>
<PropertyGroup>
<MainVersion>1.0.0</MainVersion>
<PackageVersionSuffix>-preview</PackageVersionSuffix>
</PropertyGroup>
</Project><Project>
<Import Project="MainVersion.props" />
<PropertyGroup>
<MyProductVersion>$(MainVersion)$(PackageVersionSuffix)</MyProductVersion>
<MyProductAssemblyVersion>$(MainVersion)</MyProductAssemblyVersion>
</PropertyGroup>
<!-- Dependency versions -->
<PropertyGroup>
<SomeDependencyVersion>1.2.3</SomeDependencyVersion>
</PropertyGroup>
<!-- Local overrides -->
<Import Project="Dependencies.props" Condition="Exists('Dependencies.props')" />
<PropertyGroup>
<AssemblyVersion>$(MyProductAssemblyVersion)</AssemblyVersion>
<Version>$(MyProductVersion)</Version>
</PropertyGroup>
</Project><Project>
<PropertyGroup>
<RepoDirectory>$(MSBuildThisFileDirectory)</RepoDirectory>
<RepoKind>GitHub</RepoKind>
</PropertyGroup>
<Import Project="eng\Versions.props" />
<Import Project="eng\Packaging.props" />
<Import Sdk="PostSharp.Engineering.Sdk" Project="BuildOptions.props" />
<Import Sdk="PostSharp.Engineering.Sdk" Project="SourceLink.props" />
</Project><Project>
<Import Sdk="PostSharp.Engineering.Sdk" Project="AssemblyMetadata.targets" />
<Import Sdk="PostSharp.Engineering.Sdk" Project="TeamCity.targets" />
</Project><Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>Build</AssemblyName>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PostSharp.Engineering.BuildTools" Version="$(PostSharpEngineeringVersion)" />
</ItemGroup>
</Project>using PostSharp.Engineering.BuildTools;
using PostSharp.Engineering.BuildTools.Build.Model;
using PostSharp.Engineering.BuildTools.Build.Solutions;
var product = new Product(/* your dependency definition */)
{
Solutions =
[
new DotNetSolution("YourProduct.sln") { SupportsTestCoverage = true }
],
PublicArtifacts = Pattern.Create(
"YourPackage.$(PackageVersion).nupkg")
};
var app = new EngineeringApp(product);
return app.Run(args);if ($env:VisualStudioVersion -eq $null) {
Import-Module "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
Enter-VsDevShell -VsInstallPath "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\" -StartInPath $(Get-Location)
}
& dotnet run --project "$PSScriptRoot\eng\src\Build.csproj" -- $args
exit $LASTEXITCODEartifacts/
eng/tools/
*.g.props
classDiagram
class Product {
+Solutions
+Dependencies
+Configurations
+PublicArtifacts
+Publishers
}
class Solution {
<<abstract>>
}
class DotNetSolution
class Publisher {
<<abstract>>
}
Product o-- "*" Solution
Product o-- "*" DependencyDefinition
Product o-- "3" BuildConfigurationInfo
BuildConfigurationInfo o-- "*" Publisher
BuildConfigurationInfo o-- "*" Swapper
Solution <|-- DotNetSolution
Publisher <|-- NugetPublisher
Publisher <|-- VsixPublisher
Publisher <|-- S3Publisher
Publisher <|-- MsDeployPublisher
- Product: Root configuration representing a repository
- Solution: A buildable unit (solution, project, or script)
- BuildConfigurationInfo: Configuration-specific settings (Debug, Release, Public)
- DependencyDefinition: Reference to another repository/product
- Publisher: Deploys artifacts to feeds, marketplaces, or servers
- Swapper: Blue/green deployment slot swapping
We use TeamCity with Kotlin DSL configuration stored in .teamcity/.
# Debug build
Build.ps1 test --numbered %build.number%
# Release build (signed)
Build.ps1 test --configuration Release --sign
# Publish to internal feeds
Build.ps1 publish
# Publish to public feeds
Build.ps1 publish --publicAll artifacts are published to artifacts/publish/. TeamCity configurations should export and import these paths.
Common environment variables (see EnvironmentVariableNames.cs for the complete list):
| Variable | Purpose |
|---|---|
SIGNSERVER_SECRET |
Code signing server authentication |
NUGET_ORG_API_KEY |
nuget.org API key |
GITHUB_TOKEN |
GitHub API access |
TEAMCITY_CLOUD_TOKEN |
TeamCity API access |
AWS_ACCESS_KEY_ID |
AWS S3 publishing |
AWS_SECRET_ACCESS_KEY |
AWS S3 publishing |
VS_MARKETPLACE_ACCESS_TOKEN |
VSIX publishing |
AZURE_DEVOPS_TOKEN |
Azure DevOps integration |
TYPESENSE_API_KEY |
Search indexing |
Additional design documentation is available in the doc/ folder:
- Vision - Project goals
- Use Cases - Supported scenarios
- Build Flow - Build process diagrams
- Publish Flow - Publishing process
- Dependencies - Metalama dependency graph
This project is licensed under the MIT License. See LICENSE.md for details.