Skip to content

Fix dnx ownership in native Linux packages - #7824

Merged
mthalman merged 13 commits into
dotnet:mainfrom
mthalman:mthalman-fix-dnx-ownership-vmr
Jul 25, 2026
Merged

Fix dnx ownership in native Linux packages#7824
mthalman merged 13 commits into
dotnet:mainfrom
mthalman:mthalman-fix-dnx-ownership-vmr

Conversation

@mthalman

@mthalman mthalman commented Jul 17, 2026

Copy link
Copy Markdown
Member

Summary

Released .NET 10 SDK native packages own the public dnx entries, which conflicts when package resolution upgrades the shared dotnet-host package to .NET 11. This makes the shared host authoritative for those entries while preserving side-by-side SDK 10 and SDK 11 use.

  • Extend Arcade RPM authoring with ghost files, file triggers, and post-uninstall script support.
  • Ship a private authoritative dispatcher in dotnet-host and repair the public dispatcher and symlink from package lifecycle scripts.
  • Use bounded DEB Replaces metadata plus path triggers, and RPM ghost ownership plus a transaction file trigger.
  • Clean generated public entries only when the final host package is removed.
  • Exercise the generated host package against the unpinned, repository-selected dotnet-sdk-10.0 package on Azure Linux 3 and Debian 12.

Design notes

The RPM package ghost-owns /usr/bin/dnx and /usr/share/dotnet/dnx so it can coexist with released SDK 10 packages without payload conflicts. A transaction file trigger repairs the entries after SDK installation or servicing.

The DEB package uses Replaces: dotnet-sdk-10.0 (<< 10.1.0) to transfer ownership only from affected SDK 10 packages. Its post-install script and path triggers provide the same idempotent repair behavior.

The lifecycle tests accept both the legacy SDK layout and a future SDK package that already uses the repaired layout. The current Azure Linux package exercises the legacy broken direct invocation, while the current Debian package verifies forward-compatible interoperability.

Testing

  • Arcade installer task tests: 13 passed.
  • VMR installer test project builds with zero warnings and errors.
  • RPM and DEB lifecycle tests passed through the xUnit Docker harness on Azure Linux 3 and Debian 12.
  • RPM ghost-to-real-file transition and package ordering scenarios passed on Fedora 42 and Azure Linux 3.
  • DEB install ordering, replacement bounds, reinstall, removal, and cleanup scenarios passed on Debian 12.

Note

This pull request description was generated with GitHub Copilot.

mthalman and others added 7 commits July 17, 2026 11:19
Extend Microsoft.DotNet.Build.Tasks.Installers so the RPM writer can express
package authoring that was previously impossible, in support of fixing the
dnx file-ownership conflict between dotnet-host 11 and released dotnet-sdk-10.0.

- RpmBuilder: emit RPMFILE_GHOST (flag 64) file header records that are omitted
  from the CPIO payload (AddGhostFile), and emit RPM file-trigger headers
  (AddFileTrigger) using the per-script / per-condition parallel-array layout
  that rpm expects (FileTriggerScripts/ScriptProg/ScriptFlags/Priorities and
  FileTriggerName/Version/Flags/Index).
- RpmHeaderTag: add the file-trigger header tags (5066-5072, 5084).
- CreateRpmPackage: add GhostFiles and FileTriggers task inputs. Ghost paths
  are harvested from the layout (mode/type/link target) and dropped from the
  payload; a declared ghost path missing from the payload is a hard error.
- installer.build.targets: wire the documented LinuxPostRemoveScript to the RPM
  Postun scriptlet (previously only the undocumented LinuxPostRmScript was
  wired), keeping LinuxPostRmScript as a compatibility alias and warning when
  both are set. Flow RpmGhostFile / RpmFileTrigger items into CreateRpmPackage.
- README: document RpmGhostFile, RpmFileTrigger, the DebControlFile 'triggers'
  usage, and the LinuxPostRemoveScript RPM Postun behavior.
- Add Microsoft.DotNet.Build.Tasks.Installers.Tests with round-trip coverage
  for ghost header records + CPIO omission, file-trigger header arrays, input
  validation, and the missing-ghost error path.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
…ntent

The Deb/Rpm packaging layout target (_CreateLinuxPackagingLayout) invokes the
project's PublishToDisk target with GenerateDeb/GenerateRpm intentionally stripped
via RemoveProperties, so a project cannot condition layout content on those
properties to emit files only into the native Linux packages (and not into
archives, the macOS pkg, or the MSI, which reuse PublishToDisk).

