dotnet / dotnet/msbuild

ClickOnce omits RID-specific native NuGet assets for .NET Framework projects

Open
#14,954 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
5.5k
Forks
1.5k
Avg merge
1d 8h
Merged PRs (30d)
141

Description

## Description

ClickOnce publishing a .NET Framework project can silently omit RID-specific native assets supplied by a NuGet package.

A normal build correctly copies the native asset to the output directory. ClickOnce publishing, however, includes the managed assemblies but omits the native DLL from both the published application directory and the ClickOnce application manifest. The installed application then fails with `DllNotFoundException`.

This was originally reported against ML.NET in dotnet/machinelearning#5906. The behavior still reproduces with ML.NET 5.0.0 and .NET Framework 4.8.

A related problem was addressed by dotnet/msbuild#5885. The [review discussion](https://github.com/dotnet/msbuild/pull/5885#discussion_r524437759) specifically considered SDK-style .NET Framework projects with native assets from NuGet `runtimes` directories. The decision at the time was to retain the existing .Net Framework behavior for compatibility and only include native references for .NET Core applications.

## Minimal reproduction

`Repro.csproj`:

```xml


Exe
net48
x64
win-x64

ClickOnce
publish\
true
Disk
false
1.0.0.0
false
true
false



```

`Program.cs`:

```csharp
using System;
using Microsoft.ML;
using Microsoft.ML.Data;

internal static class Program
{
private static void Main()
{
var mlContext = new MLContext(seed: 1);
var data = mlContext.Data.LoadFromEnumerable(new[]
{
new Example { Label = false, Features = new[] { 1f, 0f } },
new Example { Label = true, Features = new[] { 0f, 1f } },
});

mlContext.BinaryClassification.Trainers
.FieldAwareFactorizationMachine()
.Fit(data);

Console.WriteLine("Training succeeded.");
}

private sealed class Example
{
public bool Label { get; set; }

[VectorType(2)]
public float[] Features { get; set; }
}
}
```

Publish from a Visual Studio Developer PowerShell using .Net Framework MSBuild:

```powershell
msbuild Repro.csproj /t:Restore,Publish /p:Configuration=Release
```

Run the normal build output:

```powershell
.\bin\Release\net48\win-x64\Repro.exe
```

Output:

```text
Training succeeded.
```

The normal output directory contains:

```text
bin\Release\net48\win-x64\CpuMathNative.dll
```

Now run the executable from the ClickOnce application payload:

```powershell
.\publish\Application Files\Repro_1_0_0_0\Repro.exe
```

It fails with:

```text
Unhandled Exception: System.DllNotFoundException:
Unable to load DLL 'CpuMathNative': The specified module could not be found.
(Exception from HRESULT: 0x8007007E)

at Microsoft.ML.Internal.CpuMath.FactorizationMachine
.FieldAwareFactorizationMachineInterface
.CalculateIntermediateVariablesNative(...)
at Microsoft.ML.Trainers
.FieldAwareFactorizationMachineTrainer.TrainCore(...)
at Program.Main()
```

`CpuMathNative.dll` is absent from:

```text
publish\Application Files\Repro_1_0_0_0
```

It is also absent from:

```text
publish\Application Files\Repro_1_0_0_0\Repro.exe.manifest
```

## Expected behavior

RID-specific native NuGet assets with `CopyLocal=true` should be included in the ClickOnce application manifest and copied into the published application directory, as they are for a normal build and ordinary file-system publish.

## Analysis

NuGet correctly resolves this package asset as:

```text
NativeCopyLocalItems:
runtimes\win-x64\nativeassets\netstandard2.0\CpuMathNative.dll
AssetType=native
CopyLocal=true
DestinationSubPath=CpuMathNative.dll
```

The apparent failure sequence in `Microsoft.Common.CurrentVersion.targets` and `ResolveManifestFiles` is:

1. The asset enters `ReferenceCopyLocalPaths`, which is why normal build output contains it.
2. `_DeploymentReferencePaths` includes copy-local `.dll` files without excluding items whose `AssetType` is `native`.
3. `_ClickOnceRuntimeCopyLocalItems` initially includes `NativeCopyLocalItems`.
4. Items already in `_DeploymentReferencePaths` are removed from `_ClickOnceRuntimeCopyLocalItems`.
5. The native DLL is therefore passed through the managed-reference path rather than the ClickOnce loose-file path.
6. `ResolveManifestFiles.IsFiltered` deliberately filters DLLs without a managed assembly identity when `TargetFrameworkIdentifier` is `.NETFramework`.
7. The DLL is consequently present in neither the manifest dependency output nor the loose-file output.

This appears to be the remaining .Net Framework case discussed but intentionally excluded from dotnet/msbuild#5885.

## Possible fix

Native assets should remain in the ClickOnce loose-file collection rather than being classified as managed references. For example, `_DeploymentReferencePaths` could exclude `ReferenceCopyLocalPaths` items whose `AssetType` is `native`, or the deduplication step could avoid removing native items from `_ClickOnceRuntimeCopyLocalItems`.

As a proof of concept, adding the following target to the consuming project causes the DLL to appear in both the ClickOnce manifest and application directory, and the published application succeeds:

```xml




%(NativeCopyLocalItems.DestinationSubPath)
PreserveNewest


```

## Environment

```text
Windows 11 Enterprise 10.0.26100, x64
MSBuild 18.8.2.30814
.NET Framework 4.8
SDK-style project
PlatformTarget: x64
RuntimeIdentifier: win-x64
Microsoft.ML: 5.0.0
```

> [!NOTE]
> This issue was created with the help of GitHub Copilot.

Contributor guide

No contributing guide indexed for this repository

Research direction

Reproduce the failure with Repro.csproj using the provided MSBuild command and compare normal output with the ClickOnce payload. Start by tracing _DeploymentReferencePaths, _ClickOnceRuntimeCopyLocalItems, and ResolveManifestFiles in Microsoft.Common.CurrentVersion.targets. Done means the RID-specific native DLL appears in both the ClickOnce application directory and manifest, and the published executable runs successfully.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
build-system
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.