Skip to content

AssetCompiler: the package is build-only, a plain PackageReference or #:package is enough - #3570

Open
Kryptos-FR wants to merge 1 commit into
masterfrom
feature/asset-compiler-package-fix
Open

Kryptos-FR wants to merge 1 commit into
masterfrom
feature/asset-compiler-package-fix

Conversation

@Kryptos-FR

@Kryptos-FR Kryptos-FR commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

PR Details

Every game references Stride.AssetCompiler with IncludeAssets="build;buildTransitive". Without that attribute the game builds, but crashes at start-up:

System.TypeInitializationException: The type initializer for '<Module>' threw an exception.
 ---> System.IO.FileNotFoundException: Could not load file or assembly 'Stride.NuGetResolver, Version=1.0.0.0'
   at Stride.Core.Assets.NuGetResolverModuleInitializer.__Initialize__()
   ...
   at Stride.Core.DataSerializers.gameSerializerFactory.Initialize()

Cause: the package ships the compiler assembly under lib/ too, and its 11 dependencies flow normally. A plain reference copies Stride.AssetCompiler.dll and the asset pipeline (Assimp, NuGet.*, …) next to the game. The game's serializer factory then loads Stride.AssetCompiler.dll, whose resolver module initializer needs Stride.NuGetResolver.dll, which is only under tools/.

So every consumer has to know the package is build-only. A .NET 10 file-based app can't express it at all: #:package has no IncludeAssets, so a single-file Stride game needs a Directory.Build.targets next to it just for that.

This PR makes the package build-only by itself.

Changes

  • Stride.AssetCompiler.csproj:
    • Packs no lib/ (IncludeBuildOutput=false).
    • Every reference is private except buildTransitive, so the nuspec dependencies get exclude="Runtime,Compile,Build,Native,Analyzers".
    • Consumers get what IncludeAssets="build;buildTransitive" gave them: build/ and buildTransitive/ targets, and nothing in references or the output folder. Restore still downloads the whole dependency tree, which the compiler needs.
    • Existing projects that keep IncludeAssets="build;buildTransitive" behave the same.
  • Stride.NuGetResolver (RestoreHelper):
    • The compiler runs from the package's tools/ folder and resolves its own dependencies by restoring its package. Since the nuspec now keeps them out of runtime, that restore would no longer give the compiler its own assemblies (it fails with 'Stride.Core' was not present).
    • For a package with no lib/, the restore also adds the dependencies its nuspec excludes from runtime as direct dependencies, read from the nuspec in the global packages folder.
    • Packages with a lib/ (Game Studio, …) are unaffected.
  • Stride.Editor.Wpf: references the compiler project for build order only (ReferenceOutputAssembly=false). No editor code uses it, and the package no longer has a lib/ assembly to load.
  • New-game template (MyTemplate.Game.csproj): plain reference.
    • The samples are pinned to the published 4.4.0, which isn't build-only, so they keep IncludeAssets.
    • The 4.4 project upgrader still sets IncludeAssets="build;buildTransitive", which stays harmless.
  • Packaging tests:
    • The Consumer.Game fixture uses the plain reference; the plugin fixture keeps the IncludeAssets form, so both stay covered.
    • The test asserts that Stride.AssetCompiler.dll and Mono.Options.dll (a dependency only the compiler has) stay out of the consumer's output.
  • Docs: docs/asset-system/compiler.md and registration.md describe the plain reference. The stride-docs pages are updated in docs: The asset compiler package is Stride.AssetCompiler, build-only by itself from 4.5 stride-docs#575.

Before / after

Single-file game below, built with dotnet build game.cs:

Output folder Run
Stride.*@4.4.0 (published) 276 files, including Stride.AssetCompiler.dll, NuGet.*, Assimp, the asset pipeline crashes: Could not load file or assembly 'Stride.NuGetResolver'
Stride.*@4.5.0-dev (this branch) 75 files, nothing from the compiler CONTENT OK HelloFont (OfflineRasterizedSpriteFont, size 40), exit 0

Reproduce with a file-based app

Four files in an empty folder outside the repo. Replace 4.5.0-dev with the version of your local build (bin/packages/Stride.AssetCompiler.<version>.nupkg); use 4.4.0 and drop nuget.config to see the crash.

The asset package file must be named game.cs.sdpkg: the project name of a file-based app is game.cs, and a game.sdpkg is ignored.

game.cs

#:package Stride.Engine@4.5.0-dev
#:package Stride.AssetCompiler@4.5.0-dev

using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Games;
using Stride.Graphics;

using var game = new HelloGame();
game.Run();
return HelloGame.ExitCode;

class HelloGame : Game
{
    public static int ExitCode = 1;
    SpriteBatch? spriteBatch;
    SpriteFont? font;
    int frames;
    readonly int maxFrames = int.TryParse(System.Environment.GetEnvironmentVariable("STRIDE_FBAPP_FRAMES"), out var n) ? n : 0;

    protected override async System.Threading.Tasks.Task LoadContent()
    {
        await base.LoadContent();
        Window.Title = "Stride file-based app";
        spriteBatch = new SpriteBatch(GraphicsDevice);
        font = Content.Load<SpriteFont>("HelloFont");
        System.Console.WriteLine($"CONTENT OK HelloFont ({font.GetType().Name}, size {font.Size})");
        ExitCode = 0;
    }

