Skip to content

upnp: hard-fail when ENABLE_UPNP=YES but libupnp is missing - #511

Merged
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:pr-upnp-hard-fail
May 2, 2026
Merged

upnp: hard-fail when ENABLE_UPNP=YES but libupnp is missing#511
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:pr-upnp-hard-fail

Conversation

@got3nks

@got3nks got3nks commented May 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Mirror of #509 applied to cmake/upnp.cmake. When the user passes -DENABLE_UPNP=YES (or leaves the default-on option) but libupnp isn't present, the previous behaviour was to silently flip the flag to FALSE and emit a STATUS line — same anti-pattern #509 fixed for IP2Country: a green build with the feature mysteriously absent at runtime, with no error in the developer's terminal output.

Change

cmake/upnp.cmake's no-deps-and-no-fallback branch now emits FATAL_ERROR with platform-specific install hints:

ENABLE_UPNP=YES but libupnp was not found. Install libupnp headers
+ library (Debian/Ubuntu: libupnp-dev, Fedora: libupnp-devel,
macOS Homebrew: libupnp, MSYS2: mingw-w64-x86_64-pupnp), or pass
-DENABLE_UPNP=NO to disable the feature, or pass
-DDOWNLOAD_AND_BUILD_DEPS=YES to have CMake build libupnp from source.

The DOWNLOAD_AND_BUILD_DEPS fallback (where CMake builds libupnp from source via CmDaB_install("pupnp")) stays as a legitimate alternate fulfilment path — only the no-deps-and-no-fallback branch now errors out. The escape hatches the message points at (=NO / DOWNLOAD_AND_BUILD_DEPS=YES) are exactly the project's documented ways to satisfy the user-set flag.

Why

Per @Vollstrecker on #507 (comment):

If a user sets a feature to enable and the deps for it aren't found, we don't disable it and keep going, we error-out and the feature will be disabled by the user, or the deps is provided.

This change applies that policy to UPnP.

Companion files (out of scope here)

The same soft-fail anti-pattern lives in cmake/boost.cmake and cmake/nls.cmake. Will land as separate one-file PRs to keep each diff easily reviewable.

Test plan

  • configure with deps present (-DENABLE_UPNP=YES) — succeeds, UPnP enabled (verified locally on macOS Homebrew, libupnp 1.14.x).
  • configure with deps missing (-DENABLE_UPNP=YES) — fails with the new FATAL_ERROR message.
  • configure with -DENABLE_UPNP=NO — succeeds, no probing happens (top-level CMakeLists.txt:83 gates the include behind the option).

Mirror of amule-project#509 applied to cmake/upnp.cmake. When the user passes
-DENABLE_UPNP=YES (or leaves the option at its default-on) but libupnp
isn't present, the previous behaviour was to silently flip the flag to
FALSE and emit a STATUS line — same anti-pattern PR amule-project#509 fixed for
IP2Country: a green build with the feature mysteriously absent at
runtime, with no error in the developer's terminal output.

Replace the silent downgrade with a FATAL_ERROR carrying platform-
specific install hints. The DOWNLOAD_AND_BUILD_DEPS fallback (where
CMake builds libupnp from source) stays as a legitimate alternate
fulfilment path — only the no-deps-and-no-fallback branch now errors
out.

Per @Vollstrecker on amule-project#507 (comment):

  > If a user sets a feature to enable and the deps for it aren't found,
  > we don't disable it and keep going, we error-out and the feature
  > will be disabled by the user, or the deps is provided.

The same anti-pattern is also in cmake/boost.cmake and cmake/nls.cmake;
those land as separate one-file PRs to keep each diff easily reviewable.

Test plan:

* configure with deps present (-DENABLE_UPNP=YES) — succeeds, UPnP
  enabled (verified locally on macOS Homebrew).
* configure with deps missing (-DENABLE_UPNP=YES) — fails with the new
  FATAL_ERROR message naming libupnp-dev / libupnp / mingw-w64-x86_64-pupnp
  and pointing at the -DENABLE_UPNP=NO and -DDOWNLOAD_AND_BUILD_DEPS=YES
  escape hatches.
* configure with -DENABLE_UPNP=NO — succeeds, no probing happens
  (top-level CMakeLists.txt:83 gates the include behind the option).
@got3nks

got3nks commented May 2, 2026

Copy link
Copy Markdown
Contributor Author

Test plan completed locally on macOS Homebrew (Apple Silicon, libupnp 1.18.4):

# Test 1: deps present + ENABLE_UPNP=YES
$ cmake -B build -DENABLE_UPNP=YES
...
Should aMule be compiled with UPnP support?     YES
-- Configuring done

# Test 2: deps missing + ENABLE_UPNP=YES (PKG_CONFIG_LIBDIR=/dev/null
# + -DCMAKE_DISABLE_FIND_PACKAGE_UPNP=TRUE to hide libupnp)
$ env PKG_CONFIG_LIBDIR=/dev/null PKG_CONFIG_PATH= \
    cmake -B build -DENABLE_UPNP=YES \
        -DDOWNLOAD_AND_BUILD_DEPS=NO \
        -DCMAKE_DISABLE_FIND_PACKAGE_UPNP=TRUE
...
-- Checking for module 'libupnp'
--   Package 'libupnp' not found
CMake Error at cmake/upnp.cmake:65 (message):
  ENABLE_UPNP=YES but libupnp was not found.  Install libupnp headers +
  library (Debian/Ubuntu: libupnp-dev, Fedora: libupnp-devel, macOS Homebrew:
  libupnp, MSYS2: mingw-w64-x86_64-pupnp), or pass -DENABLE_UPNP=NO to
  disable the feature, or pass -DDOWNLOAD_AND_BUILD_DEPS=YES to have CMake
  build libupnp from source.

# Test 3: ENABLE_UPNP=NO (cmake/upnp.cmake should not be included)
$ cmake -B build -DENABLE_UPNP=NO
...
Should aMule be compiled with UPnP support?     NO
-- Configuring done

All three test-plan items now confirmed locally. Marking as checked.

@mrjimenez
mrjimenez merged commit f61e4b1 into amule-project:master May 2, 2026
12 checks passed
mrjimenez pushed a commit that referenced this pull request May 2, 2026
Mirror of #509 / #511 applied to cmake/nls.cmake. Two soft-fail spots
both flipped to FATAL_ERROR with platform-specific install hints:

1. msgfmt / msgmerge missing — fires on any platform without GNU
   gettext-tools installed. Hint covers Debian/Ubuntu (gettext),
   Fedora (gettext), macOS Homebrew (gettext), MSYS2
   (mingw-w64-x86_64-gettext).

2. libintl missing — fires only on platforms with a separate libintl
   runtime (macOS, MinGW/MSYS2, *BSD, musl). On glibc, libintl is part
   of libc itself and find_package(Intl) reports FOUND with empty
   Intl_LIBRARIES; the branch never fires there. Hint includes a
   note that hitting it on a glibc system means glibc-devel /
   libc6-dev is incomplete.

Both branches previously did 'set (ENABLE_NLS FALSE)' + STATUS
message, masking the missing dep behind a green build with no
localization at runtime — same anti-pattern PR #509 fixed for
IP2Country and #511 fixed for UPnP.

Per @Vollstrecker on #507 (comment):

  > If a user sets a feature to enable and the deps for it aren't found,
  > we don't disable it and keep going, we error-out and the feature
  > will be disabled by the user, or the deps is provided.

The same anti-pattern in cmake/boost.cmake will land as a separate
small PR (more nuanced — DOWNLOAD_AND_BUILD_DEPS interaction).

Test plan:

* configure with deps present (-DENABLE_NLS=YES) — succeeds, NLS
  enabled (verified locally on macOS Homebrew gettext).
* configure with msgfmt missing (-DENABLE_NLS=YES) — fails with the
  new FATAL_ERROR naming gettext per platform.
* configure with libintl missing on macOS/MinGW (-DENABLE_NLS=YES)
  — fails with the new FATAL_ERROR naming the runtime package.
* configure with -DENABLE_NLS=NO — succeeds, no probing happens
  (top-level CMakeLists.txt:79 gates the include behind the option).
mrjimenez pushed a commit that referenced this pull request May 2, 2026
Mirror of #509 / #511 / #512 applied to cmake/boost.cmake. Two
soft-fail spots both flipped to FATAL_ERROR:

1. asio probe failed (boost found but check_include_files for
   boost/system/error_code.hpp + boost/asio.hpp couldn't compile),
   no DOWNLOAD_AND_BUILD_DEPS opt-in. This is the realistic case —
   a user has libboost-dev installed but missing some transitive
   header / version mismatch / link-step issue tripped the probe.
   Previously: silently set ENABLE_BOOST=FALSE, falling back to
   wxWidgets sockets. Now: FATAL_ERROR with hint to install full
   boost-dev, or pass -DENABLE_BOOST=NO, or pass
   -DDOWNLOAD_AND_BUILD_DEPS=YES.

2. Boost_FOUND=FALSE — defensive branch, practically unreachable
   because find_package(Boost CONFIG REQUIRED) at the top of the
   file already errors out when boost is absent. Replacing the
   silent disable with FATAL_ERROR makes the assumption explicit:
   if a future edit ever drops the REQUIRED keyword, this branch
   surfaces the regression instead of producing a green build
   with no boost.

The DOWNLOAD_AND_BUILD_DEPS escape hatch (where CMake is expected
to build Boost itself elsewhere in the configure pass) is preserved
for branch 1 — when DAaBD is set, no error fires.

Per @Vollstrecker on #507 (comment):

  > If a user sets a feature to enable and the deps for it aren't found,
  > we don't disable it and keep going, we error-out and the feature
  > will be disabled by the user, or the deps is provided.

This completes the four-file cmake/<feature>.cmake hard-fail series
(ip2country in #509, upnp in #511, nls in #512, boost here).

Test plan:

* configure with deps present (-DENABLE_BOOST=YES) — succeeds, boost
  found, ASIO_SOCKETS=TRUE (verified locally on macOS Homebrew
  boost 1.90.0).
* configure with -DENABLE_BOOST=NO — succeeds, no probing happens
  (top-level CMakeLists.txt:71 gates the include behind the option).
* configure with deps missing (-DENABLE_BOOST=YES, no boost) — the
  REQUIRED keyword on find_package(Boost CONFIG) at line 1 errors
  out before our changes are reached; the new FATAL_ERROR for the
  asio-probe-failed case isn't triggerable without artificially
  installing a broken boost. Mechanically identical FATAL_ERROR
  shape to #509 / #511 / #512.
got3nks added a commit to got3nks/amule that referenced this pull request May 2, 2026
…3nks

mrjimenez flagged on PR amule-project#514 review that these three contributors
were missing from the roster:

* Werner Mahr / Vollstrecker — has merge access on amule-project/amule
  and merges incoming PRs (e.g. amule-project#430, amule-project#385, amule-project#319, amule-project#330, amule-project#286). Goes
  under Maintainers based on that role.
* Pablo Barciela / Sc0w — recent contributor, e.g. wxWidgets 3.2.6
  build fixes, GTK version-check cleanup, dropping gtk1/gtk2 support,
  Chinese translation work. Goes under Developers.
* got3nks — Wayland app_id binding + SNI tray (amule-project#508), macOS UX
  (amule-project#508), libmaxminddb CI fix (amule-project#507), ip2country/upnp/nls/boost
  hard-fail series (amule-project#509/amule-project#511/amule-project#512/amule-project#513), packaging (amule-project#510),
  docs modernize (this PR). Goes under Developers.

Other recent folks may be missing too — those can be added in
follow-up commits as they're identified. mrjimenez explicitly noted
this is the priority subset to not block the rename PR.
mrjimenez pushed a commit that referenced this pull request May 2, 2026
…3nks

mrjimenez flagged on PR #514 review that these three contributors
were missing from the roster:

* Werner Mahr / Vollstrecker — has merge access on amule-project/amule
  and merges incoming PRs (e.g. #430, #385, #319, #330, #286). Goes
  under Maintainers based on that role.
* Pablo Barciela / Sc0w — recent contributor, e.g. wxWidgets 3.2.6
  build fixes, GTK version-check cleanup, dropping gtk1/gtk2 support,
  Chinese translation work. Goes under Developers.
* got3nks — Wayland app_id binding + SNI tray (#508), macOS UX
  (#508), libmaxminddb CI fix (#507), ip2country/upnp/nls/boost
  hard-fail series (#509/#511/#512/#513), packaging (#510),
  docs modernize (this PR). Goes under Developers.

Other recent folks may be missing too — those can be added in
follow-up commits as they're identified. mrjimenez explicitly noted
this is the priority subset to not block the rename PR.
@got3nks
got3nks deleted the pr-upnp-hard-fail branch May 2, 2026 14:31
mrjimenez pushed a commit that referenced this pull request May 2, 2026
…lity

The libupnp 1.18.4 cmake build hard-codes lib64 as the install
directory on both x86_64 and aarch64 — its CMakeLists doesn't go
through GNUInstallDirs the usual biarch-aware way. As a result
libupnp.pc + libupnp.so end up at /app/lib64/, but Flatpak's
downstream PKG_CONFIG_PATH only searches /app/lib/pkgconfig/.

aMule's cmake/upnp.cmake then can't find libupnp via
pkg_check_modules and (since #511 made ENABLE_UPNP=YES a hard
requirement) errors out. Before #511 this silently produced a
Flatpak with no UPnP support — symptom went unnoticed because the
existing test plan didn't cover UPnP port forwarding from inside
the sandbox.

Fix: pass -DCMAKE_INSTALL_LIBDIR=lib to libupnp's config-opts.
Same pattern already applied to the ayatana-* libs further down in
the manifest for the identical reason. libupnp.pc now lands at
/app/lib/pkgconfig/libupnp.pc, pkg-config finds it, ENABLE_UPNP
resolves true, the build completes.

Affected runs: the first amule-project/amule:master Packaging run
after #510-#514 merged (https://github.com/amule-project/amule/actions/runs/25253858400)
where Flatpak (aarch64) failed at the amule cmake step with the
new #511 FATAL_ERROR. Verified locally that the same regression
existed on x86_64 too.
got3nks added a commit to got3nks/amule that referenced this pull request Jul 17, 2026
… ids

The remote GUI creates a result tab immediately on search with an optimistic
placeholder id, then rekeys it to the daemon-allocated search id when the
EC_OP_SEARCH_START reply arrives (RemapSearch). The placeholder and the daemon's
ed2k ids were both plain sequential bottom-half integers sharing one range, so
once the two counters drifted apart (another EC client searching, a reconnect,
etc.) a later search's placeholder could numerically equal an earlier tab's
daemon id. RekeySearch then matched the wrong tab, routing results to the wrong
tab and eventually corrupting the tab->id map so searches silently stopped
working (cleared only by restarting amulegui). Kad searches are affected too:
the placeholder is what collides, not the eventual (top-half) Kad id.

Partition the shared id space into three provably-disjoint ranges:
- daemon ed2k ids: low quarter [1, 0x3fffffff] (AllocateEd2kId now & 0x3fffffff)
- remote GUI placeholder tab ids: [0x40000000, 0x7fffffff] (bit 30 set)
- Kad ids: top half [0x80000000, ...]
so a placeholder can never equal any daemon id and RekeySearch always matches
exactly the tab that owns it. Applies to all search types.

ed2k ids are ephemeral (a 20-entry LRU ring), so the ed2k counter wrapping back
to 1 after ~1.07e9 searches only reuses ids whose search was evicted long
before - no live collision from the narrower range.

Remote-GUI + daemon only. The monolithic build uses the id directly (no remap)
and is unaffected. amulecmd and amuleapi address searches by the daemon id with
no placeholder/rekey step; they just consume whatever id the daemon returns.

Regression from the multi-search work (amule-project#511).
got3nks added a commit to got3nks/amule that referenced this pull request Jul 17, 2026
… ids

The remote GUI creates a result tab immediately on search with an optimistic
placeholder id, then rekeys it to the daemon-allocated search id when the
EC_OP_SEARCH_START reply arrives (RemapSearch). The placeholder and the daemon's
ed2k ids were both plain sequential bottom-half integers sharing one range, so
once the two counters drifted apart (another EC client searching, a reconnect,
etc.) a later search's placeholder could numerically equal an earlier tab's
daemon id. RekeySearch then matched the wrong tab, routing results to the wrong
tab and eventually corrupting the tab->id map so searches silently stopped
working (cleared only by restarting amulegui). Kad searches are affected too:
the placeholder is what collides, not the eventual (top-half) Kad id.

Partition the shared id space into three provably-disjoint ranges:
- daemon ed2k ids: low quarter [1, 0x3fffffff] (AllocateEd2kId now & 0x3fffffff)
- remote GUI placeholder tab ids: [0x40000000, 0x7fffffff] (bit 30 set)
- Kad ids: top half [0x80000000, ...]
so a placeholder can never equal any daemon id and RekeySearch always matches
exactly the tab that owns it. Applies to all search types.

ed2k ids are ephemeral (a 20-entry LRU ring), so the ed2k counter wrapping back
to 1 after ~1.07e9 searches only reuses ids whose search was evicted long
before - no live collision from the narrower range.

Remote-GUI + daemon only. The monolithic build uses the id directly (no remap)
and is unaffected. amulecmd and amuleapi address searches by the daemon id with
no placeholder/rekey step; they just consume whatever id the daemon returns.

Regression from the multi-search work (amule-project#511).
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