Skip to content

Convert the solution to slnx - #14

Open
henrikottesorensen wants to merge 6 commits into
Notalib:mainfrom
henrikottesorensen:build/slnx
Open

Convert the solution to slnx#14
henrikottesorensen wants to merge 6 commits into
Notalib:mainfrom
henrikottesorensen:build/slnx

Conversation

@henrikottesorensen

Copy link
Copy Markdown
Collaborator

Stacks on #7

The first commit is that PR. Review only build: Convert the solution to slnx.

Merge #7 first and this reduces to a single commit.

dotnet sln migrate, plus the two things that fall out of it. 34 lines of GUIDs and per-configuration platform mappings become five lines:

<Solution>
  <Project Path="LibLouis.NET.Tables/LibLouis.NET.Tables.csproj" />
  <Project Path="LibLouis.NET.Test/LibLouis.NET.Test.csproj" />
  <Project Path="LibLouis.NET/LibLouis.NET.csproj" />
</Solution>

It stacks on #7 because of a hard SDK requirement

.slnx needs an SDK of 9.0.200 or later. main's workflow installs 8.0.x only, and SDK 8 cannot read the format at all — it has no migrate verb and fails with MSB4068: The element <Solution> is unrecognized. #7 is what puts a .NET 10 SDK in CI, so this has to follow it.

global.json declares the floor. Being honest about what that buys: on a machine with both SDKs installed, dotnet already picks the newer one, so nothing changes in practice; and an unsatisfiable global.json reports "The command could not be loaded", which is no clearer than MSB4068. The value is that the requirement is written down where someone would look for it, not that the failure becomes obvious.

runtime.liblouis.crosscompile.sln is deleted, not converted

Nothing loads it. The build scripts pack individual .csproj files, and grepping the repository finds no reference to it. It had also never been updated to include the two macOS runtime projects, so it had been quietly wrong for a while. Converting a file nobody opens would only have preserved that.

The only consumer of a solution file is build/build_managed_packages.sh, updated here.

Verified

build_managed_packages.sh builds, tests and packs through the .slnx, on both net8.0 and net10.0. global.json resolves to SDK 10.0.302.

Note for reviewers on older tooling: .slnx needs Visual Studio 17.14+, Rider 2025.1+, or the .NET 9.0.200+ CLI.

🤖 Generated with Claude Code

henrikosorensen and others added 6 commits August 5, 2026 11:25
Adds build/verify_native_binary.sh, called from pack_runtime_package, so a
binary that fails inspection never becomes a package.

Checks per RID:

- architecture matches the RID. A mis-targeted binary produces a package
  that restores fine and never loads.
- every P/Invoke symbol is exported. The expected list is read from the
  EntryPoint attributes in NativeMethod.cs rather than kept in the script,
  so it cannot drift away from what the wrapper imports.
- no dependency outside a per-platform allowlist. This is the check that
  catches the libgcc_s_dw2-1.dll class of bug, where a binary links against
  a toolchain runtime that the package does not ship: it works on the
  machine that built it and fails everywhere else.
- Linux only, the highest required glibc symbol version stays within a
  declared floor. The floor decides which distributions can consume the
  packages and is a property of the build image, so it can rise silently
  when that image is bumped. Currently 2.34, which covers RHEL 9,
  Debian 12 and Ubuntu 22.04. linux-x86 sits exactly on it.

Verified against all eight RIDs, and against two deliberately bad inputs:
an x86-64 binary declared as linux-arm64, and a win-x86 built without
-static-libgcc, which is the bug this repository actually shipped. Both
are rejected.

Note that bug only reproduces with the mingw gcc 10 on jammy; the gcc 13
on noble does not emit the dependency at all. -static-libgcc stays so the
output does not depend on which compiler the base image happens to ship.

llvm is added to the build image because binutils cannot read aarch64 PE.

Co-Authored-By: Claude Opus 5 <[email protected]>
Two problems, both found by running the script from outside the
repository while verifying the 3.38.0 bump.

The symbol check passed against an empty list. expected_symbols read
NativeMethod.cs and was called through command substitution, so when the
file could not be read it printed an error, exited its own subshell, and
left the caller to compare against nothing. The output read "all 0
P/Invoke symbols exported", which looks like a pass and asserts nothing.
The list is now resolved once in the main shell, where a missing or
unparsable NativeMethod.cs stops the script.