Pass an explicit _PublishLinuxPackageContent=true property from the Linux
packaging layout's PublishToDisk invocation. Because it is passed explicitly
(not a global property), it survives RemoveProperties and is set only for the
Deb/Rpm layout, letting a project lay out package-only repair content without
affecting other installer formats.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
Released dotnet-sdk-10.0 native Linux packages own /usr/share/dotnet/dnx and
/usr/bin/dnx, but with different file records than dotnet-host. When dependency
resolution pulls in dotnet-host 11 alongside dotnet-sdk-10.0 (for example during
a co-install or upgrade), RPM rejects the non-identical file overlap and the
installation fails. dotnet-host owns the authoritative, robust dnx dispatcher, so
the fix belongs here and must cover both RPM and DEB while preserving supported
SDK 10 / SDK 11 side-by-side use.

dotnet-host now ships a private, non-conflicting copy of the robust dispatcher at
/usr/share/dotnet/dnx.dispatcher (native Linux packages only, gated by
_PublishLinuxPackageContent) and uses it as the repair source for the public
entries:

- Shared scripts: dnx-postinst.sh idempotently restores /usr/share/dotnet/dnx from
  the private copy and points /usr/bin/dnx at it; dnx-postremove.sh removes the
  generated public entries only on final host removal. Both are safe for DEB
  (postinst configure/triggered, postrm remove/purge) and RPM (%post, %postun
  count) invocation conventions.
- DEB: bounded 'Replaces: dotnet-sdk-10.0 (<< 10.1.0)' lets the host take over the
  dnx files from released SDK 10 without a conflict while never affecting SDK 11,
  and a dpkg 'interest-noawait' path trigger on both dnx paths re-runs the repair
  when an SDK is installed/reinstalled after the host and overwrites them.
- RPM: the public paths are owned as %ghost entries (recorded in the header but
  omitted from the payload, since RPM rejects a non-identical dual-owned file) and
  a %filetriggerin on both paths re-runs the repair when an SDK install/reinstall
  touches them.

macOS, archive, and MSI behavior is unchanged. /usr/bin/dotnet is unchanged. With
the host installed, /usr/bin/dnx is a symlink to ../share/dotnet/dnx and
/usr/share/dotnet/dnx is the robust host dispatcher, so direct 'dnx' execution
works.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
…mission

Cover the two package-metadata mechanisms the host dnx ownership fix relies
on, in a form that runs cross-platform (no Docker required):

- CreateRpmPackageTests.PostInAndPostUnScripts_AreEmittedAsRpmScriptletHeaders:
  proves a Scripts item with Kind=Postun (wired from LinuxPostRemoveScript)
  produces a real RPMTAG_POSTUN header entry alongside RPMTAG_POSTIN, closing
  the Postun-compatibility gap for the final-removal cleanup scriptlet.
- CreateControlFileTests.ReplacesAdditionalProperty_IsEmittedVerbatim: proves
  a DebControlProperty named Replaces is written to the DEB control file
  verbatim, including the bounded version relation "dotnet-sdk-10.0 (<< 10.1.0)".

All 10 installer task tests pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
Use an RPM transaction file trigger so SDK payload extraction cannot overwrite the repaired dnx paths, and validate the generated host package metadata.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: bac0961d-290f-4081-ad4c-92ff0040f82d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7a5de763-fe9f-41ba-8033-7c6e08e2955b

Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Document the package lifecycle and test intent, promote the Linux package publication context to a public property, and remove unnecessary unsafe compilation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
@dotnet-policy-service
dotnet-policy-service Bot requested review from a team July 21, 2026 19:50
Preserve the Linux package dispatcher repair source alongside the Windows dnx.cmd publication added on main.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
@mthalman
mthalman marked this pull request as ready for review July 21, 2026 20:00
@mthalman
mthalman requested review from a team as code owners July 21, 2026 20:00
Copilot AI review requested due to automatic review settings July 21, 2026 20:00
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses Linux native package conflicts around the public dnx entry points when upgrading dotnet-host to .NET 11 while keeping released .NET 10 SDK packages installed. It makes dotnet-host authoritative for /usr/bin/dnx and /usr/share/dotnet/dnx (via RPM ghost ownership + triggers, and DEB bounded Replaces + triggers) and validates the behavior with new lifecycle tests.

Changes:

  • Extend Arcade RPM packaging to support ghost-owned paths and RPM file/transaction triggers, plus improved post-uninstall script wiring.
  • Update dotnet-host native Linux packaging to ship a private dispatcher (dnx.dispatcher) and repair/cleanup the public dnx entries via postinst/postrm + triggers.
  • Add installer/lifecycle test coverage to ensure dnx works across install/reinstall/remove ordering for RPM (Azure Linux) and DEB (Debian).

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/Microsoft.DotNet.Installer.Tests/LinuxInstallerTests.cs Adds DEB/RPM dnx lifecycle tests and validates host package metadata (ghost paths, triggers, bounded replaces).
test/Microsoft.DotNet.Installer.Tests/assets/dnx-package-lifecycle.sh Container-side script that exercises install/reinstall/remove ordering and asserts dnx repair/cleanup behavior.
src/runtime/src/installer/pkg/sfx/installers/triggers Registers dpkg path triggers to re-run dnx repair when the public paths change.
src/runtime/src/installer/pkg/sfx/installers/dotnet-host.proj Adds dnx.dispatcher, deb Replaces + triggers, rpm ghost ownership, and transaction trigger registration for dnx repair.
src/runtime/src/installer/pkg/sfx/installers/dnx-postinst.sh Idempotent repair script to restore robust dispatcher and ensure /usr/bin/dnx is a symlink.
src/runtime/src/installer/pkg/sfx/installers/dnx-postremove.sh Final-removal cleanup script to delete generated public dnx entry points.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmHeaderTag.cs Adds missing RPM header tag IDs for file triggers and transaction file triggers.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmBuilder.cs Implements ghost files and emits file trigger/transaction trigger parallel header arrays.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateRpmPackage.cs Plumbs ghost file harvesting and file trigger authoring through the RPM creation task.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers/README.md Documents IsPublishingToLinuxPackage, ghost files, and file trigger support; clarifies postrm/postun wiring.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj Adds InternalsVisibleTo for the new Installers test assembly.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.build.targets Sets IsPublishingToLinuxPackage=true, fixes RPM postun script wiring, and passes ghost files + triggers to RPM creation.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers.Tests/RpmBuilderTests.cs Unit tests for ghost file behavior and trigger header emission in RpmBuilder.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers.Tests/CreateRpmPackageTests.cs Integration-style tests ensuring CreateRpmPackage harvests ghost files and emits trigger/scriptlet headers.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers.Tests/CreateControlFileTests.cs Test for emitting bounded Replaces control properties verbatim.
src/arcade/src/Microsoft.DotNet.Build.Tasks.Installers.Tests/Microsoft.DotNet.Build.Tasks.Installers.Tests.csproj New test project for Installers task functionality.
src/arcade/Arcade.slnx Includes the new Installers tests project in the Arcade solution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/Microsoft.DotNet.Installer.Tests/LinuxInstallerTests.cs Outdated
Comment thread test/Microsoft.DotNet.Installer.Tests/LinuxInstallerTests.cs
mthalman added 2 commits July 22, 2026 10:17
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@mthalman

Copy link
Copy Markdown
Member Author

Reviewers: this will need to get in for Preview 7 so prioritize this appropriately.

@jkoritzinsky jkoritzinsky 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.

Some concerns on the MSBuild targets logic. Everything else looks good.

Comment thread src/runtime/src/installer/pkg/sfx/installers/dotnet-host.proj Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 235e2408-e09f-4748-868f-94768c6ea552
@mthalman
mthalman requested a review from jkoritzinsky July 24, 2026 21:03
@mthalman
mthalman merged commit ebcb113 into dotnet:main Jul 25, 2026
11 checks passed
@mthalman
mthalman deleted the mthalman-fix-dnx-ownership-vmr branch July 25, 2026 02:44
@mthalman

Copy link
Copy Markdown
Member Author

/backport to release/11.0.1xx-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0.1xx-preview7 (link to workflow run)

@github-actions

Copy link
Copy Markdown
Contributor

@mthalman backporting to release/11.0.1xx-preview7 failed, the patch most likely resulted in conflicts. Please backport manually!

git am output
$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch

Applying: Arcade installers: add RPM ghost files, file triggers, and Postun wiring
Applying: Add _PublishLinuxPackageContent gate for Linux-package-only layout content
Applying: Fix dnx file ownership in dotnet-host Linux packages
Using index info to reconstruct a base tree...
M	src/runtime/src/installer/pkg/sfx/installers/dotnet-host.proj
Falling back to patching base and 3-way merge...
Auto-merging src/runtime/src/installer/pkg/sfx/installers/dotnet-host.proj
CONFLICT (content): Merge conflict in src/runtime/src/installer/pkg/sfx/installers/dotnet-host.proj
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0003 Fix dnx file ownership in dotnet-host Linux packages
Error: The process '/usr/bin/git' failed with exit code 128

Link to workflow output

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants