Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*.suo
*.csproj.user
/Directory.Build.props
*.vcxproj.user
*.ReSharper.user
*.DotSettings.user
Expand Down
522 changes: 522 additions & 0 deletions build/GenerateDevPackages.cs

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions build/tools/ParallelBuildAuditor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@
contextToProject[ps.BuildEventContext.ProjectContextId] = ps.ProjectFile ?? "";
}

// Multi-TFM outer→inner self-dispatch (MSBuild's DispatchToInnerBuilds): the outer build
// (TargetFramework=<unset>) dispatches the project to itself with TargetFramework set
// explicitly to produce per-TFM outputs. Sequential, not parallel — only one inner runs at
// a time, so the shared OutputPath doesn't race even though both dispatches see it.
if (string.Equals(parent, ps.ProjectFile, StringComparison.OrdinalIgnoreCase)
&& globals.TryGetValue("TargetFramework", out var tf) && tf.Length > 0
&& string.Equals((ps.TargetNames ?? "").Trim(), "Build", StringComparison.OrdinalIgnoreCase))
{
return;
}

if (!groups.TryGetValue(key, out var list))
{
list = new List<(string, IReadOnlyDictionary<string, string>, string, string)>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,13 @@

<Target Name="_StridePrepareAssetCompiler">
<PropertyGroup>
<!-- First try NuGet layout, then git checkout -->
<!-- $(StrideRoot) anchors both in-tree consumers (set by sources/Directory.Build.props)
and dev-redirect consumers (seeded from $(StrideDevRoot) by the build/<PkgId>.props
stub written by build/GenerateDevPackages.cs). -->
<StrideCompileAssetCommand Condition="'$(StrideCompileAssetCommand)' == '' And '$(StrideRoot)' != ''">$(StrideRoot)sources\assets\Stride.Core.Assets.CompilerApp\bin\$(Configuration)\net10.0\Stride.Core.Assets.CompilerApp.dll</StrideCompileAssetCommand>
<!-- NuGet consumer: lib/<TFM>/ relative to this targets file's package root. -->
<StrideCompileAssetCommand Condition="'$(StrideCompileAssetCommand)' == '' And Exists('$(MSBuildThisFileDirectory)..\lib\net10.0-windows7.0\') And $(TargetFramework.Contains('-windows'))">$(MSBuildThisFileDirectory)..\lib\net10.0-windows7.0\Stride.Core.Assets.CompilerApp.dll</StrideCompileAssetCommand>
<StrideCompileAssetCommand Condition="'$(StrideCompileAssetCommand)' == '' And Exists('$(MSBuildThisFileDirectory)..\lib\net10.0\')">$(MSBuildThisFileDirectory)..\lib\net10.0\Stride.Core.Assets.CompilerApp.dll</StrideCompileAssetCommand>
<StrideCompileAssetCommand Condition="'$(StrideCompileAssetCommand)' == '' And Exists('$(MSBuildThisFileDirectory)..\Stride.Core.Assets.CompilerApp.csproj')">$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net10.0\Stride.Core.Assets.CompilerApp.dll</StrideCompileAssetCommand>
<StrideCompileAssetCommand Condition="'$(StrideCompileAssetCommand)' == ''">$(MSBuildThisFileDirectory)..\lib\net10.0\Stride.Core.Assets.CompilerApp.dll</StrideCompileAssetCommand>
</PropertyGroup>

<Error Condition="!Exists('$(StrideCompileAssetCommand)')" Text="Stride AssetCompiler could not be found (Command: &quot;$(StrideCompileAssetCommand)&quot;)"/>
Expand All @@ -147,10 +150,16 @@
<Target Name="_StrideEnsureCompilerAppPacked"
BeforeTargets="StrideCompileAsset"
Condition="'$(StrideIsExecutable)' == 'true' And '$(StrideCompilerSkipBuild)' != 'true' And Exists('$(MSBuildThisFileDirectory)..\Stride.Core.Assets.CompilerApp.csproj')">
<!-- Strip BuildingSolutionFile so CompilerApp's own _StrideTriggerPackOnInnerBuild fires
and fans Pack out to its engine ProjectReferences (Stride.UI, Stride.Physics, ...).
Those packs complete synchronously inside this MSBuild call, so the .nupkgs land in
NugetDev before StrideCompileAsset runs CompilerApp.exe. Without this, the engine
packs only happen as the natural AfterTargets=Build Pack chain in parallel with
StrideCompileAsset, and CompilerApp races ahead and fails to find them. -->
<MSBuild Projects="$(MSBuildThisFileDirectory)..\Stride.Core.Assets.CompilerApp.csproj"
Targets="Pack"
Properties="TargetFrameworks=$(StrideXplatEditorTargetFramework);TargetFramework=$(StrideXplatEditorTargetFramework)"
RemoveProperties="StrideSkipAutoPack;StrideGraphicsApi" />
RemoveProperties="StrideSkipAutoPack;StrideGraphicsApi;BuildingSolutionFile" />
</Target>

<Target Name="StrideSkipCompileAssetCheck" Condition="'$(StrideIsExecutable)' == 'true' And '$(StrideCompilerSkipBuild)' == 'true'" BeforeTargets="StrideCompileAsset">
Expand Down
74 changes: 74 additions & 0 deletions sources/core/Stride.Core.Design/Reflection/AssemblyContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,15 @@ public void RegisterDependency(string assemblyFullPath)
// TODO: Properly deal with file duplicates (same file in multiple package, or RID conflicts)
dependenciesMapping.TryAdd(assemblyName, fullPath);
}
else
{
// DLL missing from NuGet cache — check for dev-redirect stub
var packageFolder = Path.Combine(globalPackagesFolder, library.Path);
var assemblyName = Path.GetFileNameWithoutExtension(runtimeFile.Path);
var redirectPath = TryResolveDevRedirect(packageFolder, library.Name, assemblyName);
if (redirectPath != null)
dependenciesMapping.TryAdd(assemblyName, redirectPath);
}
}
}

Expand Down Expand Up @@ -395,4 +404,69 @@ public void RegisterDependency(string assemblyFullPath)

return null;
}

// Platform-aware probe order for dev-redirect HintPaths that contain $(StrideGraphicsApi).
// Picks the first API whose dev-built DLL exists on disk; we can't know the eventual runtime
// API at resolver time (Game/GraphicsAdapter haven't initialized yet), so we prefer the
// native API for the host platform.
private static readonly string[] GraphicsApiProbeOrder = OperatingSystem.IsWindows()
? new[] { "Direct3D11", "Direct3D12", "Vulkan", "OpenGL", "OpenGLES" }
: new[] { "Vulkan", "OpenGL", "OpenGLES" };

/// <summary>
/// Checks if a NuGet package folder contains a dev-redirect stub props and resolves the assembly path from its HintPath.
/// </summary>
private static string? TryResolveDevRedirect(string packageFolder, string packageName, string assemblyName)
{
var propsPath = Path.Combine(packageFolder, "build", $"{packageName}.props");
if (!File.Exists(propsPath))
return null;

try
{
var doc = System.Xml.Linq.XDocument.Load(propsPath);
System.Xml.Linq.XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";

if (doc.Descendants(ns + "StrideDevRedirect").FirstOrDefault()?.Value != "true")
return null;

var devRoot = doc.Descendants(ns + "StrideDevRoot").FirstOrDefault()?.Value ?? "";
var devConfig = doc.Descendants(ns + "StrideDevConfiguration").FirstOrDefault()?.Value ?? "Debug";

foreach (var reference in doc.Descendants(ns + "Reference"))
{
var include = reference.Attribute("Include")?.Value;
if (!string.Equals(include, assemblyName, StringComparison.OrdinalIgnoreCase))
continue;

var hintPath = reference.Element(ns + "HintPath")?.Value;
if (hintPath == null)
continue;

hintPath = hintPath
.Replace("$(StrideDevRoot)", devRoot)
.Replace("$(StrideDevConfiguration)", devConfig);

if (hintPath.Contains("$(StrideGraphicsApi)"))
{
foreach (var api in GraphicsApiProbeOrder)
{
var candidate = hintPath.Replace("$(StrideGraphicsApi)", api);
if (File.Exists(candidate))
return candidate;
}
return null;
}

if (File.Exists(hintPath))
return hintPath;
}
}
catch
{
// Ignore malformed props files
}

return null;
}
}
8 changes: 6 additions & 2 deletions sources/core/Stride.Core/build/Stride.Core.targets
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@
<StrideAssemblyProcessor Condition="'$(StrideAssemblyProcessor)' == ''">true</StrideAssemblyProcessor>
<StrideAssemblyProcessorFramework>netstandard2.0</StrideAssemblyProcessorFramework>
<StrideAssemblyProcessorExt>.dll</StrideAssemblyProcessorExt>
<StrideAssemblyProcessorBasePath Condition="Exists('$(MSBuildThisFileDirectory)..\tools\AssemblyProcessor\$(StrideAssemblyProcessorFramework)\Stride.Core.AssemblyProcessor$(StrideAssemblyProcessorExt)')">$(MSBuildThisFileDirectory)..\tools\AssemblyProcessor\$(StrideAssemblyProcessorFramework)\</StrideAssemblyProcessorBasePath>
<StrideAssemblyProcessorBasePath Condition="Exists('$(MSBuildThisFileDirectory)..\..\..\..\deps\AssemblyProcessor\$(StrideAssemblyProcessorFramework)\Stride.Core.AssemblyProcessor$(StrideAssemblyProcessorExt)')">$(MSBuildThisFileDirectory)..\..\..\..\deps\AssemblyProcessor\$(StrideAssemblyProcessorFramework)\</StrideAssemblyProcessorBasePath>
<!-- $(StrideRoot) anchors both in-tree consumers (set by sources/Directory.Build.props)
and dev-redirect consumers (seeded from $(StrideDevRoot) by build/<PkgId>.props
stubs written by build/GenerateDevPackages.cs). NuGet-only consumers fall back to
the tools/ copy shipped in the package. -->
<StrideAssemblyProcessorBasePath Condition="'$(StrideRoot)' != ''">$(StrideRoot)deps\AssemblyProcessor\$(StrideAssemblyProcessorFramework)\</StrideAssemblyProcessorBasePath>
<StrideAssemblyProcessorBasePath Condition="'$(StrideAssemblyProcessorBasePath)' == ''">$(MSBuildThisFileDirectory)..\tools\AssemblyProcessor\$(StrideAssemblyProcessorFramework)\</StrideAssemblyProcessorBasePath>
<StrideAssemblyProcessorPath>$(StrideAssemblyProcessorBasePath)Stride.Core.AssemblyProcessor$(StrideAssemblyProcessorExt)</StrideAssemblyProcessorPath>
<StrideAssemblyProcessorSerializationHashFile>$(IntermediateOutputPath)$(TargetName).sdserializationhash</StrideAssemblyProcessorSerializationHashFile>

Expand Down
3 changes: 3 additions & 0 deletions sources/sdk/Stride.Build.Sdk/Sdk/Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@
<RemoveDir Condition="Exists('$(NuGetPackageRoot)$(PackageId.ToLowerInvariant())\$(PackageVersion)')"
Directories="$(NuGetPackageRoot)$(PackageId.ToLowerInvariant())\$(PackageVersion)" />
</Target>
<!-- Dev packages mode: skip packing and use redirect packages (must be before GeneratePackageOnBuild) -->
<Import Project="$(StrideRoot)sources\targets\Stride.DevPackages.targets" />

<!-- Per-API inner builds of cross-targeted projects (Stride.Graphics, ...) could in theory
race on the same Stride.<X>.<ver>.nupkg, but NuGet's own IsInnerBuild gate
(NuGet.Build.Tasks.Pack.targets, TargetFramework!='' AND TargetFrameworks!='') already
Expand Down
5 changes: 5 additions & 0 deletions sources/sdk/Stride.Build.Sdk/Sdk/Stride.Local.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
<StridePlatforms Condition="'$(StridePlatforms)' == '' And '$([MSBuild]::IsOSPlatform(OSX))' == 'true'">macOS</StridePlatforms>
<StridePlatforms Condition="'$(StridePlatforms)' == '' And '$([MSBuild]::IsOSPlatform(Linux))' == 'true'">Linux</StridePlatforms>
<StrideGraphicsApiDependentBuildAll Condition="'$(StrideGraphicsApiDependentBuildAll)' == ''">false</StrideGraphicsApiDependentBuildAll>
<!-- Dev-redirect stub mode: when true, NuGet auto-pack is skipped on every build and
consumers resolve Stride.* DLLs through redirect props that point at the dev-built
binaries (cuts ~30s/build). Run `dotnet run build/GenerateDevPackages.cs` to set this
to true and generate the stubs in one step; pass the disable arg to revert. -->
<!--<StrideDevPackages Condition="'$(StrideDevPackages)' == ''">true</StrideDevPackages>-->
</PropertyGroup>

<PropertyGroup Condition="'$(StridePlatform)' == 'Windows'">
Expand Down
14 changes: 13 additions & 1 deletion sources/sdk/Stride.Build.Sdk/Sdk/Stride.PackageInfo.targets
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,24 @@
@(NuGetPackOutput). Running only before GenerateNuspec lets the suffix reach PackTask
(the .nupkg is named correctly) but leaves @(NuGetPackOutput) pointing at the un-suffixed
path — StrideAutoPackDeploy then MSB3030s trying to copy a file that was never produced. -->
<!-- NuGetResolverModuleInitializerGenerate bakes $(PackageVersion) into the resolver's
module-init file at BeforeCompile, so we need the suffix appended before then too.
MSBuild silently ignores unknown target names in BeforeTargets, so projects without
the resolver target are unaffected. -->
<Target Name="StrideAppendWorktreeVersion"
DependsOnTargets="StrideEnsureWorktreeVersion"
BeforeTargets="_GetOutputItemsFromPack;GenerateNuspec">
BeforeTargets="_GetOutputItemsFromPack;GenerateNuspec;NuGetResolverModuleInitializerGenerate">
<PropertyGroup Condition="'$(StrideWorktreeSuffix)' != '' And !$(PackageVersion.EndsWith('$(StrideWorktreeSuffix)'))">
<PackageVersion>$(PackageVersion)$(StrideWorktreeSuffix)</PackageVersion>
</PropertyGroup>
</Target>

<!-- Also run during cross-project version queries: NuGet pack on a parent calls _GetProjectVersion
on each ProjectReference child to fill in dependency versions in the nuspec, and that target
depends on $(GetPackageVersionDependsOn). Without this, child returns the un-suffixed version
and consumers hit a Stride.A 4.4.0.1-devN -> Stride.B (>= 4.4.0.1) downgrade error. -->
<PropertyGroup>
<GetPackageVersionDependsOn>$(GetPackageVersionDependsOn);StrideAppendWorktreeVersion</GetPackageVersionDependsOn>
</PropertyGroup>

</Project>
2 changes: 1 addition & 1 deletion sources/shared/SharedAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ internal partial class PublicKeys
#else
public const string Default = "";
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ public static void SetupNuGet(List<(string targetFramework, string packageName,
var aname = new AssemblyName(eventArgs.Name);
if (aname.Name!.StartsWith("Microsoft.Build", StringComparison.Ordinal) && aname.Name != "Microsoft.Build.Locator")
return null;

if (assemblyNameToPath.TryGetValue(aname.Name, out var assemblyPath))
{
return Assembly.LoadFrom(assemblyPath);
Expand Down
70 changes: 70 additions & 0 deletions sources/shared/Stride.NuGetResolver/RestoreHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using NuGet.Commands;
using NuGet.Common;
using NuGet.Configuration;
Expand Down Expand Up @@ -51,6 +52,10 @@ bool TryCollectGraphicsApiDependentAssemblies(string assemblyFile)
{
if (libPaths.TryGetValue(ValueTuple.Create(lib.Name, lib.Version), out var libPath))
{
// Check if this package has a dev redirect
if (TryResolveDevRedirect(lib.Name, libPath, graphicsApi, assemblies))
continue;

foreach (var a in lib.RuntimeAssemblies)
{
var assemblyFile = Path.Combine(libPath, a.Path.Replace('/', Path.DirectorySeparatorChar));
Expand Down Expand Up @@ -80,6 +85,71 @@ bool TryCollectGraphicsApiDependentAssemblies(string assemblyFile)
return assemblies;
}

/// <summary>
/// Checks if a package has dev-redirect stub props and resolves assembly paths from HintPath.
/// </summary>
private static bool TryResolveDevRedirect(string packageName, string libPath, string graphicsApi, List<string> assemblies)
{
// Look for build/<PackageId>.props in the package folder
var propsPath = Path.Combine(libPath, "build", $"{packageName}.props");
if (!File.Exists(propsPath))
return false;

try
{
var doc = XDocument.Load(propsPath);
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";

// Check if this is a dev-redirect stub
var devRedirect = doc.Descendants(ns + "StrideDevRedirect").FirstOrDefault();
if (devRedirect?.Value != "true")
return false;

// Read StrideDevRoot and StrideDevConfiguration
var devRoot = doc.Descendants(ns + "StrideDevRoot").FirstOrDefault()?.Value ?? "";
var devConfig = doc.Descendants(ns + "StrideDevConfiguration").FirstOrDefault()?.Value ?? "Debug";

// Find all Reference HintPaths and resolve MSBuild properties
foreach (var reference in doc.Descendants(ns + "Reference"))
{
var hintPath = reference.Element(ns + "HintPath")?.Value;
if (hintPath == null)
continue;

// Resolve MSBuild-style properties. The graphicsApi arg drives the first probe
// (Direct3D11 by default on the editor path); fall back to other APIs whose
// dev DLL exists, so a Linux/macOS build using Vulkan/OpenGL still resolves.
hintPath = hintPath
.Replace("$(StrideDevRoot)", devRoot)
.Replace("$(StrideDevConfiguration)", devConfig);

if (hintPath.Contains("$(StrideGraphicsApi)"))
{
var probeOrder = new[] { graphicsApi, "Direct3D11", "Direct3D12", "Vulkan", "OpenGL", "OpenGLES" };
foreach (var api in probeOrder)
{
var candidate = hintPath.Replace("$(StrideGraphicsApi)", api);
if (File.Exists(candidate))
{
assemblies.Add(candidate);
break;
}
}
}
else if (File.Exists(hintPath))
{
assemblies.Add(hintPath);
}
}

return true;
}
catch
{
return false;
}
}

public static List<string> ListNativeLibs(LockFile lockFile)
{
var libs = new List<string>();
Expand Down
Loading
Loading