The ICU entry was inherited from the original package-name check (libicu-dev
on apt, libicu on dnf) and translated to a library probe without verifying
which library that should be. Three things were wrong with it.
libicui18n was never checked. The strings in libSystem.Globalization.Native.so
are exactly libicuuc and libicui18n, so probe both; libicudata arrives as a
dependency of libicuuc and the remaining ICU libraries are never referenced.
The probe accepted any SONAME down to .so.0, but the runtime's floor is
MinICUVersion 60. A host carrying only an older ICU passed the check and then
FailFast'd at startup. RHEL/CentOS 7 ships ICU 50 and is affected.
The ldconfig fast path bypassed the version range entirely, since a cache line
for libicuuc.so.50 still matches the "libicuuc.so" prefix, and it trusted a
stale cache: removing a library without refreshing the cache reported it
present. Drop it and ask the loader directly. dlopen consults the same cache
but answers the question we are actually asking, so this also removes the musl
special-case, where ldconfig exits 0 while producing nothing usable.
Add tzdata. It is data rather than a library, so no loader probe finds it, but
the event scheduler resolves configured zone IDs through TimeZoneInfo and
without /usr/share/zoneinfo every lookup except UTC throws. Slim containers
routinely omit it.
Move the rationale to dev-docs/platform-prerequisites.md so it is discoverable
without reading the build tool, covering what each dependency is for, why
InvariantGlobalization stays false, and the tzdata-legacy split on Debian 12
and Ubuntu 24.04 that hides aliases such as US/Eastern.
README no longer asks for libicu-dev. Matching the runtime package by pattern
('^libicu[0-9]+$') is version-independent without pulling in headers, so no
-dev package is required on any supported distribution.
Verified on Debian and Alpine: all present, each dependency removed
individually, a stale ldconfig cache, and ICU downgraded to .so.50.
ModernUO mandated -dev packages on production servers for one reason:
DllImport never asks for a versioned SONAME, so libdeflate.so.0 and
libargon2.so.1 sitting in /usr/lib went unfound and the -dev package's
unversioned symlink was the only thing making resolution work. That is
fixed in the binding packages, so pick them up and stop asking for
build tooling on machines that compile nothing.
LibDeflate.Bindings 1.0.3 -> 1.0.4
Argon2.Bindings 1.17.0 -> 1.19.0
Also drops zstd, on every platform including macOS. ZstdNet bundles
libzstd for linux-x64, linux-arm64, osx-x64, osx-arm64 and win, and
nothing shells out to the CLI. Verified: the 15 ManagedArchive
round-trip tests pass in a container with no zstd package installed.
NativeLibraryChecker now asks whether the loader can find each library
rather than whether a named package is installed. Package-name checks
are what forced -dev in the first place, and no hardcoded name works
for ICU anyway: its apt package is release-specific (libicu74 on 24.04,
libicu76 on Alpine, libicu77 on Fedora). ldconfig -p is version
agnostic and is the loader's own cache, so one code path replaces the
apt/dnf/generic split.
ldconfig is treated as a positive signal only. musl's exits 0 while
producing no usable cache, so trusting a negative reported every
library missing on Alpine when all were installed; anything it does not
vouch for is now dlopen'd before being called missing. That fallback
bounds the .so.N probe per library, because a single small bound
reports ICU missing when it is present.
CI installs runtime packages only, deliberately: installing -dev there
would restore the unversioned symlink and mask the very resolution this
depends on, so a regression would sail through.
Adds --check-prereqs. The interactive flow was the only path that ran
these checks, so there was no way to verify a deployment target from a
script or a container.
Audited the rest of the codebase for the same hazard. ModernUO's only
other native imports are ws2_32.dll, which is always present on
Windows, and libc in SocketHelper. libc is not affected even though
libc.so is a libc6-dev linker script: DllImport("libc") was verified to
resolve and call successfully on both glibc and musl with no -dev
package installed. Nothing else P/Invokes, and no code here registers a
DllImportResolver.
Verified with 1.0.4 and 1.19.0: build plus 810 Server.Tests and 642
UOContent.Tests, and --check-prereqs reporting correctly on Debian,
Ubuntu, Fedora and Alpine with only the runtime packages installed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Why
`IORingGroup` issues io_uring syscalls directly rather than linking `liburing`, so the package has
never been needed — but we ask operators to install it in the README, install it in CI, and check
for it in `build-tool`.
Verified against the **shipped** `IORingGroup` 1.0.9 assembly, not just the source:
| Symbol | Occurrences in `IORingGroup.dll` |
|---|---|
| `libc`, `libSystem.dylib`, `kernel32.dll`, `kernelbase.dll`, `ws2_32.dll` | present |
| `liburing` | **0** |
| `io_uring_queue_init` — liburing's entry point | **0** |
| `io_uring_setup` — the raw syscall | 1 |
If it linked liburing it would call `io_uring_queue_init` / `io_uring_submit`. It calls neither.
## What changes
Nine lines across three files, removing `liburing-dev` / `liburing-devel` from:
- `README.md` — both the dnf and apt prerequisite blocks
- `.github/workflows/build-test.yml` — both install steps
- `Projects/BuildTool/Prerequisites/NativeLibraryChecker.cs` — the cross-compile target text, the
apt and dnf package lists, and the `ldconfig` fallback map
Nothing else is touched. `zstd` and the `-dev` packages are a separate discussion and a separate PR.
## Risk
None to the build. `liburing` was only ever installed, never linked or loaded — removing it cannot
change resolution behaviour. `build-tool` builds clean.
This was found while investigating why Linux requires `-dev` packages at all; that fix lives in the
binding packages (modernuo/LibDeflate.Bindings#4, modernuo/Argon2.Bindings#13) and lands separately
once those publish. This piece is independent and unblocked, hence its own PR.
## Summary
Replaces the basic `publish.cmd`/`publish.sh` scripts with an interactive **BuildTool** — a C# console app using [Spectre.Console](https://spectreconsole.net/) that guides users through publishing, prerequisite checking, and cross-compilation.
### Why
The community found the existing publish scripts unhelpful for newcomers. They worked but didn't walk users through the process, didn't check prerequisites, and provided no feedback when things went wrong.
### What's New
**Interactive BuildTool** (`Projects/BuildTool/`)
- NativeAOT-compiled C# console app with true-color ASCII logo and ModernUO brand gold/silver palette
- Guided publish wizard with step-by-step back navigation (Ctrl+C or menu "Back" to go to previous step)
- Prerequisite checking: .NET SDK version, VC++ Redistributable (Windows), native libraries (Linux/macOS)
- .NET SDK auto-install offer via Microsoft's official install scripts
- Platform detection: Windows 10 vs 11 (build number), macOS codenames, Linux distro + kernel version
- Cross-compilation support: skips native library checks, shows target prerequisites after build
- Non-interactive mode for CI: `--config Release --skip-prereqs`
- Backward-compatible positional args: `publish.cmd release win x64` still works
**Shell Wrappers** (`publish.cmd`, `publish.ps1`, `publish.sh`)
- Try native BuildTool binary first (downloaded from GitHub Releases)
- Fall back to `dotnet run --project Projects/BuildTool` if unavailable
- SDK bootstrapping: offer to install .NET if not found
**CI/CD Updates**
- Build/test workflows target `Projects/Application/Application.csproj` instead of the solution (excludes BuildTool and test projects from publish)
- New `build-tool-release.yml` workflow builds NativeAOT binaries for win-x64, win-arm64, osx-arm64, linux-x64, linux-arm64
- Minimum SDK bumped to 10.0.201 (required for Serialization Generator 2.14.3 / Roslyn 5.3.0)
**Other Changes**
- Solution converted from `.sln` to `.slnx`
- Updated README with interactive mode instructions and deployment guidance
## Screenshots
<img width="320" height="378" alt="image" src="https://github.com/user-attachments/assets/83c057c5-3992-4dbd-99fa-0e3c24ef6428" />
<img width="749" height="554" alt="image" src="https://github.com/user-attachments/assets/e4ee4d6f-71d4-47f9-86b6-8fd1ca3c3e7a" />