    protected override void Draw(GameTime gameTime)
    {
        base.Draw(gameTime);
        var context = GraphicsContext;
        context.CommandList.SetRenderTargetAndViewport(GraphicsDevice.Presenter.DepthStencilBuffer, GraphicsDevice.Presenter.BackBuffer);
        context.CommandList.Clear(GraphicsDevice.Presenter.BackBuffer, Color.CornflowerBlue);
        context.CommandList.Clear(GraphicsDevice.Presenter.DepthStencilBuffer, DepthStencilClearOptions.DepthBuffer);
        spriteBatch!.Begin(context);
        spriteBatch.DrawString(font!, "Hello from a single-file Stride app!", new Vector2(40, 40), Color.White);
        spriteBatch.End();
        if (maxFrames > 0 && ++frames >= maxFrames)
            Exit();
    }
}

game.cs.sdpkg

!Package
SerializedVersion: {Assets: 3.1.0.0}
Meta:
    Name: game
    Version: 1.0.0
    Authors: []
    Owners: []
    Dependencies: null
AssetFolders:
    -   Path: !dir Assets
RootAssets:
    - 5376e512-4179-43b6-9d7c-75b0a1ea6f9c:HelloFont

Assets/HelloFont.sdfnt

!SpriteFont
Id: 5376e512-4179-43b6-9d7c-75b0a1ea6f9c
SerializedVersion: {Stride: 2.1.0.0}
Tags: []
FontSource: !SystemFontProvider
    FontName: Arial
    Style: Regular
FontType: !OfflineRasterizedSpriteFontType
    Size: 40.0
    CharacterSet: null
    CharacterRegions:
        4bba4088f15a824eb814e69fefe591ef:
            Start: ' '
            End: "\x7F"
    AntiAlias: Aliased

nuget.config (only for a local build; point it at your checkout's bin/packages)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="stride-local" value="C:/path/to/stride/bin/packages" />
  </packageSources>
</configuration>

Then (PowerShell):

$env:NUGET_PACKAGES = "C:\Temp\stride-fbapp-packages"   # a fresh folder: no stale same-version extraction
dotnet build game.cs --disable-build-servers
dotnet <output folder from the "game.cs -> ...\game.dll" line>\game.dll

Related Issue

No GitHub issue; reported on Discord. A plain reference to Stride.AssetCompiler, which is the only possible form in a .NET 10 file-based app (#:package), makes the game fail at start-up with Could not load file or assembly 'Stride.NuGetResolver'.

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My change requires a change to the documentation.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have built and run the editor to try this change out.

… #:package is enough

Games referenced Stride.AssetCompiler with IncludeAssets="build;buildTransitive"; without it the
package's lib/ assembly and its dependencies were copied next to the game, whose serializer factory
then loaded Stride.AssetCompiler.dll at start-up and failed on Stride.NuGetResolver. A .NET 10
file-based app can't write IncludeAssets at all.

- Stride.AssetCompiler packs no lib/ (IncludeBuildOutput=false) and every dependency is private
  except buildTransitive, so its nuspec dependencies exclude Runtime,Compile,Build,Native,Analyzers.
  Consumers get what IncludeAssets="build;buildTransitive" gave them; the old form keeps working.
- Stride.NuGetResolver: for a package with no lib/, the restore also takes the dependencies its
  nuspec keeps out of consumers' runtime as direct dependencies, so the compiler running from the
  package's tools/ still resolves its own closure.
- Stride.Editor.Wpf references the compiler project for build order only (nothing loads it in-process).
- The new-game template and the packaging-test consumer use the plain reference; the packaging test
  checks the compiler and Mono.Options stay out of the consumer's output. Docs updated.
@Kryptos-FR

Copy link
Copy Markdown
Member Author

Note

It should be easy to backport to 4.4 if needed.

Should I update all game samples to remove IncludeAssets="build;buildTransitive" on the reference to Stride.AssetCompiler?

@Kryptos-FR Kryptos-FR added ci-enduser Run the end-user sample screenshot + packaging suite on this PR ci-editor Run the GameStudio editor screenshot suite on this PR labels Oct 11, 2026
@Kryptos-FR
Kryptos-FR requested a review from xen2 October 11, 2026 16:45

@xen2 xen2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change!

<ProjectReference Include="..\..\assets\Stride.AssetCompiler\Stride.AssetCompiler.csproj" />
<!-- Build order only: a Game Studio run from the source tree builds games with the compiler built here.
Nothing in the editor loads the compiler in-process, and its package has no lib/ assembly to load. -->
<ProjectReference Include="..\..\assets\Stride.AssetCompiler\Stride.AssetCompiler.csproj" ReferenceOutputAssembly="false" Private="false" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With ReferenceOutputAssembly="false", NuGet drops this dependency from the nuspec. It was the only path from Stride.GameStudio to Stride.AssetCompiler.

The CLI installs only Stride.GameStudio, then looks for the Stride.AssetCompiler package. On a clean cache, stride upgrade fails with "couldn't locate its Asset Compiler", and stride new keeps the old version.

We need to keep the dependency in the nuspec somehow.

Comment on lines +340 to +343
var pathResolver = new VersionFolderPathResolver(packagesPath);
var nuspecPath = pathResolver.GetManifestFilePath(packageName, versionRange.MinVersion);
if (!File.Exists(nuspecPath) || Directory.Exists(Path.Combine(pathResolver.GetInstallPath(packageName, versionRange.MinVersion), "lib")))
return [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads the nuspec before the restore, and only from the user-level packages folder. If the game restores somewhere else (globalPackagesFolder in nuget.config, RestorePackagesPath, a fallback folder), nothing is found. The first build then fails with "'Stride.Core' was not present", and the second build works.

Suggestion: if the nuspec is missing, restore first, read the nuspec from the restored package, then restore again.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Asset bug-fix ci-editor Run the GameStudio editor screenshot suite on this PR ci-enduser Run the end-user sample screenshot + packaging suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants