Describe the issue
buildTransitive/Microsoft.AI.Foundry.Local.Core.targets pins the consuming project's RuntimeIdentifier to the build machine's RID. The file's entire contents:
<!-- default to the host machine's runtime identifier -->
<PropertyGroup Condition="'$(RuntimeIdentifier)'==''">
<RuntimeIdentifier>$(NETCoreSdkRuntimeIdentifier)</RuntimeIdentifier>
</PropertyGroup>
For a portable app that ships to more than one architecture this is silently destructive. It flips the whole project from portable to RID-specific, which changes how NuGet resolves native assets: they stop arriving as RuntimeTargetsCopyLocalItems destined for runtimes//native/ and instead arrive as NativeCopyLocalItems flattened into the output root, for one architecture only.
The failure is maximally delayed. The build succeeds, the installer succeeds, and the app dies on first launch on any machine whose architecture isn't the build agent's: because deps.json is still generated portable, with per-RID runtimeTargets entries. The host looks for e.g. runtimes/win-arm64/native/libSkiaSharp.dll, doesn't find it, falls back to the app directory, and loads the x64 copy the build left there. BadImageFormatException.
To reproduce
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="3.119.4" />
<!-- comment this line out for the "expected" result -->
<PackageReference Include="Microsoft.AI.Foundry.Local" Version="1.2.4" />
</ItemGroup>
</Project>
msbuild App.csproj -t:ResolvePackageAssets -getProperty:RuntimeIdentifier
msbuild App.csproj -t:ResolvePackageAssets -getItem:NativeCopyLocalItems,RuntimeTargetsCopyLocalItems
|
without the package |
with it |
| RuntimeIdentifier after targets |
(empty) |
win-x64 (the host's) |
| item group |
RuntimeTargetsCopyLocalItems |
NativeCopyLocalItems |
| DestinationSubPath |
runtimes\win-x64\native\libSkiaSharp.dll, runtimes\win-arm64\native\libSkiaSharp.dll |
libSkiaSharp.dll |
Note RuntimeIdentifier evaluates empty and only becomes non-empty after targets run, so -getProperty without -t: won't show it. MSBUILDLOGPROPERTYTRACKING=15 attributes it to the package's targets file.
Urgency
Why this is a packaging bug, not a preference
- A library shouldn't set a global property that redefines its consumer's build: asset resolution, output layout and deps.json all change, none of it requested.
- It's in buildTransitive, the widest possible reach, so it hits projects that never referenced the package. In our case the PackageReference was on a library; the damage landed in the application project several hops away.
- Defaulting to the build host's RID is wrong for anything built once and shipped to many architectures. CI agents are overwhelmingly x64; users increasingly are not.
- Condition="'$(RuntimeIdentifier)'==''" means it's invisible to anyone already setting a RID; i.e. it only fires on exactly the projects it will break.
Workaround:
This must be applied to every project that produces a payload, not just the one with the direct reference. The targets flow transitively through ProjectReference. Nothing is lost; that six-line targets file is the package's entire build contribution.
Platform and architecture
Windows ARM64
OS Version
Any
Installation type
Released package/binary
Foundry Local version
1.2.5
API or surface area
C# SDK
Hardware acceleration/backend
Other / Unknown
Backend/runtime version
Doesn't matter
Describe the issue
buildTransitive/Microsoft.AI.Foundry.Local.Core.targetspins the consuming project's RuntimeIdentifier to the build machine's RID. The file's entire contents:For a portable app that ships to more than one architecture this is silently destructive. It flips the whole project from portable to RID-specific, which changes how NuGet resolves native assets: they stop arriving as
RuntimeTargetsCopyLocalItemsdestined for runtimes//native/ and instead arrive asNativeCopyLocalItemsflattened into the output root, for one architecture only.The failure is maximally delayed. The build succeeds, the installer succeeds, and the app dies on first launch on any machine whose architecture isn't the build agent's: because deps.json is still generated portable, with per-RID runtimeTargets entries. The host looks for e.g. runtimes/win-arm64/native/libSkiaSharp.dll, doesn't find it, falls back to the app directory, and loads the x64 copy the build left there.
BadImageFormatException.To reproduce
Note RuntimeIdentifier evaluates empty and only becomes non-empty after targets run, so -getProperty without -t: won't show it. MSBUILDLOGPROPERTYTRACKING=15 attributes it to the package's targets file.
Urgency
Why this is a packaging bug, not a preference
Workaround:
This must be applied to every project that produces a payload, not just the one with the direct reference. The targets flow transitively through ProjectReference. Nothing is lost; that six-line targets file is the package's entire build contribution.
Platform and architecture
Windows ARM64
OS Version
Any
Installation type
Released package/binary
Foundry Local version
1.2.5
API or surface area
C# SDK
Hardware acceleration/backend
Other / Unknown
Backend/runtime version
Doesn't matter