Skip to content

Restore a single deployment root and .NET assembly resolution [4/4] - #406

Open
andrewiethoff wants to merge 23 commits into
gchudov:masterfrom
andrewiethoff:fix/net10-build-output-and-assembly-resolution
Open

Restore a single deployment root and .NET assembly resolution [4/4]#406
andrewiethoff wants to merge 23 commits into
gchudov:masterfrom
andrewiethoff:fix/net10-build-output-and-assembly-resolution

Conversation

@andrewiethoff

@andrewiethoff andrewiethoff commented Jul 31, 2026

Copy link
Copy Markdown

Merge order: 4 of 4

Depends on feat/accuraterip-meta-implementation (#405), and transitively on dotnet10-update (#403). Merge last.

Order PR Branch Depends on
1 #403 dotnet10-update
2 #404 security-audit #403
3 #405 feat/accuraterip-meta-implementation #403
4 #406 fix/net10-build-output-and-assembly-resolution #405

What this does

Three defects that surfaced with the .NET 10 port, all of them cases where .NET Framework used to supply something implicitly.

Build output layout

Projects wrote to bin\<cfg>\$(TargetFramework)\..., and the SDK appends the target framework even to an explicitly declared OutputPath. That was harmless while every runnable project produced a net47 output: bin\<cfg>\net47\ was then the one real deployment root and the other framework trees were unused byproducts. With executables on net10.0-windows, console tools on net10.0 and libraries on netstandard2.1, the root split into three, so the plugins folder no longer sat next to the running assembly and the GUI discovered only a fraction of its plugins.

Fixed by pinning the root through a new CueToolsBinRoot property in Directory.Build.props; every project opts into a subfolder of it and sets AppendTargetFrameworkToOutputPath=false. Both collect_files scripts stop merging several roots, and the WindowsMediaLib submodule patch is regenerated to match.

Plugin loading

CUEProcessorPlugins used Assembly.Load(AssemblyName), which worked only because .NET Framework populated AssemblyName.CodeBase and honoured it as a load hint. On .NET, CodeBase is not consulted and Load() resolves by name against the host's deps.json, so every plugin the host does not itself reference failed with FileNotFoundException. Replaced with Assembly.LoadFrom, which loads the file directly and also probes its directory for the plugin's own dependencies.

Executable startup

Upstream combined <probing privatePath="plugins"/> in app.config with a blanket <Private>False</Private> on project references. .NET ignores app.config assembly binding entirely, and Private=False keeps references out of deps.json — so eight executables could not start at all. The blanket ItemDefinitionGroup is removed from them. CUETools.Codecs.FLACCL instead sets Private per reference: a copy of CUETools.Codecs inside plugins\ would be picked up by the plugin scan and register the built-in codecs twice, while OpenCLNet does have to be copied next to the plugin.

Also fixes GetPluginLogo in the EAC plugin, which loaded its logo through Properties.Resources and so deserialized a Bitmap via BinaryFormatter — unsupported since .NET 9. It now reads the PNG manifest stream directly, and the project embeds that resource.

Verification

Debug and Release both build with 0 errors and no copy races. All 14 plugins load; libFLAC, MACLib, libwavpack, FLACCL and the built-in encoder each round trip bit-identically; every console tool starts; 99 tests pass; and collect_files.bat produces a distribution with no target-framework folders, no test assemblies and no pdbs.

Scope

1 commit, 65 files, +236 / −197.

Note on reviewing this stack

All four PRs target master, because a cross-fork pull request can only target a branch that exists in this repository. Until #403 and #405 merge, this diff also contains their commits.

andrewiethoff and others added 23 commits May 24, 2026 22:22
The provider was gated behind "no other release was found, or the search mode
is Extensive", mirroring how the now dormant FreeDB lookup had been wired as a
last resort. Since CTDB answers for virtually every recognised disc, that meant
AccurateRip Meta was never consulted on the initial lookup: its release, its
track metadata and its cover art only appeared after pressing Reload.

Treat it as a peer of the CTDB metadata sources instead - whenever CTDB is asked
for metadata, ask AccurateRip Meta too. A configured search mode of None still
suppresses both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSHyfpM8tS5fv5bX2fn5Lk
Three defects that surfaced with the .NET 10 migration, all of them cases where
.NET Framework used to provide something implicitly.

Build output layout. Projects wrote to bin\<cfg>\$(TargetFramework)\..., and the
SDK appends the target framework even to an explicitly declared OutputPath. That
was harmless while every runnable project produced a net47 output, because
bin\<cfg>\net47\ was then the one real deployment root and the other framework
trees were unused byproducts. With executables on net10.0-windows, console tools
on net10.0 and libraries on netstandard2.1 the root split into three, so the
plugin folder no longer sat next to the running assembly and the GUI found only
a fraction of its plugins. Pin the root through CueToolsBinRoot in
Directory.Build.props, have every project opt into a subfolder of it and set
AppendTargetFrameworkToOutputPath=false. Both collect_files scripts stop merging
several roots, and the WindowsMediaLib submodule patch is regenerated to match.

Plugin loading. CUEProcessorPlugins used Assembly.Load(AssemblyName), which
worked only because .NET Framework filled in AssemblyName.CodeBase and honoured
it as a load hint. On .NET, CodeBase is not consulted and Load() resolves by name
against the host's deps.json, so every plugin the host does not itself reference
failed with FileNotFoundException. Use Assembly.LoadFrom, which loads the file
directly and also probes its directory for the plugin's own dependencies.

Executable startup. Upstream combined <probing privatePath="plugins"/> in
app.config with <Private>False</Private> on project references; .NET ignores
app.config binding entirely, and Private=False keeps references out of
deps.json, so eight executables could not start at all. Drop the blanket
ItemDefinitionGroup from them. CUETools.Codecs.FLACCL keeps Private per
reference, because a copy of CUETools.Codecs inside plugins\ would be picked up
by the plugin scan and register the built-in codecs twice, while OpenCLNet does
have to be copied next to the plugin.

Also fixes GetPluginLogo in the EAC plugin, which loaded its logo through
Properties.Resources and so deserialized a Bitmap via BinaryFormatter -
unsupported since .NET 9. It now reads the PNG manifest stream directly, and the
project embeds that resource.

Verified on Debug and Release: unified layout, all 14 plugins load, libFLAC,
MACLib, libwavpack, FLACCL and the built-in encoder round trip bit-identically,
all console tools start, 99 tests pass and collect_files produces a clean
distribution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSHyfpM8tS5fv5bX2fn5Lk
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.

1 participant