find_tool only looked where Linux distributions put things, so on macOS
the ELF checks could not run at all: command -v nm finds BSD nm, which
has no -D. It now takes several interchangeable names in preference
order and also searches Homebrew's keg-only prefixes, so llvm-readelf,
llvm-nm and llvm-readobj are used when present. Those read ELF, PE and
Mach-O alike, so with brew install llvm all eight RIDs can be verified on
a Mac without a container.

Verified: all eight RIDs pass natively on macOS, and a script run where
NativeMethod.cs is unreachable now exits 1 instead of reporting success.

Co-Authored-By: Claude Opus 5 <[email protected]>
CI failed fetching linux-libc-dev, a dependency of build-essential:

  E: Failed to fetch .../linux-libc-dev_5.15.0-187.197_amd64.deb  404

archive.ubuntu.com does not update its index and its pool atomically, so
a package version can still be listed after it has been removed, and the
fetch 404s. Nothing to do with the packages this image asks for; it is
luck, and a single apt-get run has none to spare.

Both apt steps now try three times, refreshing the index each time, since
a newer index is usually what resolves it. The explicit ok check is load
bearing: a bare loop that never succeeds still falls through, and the
layer would build with nothing installed and fail much later with
something unrecognisable.

Verified with --no-cache, which is the case that actually hits the
network.

Co-Authored-By: Claude Opus 5 <[email protected]>
LibLouis.NET and the test project build for both.

The extensions packages are referenced per target framework, 8.0.2 for
net8.0 and 10.0.10 for net10.0, so a net10.0 consumer does not drag an
older set into its dependency graph. The runtime packages stay on
netstandard2.0: they carry no managed code, and netstandard2.0 is the
widest thing to be compatible with. Microsoft.NET.Test.Sdk moves to
18.8.1, the first version that supports net10.0.

Packing a multi-targeted project runs the cross-targeting outer build,
which has no default None items, so the existing <None Update="LICENSE">
matched nothing there and the licence silently never reached the package
(NU5030). The inner builds do have the item, where a second Include would
be a duplicate, so both forms are present and conditioned on whether
TargetFramework is set.

The container's own SDK version is deliberately not touched here. It
compiles C and runs dotnet pack, and which SDK does the packing barely
affects the output; moving it belongs with the Dockerfile restructure
that separates compiling from packing, not with the multi-targeting.

Verified: both target frameworks build and pass, and the package carries
lib/net8.0 and lib/net10.0 with matching per-TFM dependency groups.

Co-Authored-By: Claude Opus 5 <[email protected]>
dotnet sln migrate, plus the two things that fall out of it.

runtime.liblouis.crosscompile.sln is deleted rather than converted.
Nothing loaded it: the build scripts pack individual csproj files, and it
had not been updated to include the macOS runtime projects, so it had
been quietly wrong for some time. Converting a file nobody opens would
only preserve that.

global.json declares an SDK floor of 9.0.200, the first release that
reads slnx. This is mostly documentation: on a machine with both SDKs
dotnet already picks the newer one, so nothing changes in practice. It
does not buy a good error either - an unsatisfiable global.json reports
that the command could not be loaded, which is no clearer than the
MSB4068 an old SDK gives on an slnx. The value is that the requirement is
written down where someone would look for it.

Stacks on the multi-targeting change because that is what puts a .NET 10
SDK in CI. On main the workflow installs 8.0.x only, which cannot read
slnx at all.

Verified: build_managed_packages.sh builds, tests and packs through the
slnx, on both target frameworks.

Co-Authored-By: Claude Opus 5 <[email protected]>
It broke the container build:

  A compatible .NET SDK was not found.
  process "/bin/sh -c sh ./build/build_metapackage.sh" exited 145

global.json applies to every dotnet invocation in the repository, and the
container runs sdk:8.0-jammy. I had reasoned about which SDK CI installs
and missed that the container has its own.

Nothing in the container needs a newer SDK. It packs individual project
files, which any SDK handles, and never loads the solution. Only
build_managed_packages.sh does, and that runs on a runner where
setup-dotnet provides .NET 10. The floor is documented instead.

Co-Authored-By: Claude Opus 5 <[email protected]>
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.

2 participants