Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/asset-system/compiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,4 @@ Compiler classes live in the same assembly as the asset class. For engine assets
The `Prepare` method and build command shown above form the complete starting template for a new compiler. Copy both blocks, replace `%%AssetName%%` with your asset's PascalCase name, and fill in the property mapping inside `DoCommandOverride`.

> [!NOTE]
> **For game-project custom assets:** the compiler class lives in the game project itself. Add `<PackageReference Include="Stride.AssetCompiler" IncludeAssets="build;buildTransitive" />` to the game project's `.csproj` — this brings in the infrastructure that discovers and invokes the compiler; it does not change where the compiler class lives.
> **For game-project custom assets:** the compiler class lives in the game project itself. Add `<PackageReference Include="Stride.AssetCompiler" Version="..." />` to the game project's `.csproj` (or `#:package Stride.AssetCompiler@...` in a file-based app) — this brings in the infrastructure that discovers and invokes the compiler; it does not change where the compiler class lives. The package is build-only: it contributes its build targets and nothing to the game's references or output folder, so the reference needs no `IncludeAssets`.
2 changes: 1 addition & 1 deletion docs/asset-system/registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@ The directory name under `Assets/` does not have to match the `Group` string exa
> - !file Templates/%%AssetName%%.sdtpl
> ```
>
> No `Module.cs` or `AssemblyRegistry.Register` call is needed for game-project assets. Compiler discovery is handled by the `Stride.AssetCompiler` plugin mechanism — the compiler class is found automatically as long as the game project references `Stride.AssetCompiler` as a build-only dependency.
> No `Module.cs` or `AssemblyRegistry.Register` call is needed for game-project assets. Compiler discovery is handled by the `Stride.AssetCompiler` plugin mechanism — the compiler class is found automatically as long as the game project references the `Stride.AssetCompiler` package (a build-only package, so a plain `PackageReference` is enough).
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
<PackageReference Include="Stride.Navigation" Version="$EngineVersion$" />
<PackageReference Include="Stride.Particles" Version="$EngineVersion$" />
<PackageReference Include="Stride.UI" Version="$EngineVersion$" />
<PackageReference Include="Stride.AssetCompiler" Version="$EngineVersion$" IncludeAssets="build;buildTransitive" />
<PackageReference Include="Stride.AssetCompiler" Version="$EngineVersion$" />
</ItemGroup>
</Project>
34 changes: 20 additions & 14 deletions sources/assets/Stride.AssetCompiler/Stride.AssetCompiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'Directory.Build.props'))/sdk/Stride.Build.Sdk.Editor/Sdk/Sdk.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<!-- Referenced in-process (Stride.Editor) AND run as a tool: pack the runnable app under tools/<tfm>/
and keep lib/<tfm>/ to just the reference assembly, so consumers don't inherit the apphost
(a duplicate of the managed assembly) or the resolver bootstrap dlls. -->
<!-- Build-only package: a game references it (a plain PackageReference or #:package is enough) for its
build/ targets, which run the compiler packed under tools/<tfm>/. No lib/, and every dependency is
private except buildTransitive (see _StrideBuildOnlyPrivateAssets below), so nothing of the compiler
reaches the game's compile references or output folder. The compiler restores those dependencies for
itself (Stride.NuGetResolver restores the ones its nuspec keeps out of consumers' runtime). -->
<StridePackRunnableInTools>true</StridePackRunnableInTools>
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- NU5128: dependency groups without a matching lib/ folder, which is the build-only layout above. -->
<NoWarn>$(NoWarn);NU5128</NoWarn>
<_StrideBuildOnlyPrivateAssets>compile;runtime;native;contentfiles;analyzers;build</_StrideBuildOnlyPrivateAssets>
<StrideAssemblyProcessor>true</StrideAssemblyProcessor>
<!-- Lay graphics-API DLLs into per-API subfolders so the API is chosen at launch. -->
<StrideMultiGraphicsApiHost>true</StrideMultiGraphicsApiHost>
Expand All @@ -20,7 +26,7 @@
<StrideAssemblyProcessorOptions>--auto-module-initializer --serialization</StrideAssemblyProcessorOptions>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Options" />
<PackageReference Include="Mono.Options" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\SharedAssemblyInfo.cs">
Expand All @@ -35,10 +41,10 @@
<PackageReference Include="Microsoft.NET.StringTools" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Framework" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Utilities.Core" ExcludeAssets="runtime" PrivateAssets="all" />
<ProjectReference Include="..\Stride.Core.Assets.Quantum\Stride.Core.Assets.Quantum.csproj" />
<ProjectReference Include="..\Stride.Core.Assets.Quantum\Stride.Core.Assets.Quantum.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
<!-- Headless crash capture: write crashes to the store and (in CI) send them. UI-agnostic core only —
the reporter window is spawned out-of-process, never referenced here. -->
<ProjectReference Include="..\..\crashreport\Stride.CrashReport\Stride.CrashReport.csproj" />
<ProjectReference Include="..\..\crashreport\Stride.CrashReport\Stride.CrashReport.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
<!-- Sentry is used by the core's crash sender at send time (CI send mode), but it's PrivateAssets=all in the
core (to keep its Roslyn generator out of game builds) so it doesn't flow here. Reference it directly so
Sentry.dll is deployed for the send. PrivateAssets=all is load-bearing: the asset compiler flows to games
Expand All @@ -48,16 +54,16 @@
<!-- The out-of-process reporter, resolved from the store at crash time. Private=false emits the nuspec
<dependency> (so a standalone compiler install pulls the reporter in) without copying its
self-contained closure into our bin. ReferenceOutputAssembly=false would suppress the dependency. -->
<ProjectReference Include="..\..\crashreport\Stride.CrashReporter\Stride.CrashReporter.csproj">
<ProjectReference Include="..\..\crashreport\Stride.CrashReporter\Stride.CrashReporter.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\engine\Stride.Assets.Models\Stride.Assets.Models.csproj" />
<ProjectReference Include="..\..\engine\Stride.Particles\Stride.Particles.csproj" />
<ProjectReference Include="..\..\engine\Stride.Physics\Stride.Physics.csproj" />
<ProjectReference Include="..\..\engine\Stride.BepuPhysics\Stride.BepuPhysics\Stride.BepuPhysics.csproj" />
<ProjectReference Include="..\..\engine\Stride.SpriteStudio.Offline\Stride.SpriteStudio.Offline.csproj" />
<ProjectReference Include="..\..\engine\Stride.UI\Stride.UI.csproj" />
<ProjectReference Include="..\..\core\Stride.Core.MicroThreading\Stride.Core.MicroThreading.csproj" />
<ProjectReference Include="..\..\engine\Stride.Assets.Models\Stride.Assets.Models.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
<ProjectReference Include="..\..\engine\Stride.Particles\Stride.Particles.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
<ProjectReference Include="..\..\engine\Stride.Physics\Stride.Physics.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
<ProjectReference Include="..\..\engine\Stride.BepuPhysics\Stride.BepuPhysics\Stride.BepuPhysics.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
<ProjectReference Include="..\..\engine\Stride.SpriteStudio.Offline\Stride.SpriteStudio.Offline.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
<ProjectReference Include="..\..\engine\Stride.UI\Stride.UI.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
<ProjectReference Include="..\..\core\Stride.Core.MicroThreading\Stride.Core.MicroThreading.csproj" PrivateAssets="$(_StrideBuildOnlyPrivateAssets)" />
</ItemGroup>
<Import Project="..\..\shared\Stride.NuGetResolver.Targets\Stride.NuGetResolver.Targets.projitems" Label="Shared" />
<Import Project="$(StrideRoot)sources/sdk/Stride.Build.Sdk.Editor/Sdk/Sdk.targets" />
Expand Down
4 changes: 3 additions & 1 deletion sources/editor/Stride.Editor.Wpf/Stride.Editor.Wpf.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
</Compile>
</ItemGroup>
<ItemGroup>
<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.

<ProjectReference Include="..\..\engine\Stride.Voxels\Stride.Voxels.csproj" />
<ProjectReference Include="..\..\presentation\Stride.Core.Presentation.Quantum\Stride.Core.Presentation.Quantum.csproj" />
<ProjectReference Include="..\..\engine\Stride.Assets\Stride.Assets.csproj" />
Expand Down
41 changes: 39 additions & 2 deletions sources/shared/Stride.NuGetResolver/RestoreHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.Packaging;
using NuGet.ProjectModel;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
Expand Down Expand Up @@ -222,6 +223,7 @@ public static (RestoreRequest, RestoreResult) Restore(ILogger logger, NuGetFrame
var assemblies = new List<string>();

var projectPath = Path.Combine("StrideNugetResolver.json");
var packagesPath = SettingsUtility.GetGlobalPackagesFolder(settings);
var spec = new PackageSpec()
{
Name = Path.GetFileNameWithoutExtension(projectPath), // make sure this package never collides with a dependency
Expand All @@ -235,7 +237,8 @@ public static (RestoreRequest, RestoreResult) Restore(ILogger logger, NuGetFrame
new LibraryDependency
{
LibraryRange = new LibraryRange(packageName, versionRange, LibraryDependencyTarget.Package),
}
},
.. GetRuntimeExcludedDependencies(packagesPath, packageName, versionRange, nugetFramework),
],
}
},
Expand All @@ -249,7 +252,7 @@ public static (RestoreRequest, RestoreResult) Restore(ILogger logger, NuGetFrame
OutputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "stride", "nugetresolver", $"{packageName}-{versionRange.MinVersion.ToString()}-{nugetFramework.GetShortFolderName()}-{runtimeIdentifier}"),
OriginalTargetFrameworks = new[] { nugetFramework.GetShortFolderName() },
ConfigFilePaths = settings.GetConfigFilePaths(),
PackagesPath = SettingsUtility.GetGlobalPackagesFolder(settings),
PackagesPath = packagesPath,
Sources = SettingsUtility.GetEnabledSources(settings).ToList(),
FallbackFolders = SettingsUtility.GetFallbackPackageFolders(settings).ToList()
},
Expand Down Expand Up @@ -322,5 +325,39 @@ public static (RestoreRequest, RestoreResult) Restore(ILogger logger, NuGetFrame
throw new InvalidOperationException("Unreachable code");
}
}

/// <summary>
/// The dependencies a build-only package (no lib/, e.g. Stride.AssetCompiler, a tool that runs from its own
/// package) keeps out of its consumers' runtime, read from its nuspec in the global packages folder. They are
/// the package's own runtime dependencies, so they are restored as direct dependencies to get their runtime
/// assets. None for a package with a lib/ folder, one not in the global packages folder yet, or an inexact version.
/// </summary>
private static IEnumerable<LibraryDependency> GetRuntimeExcludedDependencies(string packagesPath, string packageName, VersionRange versionRange, NuGetFramework nugetFramework)
{
if (!versionRange.HasLowerAndUpperBounds || versionRange.MinVersion != versionRange.MaxVersion)
return [];

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 [];
Comment on lines +340 to +343

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.


var dependencyGroup = NuGetFrameworkUtility.GetNearest(new NuspecReader(nuspecPath).GetDependencyGroups(), nugetFramework);
if (dependencyGroup == null)
return [];

return dependencyGroup.Packages
.Where(dependency =>
{
var includeFlags = dependency.Include.Count > 0 ? LibraryIncludeFlagUtils.GetFlags(dependency.Include) : LibraryIncludeFlags.All;
includeFlags &= ~LibraryIncludeFlagUtils.GetFlags(dependency.Exclude);
return (includeFlags & LibraryIncludeFlags.Runtime) == 0;
})
.Select(dependency => new LibraryDependency
{
LibraryRange = new LibraryRange(dependency.Id, dependency.VersionRange, LibraryDependencyTarget.Package),
})
.ToList();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ public void ConsumerResolvesPluginTypeWhenAssetAssemblyDeclared()
Assert.Contains("PluginPage|/StrideAssetPlugin/PluginPage", aliases);
Assert.Contains("Page|/Consumer/Page", aliases);

// Stride.AssetCompiler is build-only: Consumer.Game's plain reference brings its build targets, and
// neither the compiler nor a dependency only it has (Mono.Options) reaches the output folder.
var binDir = Path.Combine(consumerDir, "bin", "Debug", "net10.0");
Assert.False(File.Exists(Path.Combine(binDir, "Stride.AssetCompiler.dll")), "Stride.AssetCompiler.dll was copied to the consumer output.");
Assert.False(File.Exists(Path.Combine(binDir, "Mono.Options.dll")), "Mono.Options.dll (an asset compiler dependency) was copied to the consumer output.");

AssertRuntimeContentResolves(consumerDir);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<PackageReference Include="Stride.Engine" Version="$(StrideEngineVersion)" />
<PackageReference Include="Stride.UI" Version="$(StrideEngineVersion)" />
<PackageReference Include="StrideAssetPlugin" Version="1.0.0" />
<PackageReference Include="Stride.AssetCompiler" Version="$(StrideEngineVersion)" IncludeAssets="build;buildTransitive" />
<!-- Plain reference: the package is build-only by itself (the plugin fixture keeps the
IncludeAssets="build;buildTransitive" form, so both stay covered). -->
<PackageReference Include="Stride.AssetCompiler" Version="$(StrideEngineVersion)" />
</ItemGroup>
</Project>
Loading