Building the Toolkit
How the repository is built, why the examples build is configured the way it is, and how to produce local NuGet packages for testing.
Solutions and solution filters
| File | Contents | Use it when |
|---|---|---|
Stride.CommunityToolkit.slnx |
Everything: libraries, tests, tools, benchmarks and all example projects | Verifying a change across every example, e.g. after a Stride upgrade |
Stride.CommunityToolkit.Core.slnf |
The core libraries, tests and tools; the Box2D, Charts, Effects and Shapes packages are left out as well | Day-to-day library work |
The repository contains over a hundred example projects. Loading all of them slows the IDE noticeably, so the
solution filter exists to skip them. Open the .slnf exactly like a solution; excluded projects
still appear in Solution Explorer as unloaded nodes, and Load Project pulls in any single one
when you need to debug it. Load All Projects restores the full set without switching files.
Because a filter is only a view over Stride.CommunityToolkit.slnx, adding a project to the
solution automatically makes it available in the filter, and no filter update is needed.
dotnet build accepts a filter too:
dotnet build Stride.CommunityToolkit.Core.slnf
Why the examples build is fast
Two shared files under build/ keep the example, test and tool builds small. Without them, each
Stride project copies roughly 476 MB into bin, most of which is unreachable from a desktop build:
about 239 MB of Android native runtimes, a further ~38 MB of iOS/tvOS/macOS, and 54 MB of XML
documentation from referenced packages. The Avalonia launcher was worse still, at 561 MB per
configuration: Skia and HarfBuzz for 25 platforms, and 100 MB of native symbol files even for the
one platform it runs on.
| File | What it does |
|---|---|
build/HostRuntime.props |
Restricts the build to the host runtime identifier, so only the current platform's native runtimes are copied |
build/HostRuntime.targets |
Removes package XML documentation and native symbol files from the output |
The Directory.Build.props and .targets under examples/, tests/ and tools/ import them; the
library projects under src/ do not, because a package has to stay runtime-neutral. Together these
take an example from ~476 MB to ~80 MB, the test project from 495 MB to 89 MB and the launcher from
561 MB to 28 MB, and a clean build of the whole solution to well under a minute.
Two details worth knowing before editing them:
- Every nested
Directory.Build.propsand.targetsexplicitly imports the repository-root equivalent. MSBuild imports only the nearest one, so without that import the settings defined at the repository root, such asTargetFrameworkandStrideVersion, would be silently lost. - The runtime identifier is derived from the host OS, not hard-coded to Windows, so Linux and
macOS builds keep working.
NETCoreSdkPortableRuntimeIdentifierwould be the obvious source for this but the SDK sets it afterDirectory.Build.propsis evaluated, so explicitIsOSPlatformchecks are used instead.
Note
AppendRuntimeIdentifierToOutputPath is disabled deliberately, so output stays at
bin/<Configuration>/net10.0/ rather than gaining a win-x64/ segment. This keeps documented
paths and launcher commands valid. With a runtime identifier set, native libraries land flat
beside the executable rather than under runtimes/<rid>/.
Cleaning build output
Even at the smaller sizes above, a full tree of bin and obj folders runs to several gigabytes,
and a stale obj/stride can keep shaders compiled from a previous Stride package (see the
release notes for the asset-bundle trap). Two scripts at the repository root remove them:
| Script | Removes |
|---|---|
delete-bin.bat |
Every bin and obj folder in the repository, or only under the folders given as arguments: delete-bin.bat examples tests |
delete-bin-examples.bat |
The same under examples/ only, snippets included; library, test and tool outputs stay |
Each folder removed is printed. One that a running process holds open, such as Visual Studio with
the project loaded, is reported as locked and skipped. Nothing git tracks is named bin or obj,
so the scripts never touch source. They are deliberately not git clean -xdf, which would also
delete every other ignored file, local settings included.
Building local NuGet packages
To test the toolkit the way a consumer uses it, through PackageReference rather than
ProjectReference, build the packages into a local feed:
build\pack-local.bat
Or, on any platform:
dotnet run --file build/pack-local.cs
Useful arguments: --version <version>, --configuration <configuration> and --clean.
This packs every publishable library into bin/packages as version 99.0.0-dev. The version is
fixed by design, so a reference such as Stride.CommunityToolkit.Bepu@99.0.0-dev keeps working
across rebuilds and never has to be edited.
The 99 is deliberate. A PackageReference means at least that version, and NuGet then picks the
lowest version satisfying it. Because prerelease labels compare alphabetically, a 1.0.0-dev
constraint was satisfied by every published 1.0.0-preview.* - so whenever the local feed was not
consulted, NuGet quietly chose the oldest of them and the failure surfaced much later as a missing
Stride 4.2 assembly. Nothing published can satisfy 99.0.0, so that mistake now fails immediately
with NU1101/NU1102 instead.
Important
NuGet keys its global cache by package id and version, and will not re-extract a rebuilt package
that carries a version it has already seen. The script therefore deletes the matching folders under
~/.nuget/packages before packing, so a freshly built package always wins. This mirrors what
Stride does in build/install-gamestudio.targets.
Consuming the local packages
The script also writes a ready-to-use bin/packages/NuGet.config. Copy it next to the project that
should consume the packages, then reference them normally:
<PackageReference Include="Stride.CommunityToolkit.Bepu" Version="99.0.0-dev" />
Nothing machine-wide needs changing. NuGet merges that configuration with any existing one, so nuget.org and the Stride dev feed keep resolving exactly as before.
Warning
The packageSourceMapping entry in that file is required, not optional. Once any NuGet
configuration on the machine defines packageSourceMapping, and the Stride development setup does,
a source that is not mapped is silently never consulted - so the local feed is skipped entirely and
the restore fails as if the packages did not exist.
The file maps two patterns, and both are needed:
<package pattern="Stride.CommunityToolkit" />
<package pattern="Stride.CommunityToolkit.*" />
Stride.CommunityToolkit.* requires a dot after the prefix, so it matches
Stride.CommunityToolkit.Bepu and friends but not the base Stride.CommunityToolkit package,
which needs the exact-name pattern of its own. Without it the base package falls through to the
nuget.org catch-all, where no 99.0.0 exists, and the restore fails.
Both are more specific than the Stride.* mapped to the Stride dev feed. NuGet resolves by longest
matching prefix, so the toolkit packages come from the local feed without disturbing how Stride
packages resolve.
Testing the packages on Linux from WSL
The same feed serves a project built inside WSL, since WSL mounts the Windows drives under /mnt.
On Windows the pack script also writes bin/packages/NuGet.wsl.config: the feed by its /mnt path,
plus nuget.org and the Stride dev feed, because a Linux NuGet has no machine-wide configuration
mapping them and a source that is not mapped is never consulted. Copy it next to the project under
WSL as NuGet.config, and reference the exact versions the feeds hold: the toolkit at 99.0.0-dev,
Stride at the version in the dev feed.
Four things that cost a day each the first time:
- A same-version repack is invisible to NuGet under WSL too. After every pack, delete the
extractions and the resolver's cache in the distribution, then restore again:
rm -rf ~/.nuget/packages/stride.communitytoolkit*/99.0.0-dev ~/.nuget/packages/stride.*/<version> /tmp/StrideNugetResolver-*. To make the asset steps rerun as well, delete the game'sobj/stride, the*.Linux/obj/Debug/stridefolder andBin/Linux/Debug/data. - A self-built Stride dev feed must include Vulkan. The engine's
build/Stride.Local.propsdefaultsStrideGraphicsApisto Direct3D11 only, and a Direct3D11-onlyStride.Graphicson Linux fails inside the skybox compile with a null reference inSilk.NET.DXGI.GetApi. Pack the engine withStrideGraphicsApiDependentBuildAll=true, which is what its CI passes. - Use Microsoft's .NET build, not the distribution's package. Ubuntu's apt
dotnetreports a distribution-specific runtime identifier and ships only the 1xx SDK band, whose Roslyn is older than the one Stride's analyzers target, so the engine's source generators are silently skipped (CS9057) and serializer registration never happens. Install the matching band withdotnet-install.sh --channel 10.0.4xx. - The asset compiler under WSL needs
libgomp1(the texture compressor links it) and, on a build where the SDK has switched NuGet signature verification on for its child processes,DOTNET_NUGET_SIGNATURE_VERIFICATION=false dotnet builduntil the compiler learns the SDK's trust store. Rerun with-tl:offto see the errors the terminal logger hides.
A game that shows no window but burns several cores is WSLg's RDP client not running; wsl --shutdown
and reopen. Rendering goes through Mesa's software Vulkan, so it is CPU-bound, and SDL there is X11
only.
Testing local packages inside this repository
The examples reference the libraries by ProjectReference, which always wins over a package. To test
an example against the local packages instead:
- Copy
bin/packages/NuGet.configinto that example's folder first, before touching the project file. NuGet discovers configuration by walking up from the project directory, so it applies to that example only. The order matters - see the warning below. - Replace the toolkit
ProjectReferenceentries withPackageReferenceentries. Keeping both pulls the same assemblies in twice, and theProjectReferencewins, so the package is never actually exercised. - Revert both changes when finished. The copied file contains an absolute, machine-specific path and must not be committed.
Warning
Add the NuGet.config before the package references, or restore explicitly afterwards.
If the local feed is not configured yet, the restore fails with NU1101/NU1102: nothing
published satisfies 99.0.0, which is the whole reason that version was chosen.
Adding the NuGet.config afterwards does not fix it on its own, though. The failed resolution is
already recorded in obj/project.assets.json, and a plain dotnet build keeps using it. Force a
restore to recover:
dotnet restore examples/code-only/<Example>/<Example>.csproj
Deleting the example's obj folder has the same effect. This is also why the base package matters:
the mapping in the generated config includes both Stride.CommunityToolkit and
Stride.CommunityToolkit.*, because the .* pattern alone does not match the base package name and
would leave it looking on nuget.org, where no 99.0.0 exists.
Building against a local Stride build
The repository builds against the Stride version set by StrideVersion in Directory.Build.props. To
build against a different version, such as an engine you built from source, override it in a local file.
Build Stride from source. Its build writes
4.4.0-devpackages to%LOCALAPPDATA%\stride\nugetdevand registers that folder as the Stride Dev NuGet source.Create
Directory.Build.local.propsin the repository root:<Project> <PropertyGroup> <StrideVersion>4.4.0-dev</StrideVersion> </PropertyGroup> </Project>Delete the build output, then build:
delete-bin.bat dotnet build Stride.CommunityToolkit.slnx
To return to the released version, delete Directory.Build.local.props and the build output.
| Item | Description |
|---|---|
Directory.Build.local.props |
Imported last by Directory.Build.props. Ignored by git |
StrideVersion |
Any property set in the local file overrides the committed value |
Important
Delete the build output after every version switch. The asset compiler keeps shaders compiled from the previous package, and the examples keep loading them.
Note
Keep a released version in Directory.Build.props. A -dev version exists only as local packages from a
Stride build, so the CI workflows, which restore from nuget.org, cannot restore it.
Running the examples
Code-only examples are GUI applications. Run one directly:
dotnet run --project examples/code-only/E01_3D_BasicScene/E01_3D_BasicScene.csproj
E01_3D_BasicScene_FileBasedApp is a file-based app:
a single .cs file with no project file, which declares its dependencies inline with #:package
and #:project directives. It has no .csproj, so it is not part of the solution and Visual Studio
will not build it alongside the other projects. Run it from the command line instead:
dotnet run --file examples/code-only/E01_3D_BasicScene_FileBasedApp/Program.cs
Debugging an example
Examples run until their window is closed, so a plain dotnet run cannot be waited on and read back.
Build first, then launch the executable with redirected output, wait, terminate, and read the log:
$out = "$env:TEMP\example-run.txt"
dotnet build examples\code-only\E02_3D_GiveMeACube\E02_3D_GiveMeACube.csproj -v q --nologo
$exe = "examples\code-only\E02_3D_GiveMeACube\bin\Debug\net10.0\E02_3D_GiveMeACube.exe"
$process = Start-Process $exe -PassThru -RedirectStandardOutput $out -WorkingDirectory (Split-Path $exe)
Start-Sleep -Seconds 12
if (-not $process.HasExited) { Stop-Process -Id $process.Id -Force }
Get-Content $out | Select-String "DIAG"
Where diagnostics actually appear
This catches people out, because the wrong choice produces no output at all rather than an error:
| Location | Use |
|---|---|
Top-level statements, game.Run(start:/update:) callbacks |
Console.WriteLine reaches the redirected stream |
Inside a SyncScript / AsyncScript / StartupScript |
Console.WriteLine does not reach it - use Log.Info, Log.Warning |
| Inside a render feature or game system | GlobalLogger.GetLogger("Name") |
Stride writes each line to both the console and the redirected stream, so captured output shows
everything twice. Expect the duplicates, or pipe through Select-Object -Unique.
Keeping per-frame logging readable
Gate on a frame counter, but always include the first few frames:
_frames++;
if (_frames > 3 && _frames % 120 != 0) return;
Log.Warning($"DIAG position={Entity.Transform.Position}");
Gating on % N alone can produce no output at all when the run is short or the frame rate is low,
which is easily misread as "the code never ran". Prefixing lines with a token such as DIAG makes
them easy to separate from Stride's own logging.
Build warnings are a debugging tool
Real defects hide in the warning list. A Stride 4.4 regression that silently broke the ImGui.NET
integration was found only through a single warning CS9193 among 66 warnings. Filter with
Select-String ": error|warning CS"; filtering by project path also matches unrelated NU1903
NuGet advisories.
Tip
Reach out to maintainers anytime, process improvements, clarifications, or code reviews, we're happy to help!