Conversation
libe3.pc carried LATREC_DEFAULT_DIR as a quoted string macro, and
pkg-config's Cflags tokenizer strips unescaped quotes. A consumer therefore
received the bare path and the preprocessor could not lex it:
error: expected expression before '/' token
The macro only appears when the library is configured with
LIBE3_ENABLE_LATREC=ON and LIBE3_BUILD_TESTS=ON -- the latter being the
option's own default -- so anyone who did not think to pass
-DLIBE3_BUILD_TESTS=OFF got a libe3.pc that broke their build.
No quoting fixes this. Escaping as \" does survive pkg-config and works for
CMake consumers reading *_CFLAGS_OTHER, but the same string then reaches a
plain `cc $(pkg-config --cflags libe3)` as an unterminated literal
("missing terminating \" character"). There is no form that is safe for both
consumption styles, so a string-valued macro must not be exported at all.
It never needed to be. latrec_open_in() applies the fallback from
src/core/latrec.c, which is libe3's own translation unit, so every caller of
latrec_tls_open_as() inherits the compiled-in default without seeing the
macro; nothing outside the library references it, and latrec.h already has
an #ifndef fallback to /tmp/latrec for anyone who does. Making it PRIVATE
keeps the build-tree default working for libe3's own tests and examples --
which is all it was ever for -- while removing it from the exported
interface. Exporting it was wrong on a second count anyway: it is a
build-tree path, meaningless to a consumer once the library is installed.
LIBE3_ENABLE_LATREC itself stays PUBLIC. It is a bare flag with no quotes to
mangle, and a consumer must see it or its own latrec_* calls resolve to the
no-op stubs while linking a library that has the ring registry compiled in.
Also guards the .pc generator against the whole class: it already fails
loudly on a generator expression, and now does the same for a quoted value,
so a future PUBLIC string macro cannot reintroduce this silently.
Verified with LIBE3_ENABLE_LATREC=ON and LIBE3_BUILD_TESTS=ON, the
configuration that used to break: the generated Cflags is
`-I${includedir} -DLIBE3_ENABLE_JSON -DLIBE3_ENABLE_ASN1 -DLIBE3_HAS_ZMQ=1
-DLIBE3_ENABLE_LATREC` with no quotes left to strip, libe3's own flags.make
still carries the private LATREC_DEFAULT_DIR, and ctest is 23/23.
Closes #71
Assisted-by: Claude Opus 5 (1M context)
… prefix
libe3.pc.in wrote `prefix=@CMAKE_INSTALL_PREFIX@`, a configure-time value, so
the installed file described where the build expected to go rather than where
it ended up. `cmake --install --prefix <other>` moves every file but cannot
rewrite the .pc, and neither can a DESTDIR-staged packaging step: the result
hands a consumer -I, -L and `smdir` under the original prefix, which may hold
no libe3 at all. pkg-config reports success while doing it, so the failure
surfaces later as a missing header from a directory nobody asked for.
That is not hypothetical. Installing this tree to a scratch prefix produced a
.pc still claiming prefix=/usr/local, with
`pkg-config --variable=smdir libe3` naming a service-model directory that did
not exist, while the real one sat under the install prefix.
pkg-config expands ${pcfiledir} to the directory holding the .pc, so the
prefix is derived from that and the file describes wherever it actually is.
The CMake package config has always been relocatable through @PACKAGE_INIT@;
this only makes the pkg-config path agree with it.
GNUInstallDirs permits any component directory to be absolute. Those are not
under the prefix and have nothing relative to derive, so they keep their
absolute value, and an absolute libdir also leaves the prefix with no relative
form -- that is where the .pc lands -- so it falls back to the absolute
prefix. The component loop also reads CMAKE_INSTALL_DATAROOTDIR rather than
CMAKE_INSTALL_DATADIR, whose cache entry is empty until GNUInstallDirs derives
it, which previously emitted `datarootdir=${prefix}/share` only by way of the
derived variable already being set.
Verified by installing to a non-default prefix and consuming from there:
--cflags/--libs/--variable=smdir all resolve under that prefix, a program
compiles and links through the shell-split `cc $(pkg-config ...)` path, the
reported smdir really contains sm_simple, a DESTDIR-staged install is
relocatable too, and ctest is 23/23.
Bumps VERSION to 0.1.3, covering this and the LATREC_DEFAULT_DIR export fix in
the preceding commit.
Assisted-by: Claude Opus 5 (1M context)
Thecave3
force-pushed
the
71-libe3pc-exports-latrec_default_dir-as-a-quoted-macro-which-pkg-config-strips-breaking-the-build
branch
from
August 28, 2026 15:41
3dc1e4c to
dad8089
Compare
Thecave3
marked this pull request as ready for review
August 28, 2026 15:44
Contributor
CI report —
|
| Workflow | Result | Time | Run |
|---|---|---|---|
| Commit policy | ✅ success | 1m48s | #100 |
| E2E dApp Integration | ✅ success | 2m14s | #112 |
| E2E Topologies (multi-dApp / multi-RAN) | ✅ success | 2m43s | #110 |
| Full-loop Latency Benchmark | ✅ success | 1m16s | #111 |
| Unit Tests | ✅ success | 4m38s | #138 |
| latrec portability | ⏭️ not triggered (paths filter) | — | — |
| MPMC Queue Benchmark | ⏭️ not triggered (paths filter) | — | — |
E2E Topologies (multi-dApp / multi-RAN)
zmq/ipc
- ✅ 1 RAN - 1 dApp: indications=
5dapppeer=t11 ran=ran-solo sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
- ✅ 1 RAN - 2 dApps: dApp#1 ind=
5sub=1, dApp#2 ind=6sub=2, RAN saw2dApps - ✅ 2 RANs - 1 dApp: from ran-a ind=
5, from ran-b ind=5
zmq/tcp
- ✅ 1 RAN - 1 dApp: indications=
5dapppeer=default ran=ran-solo sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
- ✅ 1 RAN - 2 dApps: dApp#1 ind=
5sub=1, dApp#2 ind=6sub=2, RAN saw2dApps - ✅ 2 RANs - 1 dApp: from ran-a ind=
5, from ran-b ind=5dapppeer=default ran=ran-a sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=1 max=1 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]dapppeer=off100 ran=ran-b sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=14.8 max=73 @seq=4) hist[<=1:4 2-5:0 6-10:0 >10:1] [note: indication queueing >1ms]⚠️ dappindication queueing exceeded 1ms (report-only)
E2E dApp Integration
✅ posix/ipc
- dApp exit:
0 - Indications received:
7
✅ posix/tcp
- dApp exit:
0 - Indications received:
7
✅ zmq/ipc
- dApp exit:
0 - Indications received:
7
✅ zmq/tcp
- dApp exit:
0 - Indications received:
7
Full-loop Latency Benchmark
Full-loop latency
Full-loop latency benchmark (N=1102 after 50 warmup)
All values in microseconds (μs). Link: zmq, transport: ipc, encoding: ASN.1 APER.
| # | Description | Tags | mean | p50 | p99 | max |
|---|---|---|---|---|---|---|
| 1 | Collect indication data | RECORD_BEGIN to ENCODE_E3SM_BEGIN |
0.12 | 0.10 | 0.35 | 0.60 |
| 2 | Create & encode indication | ENCODE_E3SM_BEGIN to ENCODE_E3SM_DONE |
1.10 | 1.07 | 3.36 | 3.80 |
| 3 | Encode E3AP (indication) | EMIT_ENTER to ENQUEUE, then DEQUEUE to ENCODE_E3AP_DONE |
2.61 | 2.90 | 3.80 | 7.50 |
| 4 | Queuing (indication) | ENQUEUE to DEQUEUE |
5.32 | 5.51 | 15.90 | 83.69 |
| 5 | Delivery (indication) | ENCODE_E3AP_DONE to SEND_DONE |
0.28 | 0.25 | 0.73 | 6.29 |
| 6 | E3 wire (RAN -> dApp) | SEND_DONE to RECV |
53.68 | 54.39 | 67.03 | 452.77 |
| 7 | Decode E3AP (indication) | RECV to DECODE_E3AP_DONE |
1.74 | 1.48 | 2.60 | 23.13 |
| 8 | libe3 dispatch (indication) | DECODE_E3AP_DONE to DELIVER_BEGIN |
0.10 | 0.10 | 0.15 | 0.42 |
| 9 | Decode indication | DELIVER_BEGIN to DECODE_E3SM_DONE |
0.64 | 0.57 | 0.91 | 1.34 |
| 10 | Process data | DECODE_E3SM_DONE to ENCODE_E3SM_BEGIN |
0.04 | 0.04 | 0.05 | 0.15 |
| 11 | Create & encode control | ENCODE_E3SM_BEGIN to ENCODE_E3SM_DONE |
0.36 | 0.35 | 0.55 | 0.75 |
| 12 | Encode E3AP (control) | EMIT_ENTER to ENQUEUE, then DEQUEUE to ENCODE_E3AP_DONE |
3.84 | 3.83 | 5.34 | 6.78 |
| 13 | Queuing (control) | ENQUEUE to DEQUEUE |
16.76 | 16.34 | 21.65 | 51.72 |
| 14 | Delivery (control) | ENCODE_E3AP_DONE to SEND_DONE |
5.53 | 5.26 | 8.48 | 20.03 |
| 15 | E3 wire (dApp -> RAN) | SEND_DONE to RECV |
54.31 | 54.59 | 63.37 | 650.26 |
| 16 | Decode E3AP (control) | RECV to DECODE_E3AP_DONE |
2.93 | 2.85 | 6.25 | 15.65 |
| 17 | libe3 dispatch (control) | DECODE_E3AP_DONE to DECODE_E3SM_BEGIN |
0.29 | 0.28 | 0.51 | 0.90 |
| 18 | Decode & handle control | DECODE_E3SM_BEGIN to DECODE_E3SM_DONE |
0.45 | 0.41 | 0.70 | 0.95 |
| Total | Total round-trip | 150.68 | 150.26 | 178.75 | 746.76 |
ubuntu-latest, Release build, ZMQ + IPC, ASN.1 APER.
These numbers are measured inside a GitHub Actions container and should be treated as an upper bound on E3AP's and the library's own latency, not a representative deployment measurement.
Ready to merge (fast-forward only)
A maintainer can land the reviewed commits with:
git fetch origin
git checkout main && git merge --ff-only dad8089f2fe1ca6f895b80efff42e10fb10bfa89 && git push origin mainHead: dad8089f2fe1ca6f895b80efff42e10fb10bfa89 (branch 71-libe3pc-exports-latrec_default_dir-as-a-quoted-macro-which-pkg-config-strips-breaking-the-build). If --ff-only fails as non-fast-forward, the branch must be rebased on the latest main.
One comment per PR, rewritten in place once every workflow for
dad8089finished.
Thecave3
deleted the
71-libe3pc-exports-latrec_default_dir-as-a-quoted-macro-which-pkg-config-strips-breaking-the-build
branch
August 28, 2026 17:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two
libe3.pcgeneration bugs. The first is #71; the second was found while verifying the fix for it, and has the same shape — a.pcconfidently describing a location that holds no libe3.VERSION0.1.2 → 0.1.3, folded into the second commit.1.
LATREC_DEFAULT_DIRwas exported as a quoted macro — closes #71pkg-config's Cflags tokenizer strips unescaped quotes, so a consumer received the bare path and the preprocessor could not lex it:
Only present when configured with
LIBE3_ENABLE_LATREC=ONandLIBE3_BUILD_TESTS=ON— the latter being that option's own default — so anyone who did not think to pass-DLIBE3_BUILD_TESTS=OFFgot alibe3.pcthat broke their build.No quoting fix exists. The issue offered either fixing the quoting or dropping the path; the quoting route was tested and cannot work:
libe3.pcpkg-config --cflagsemits-DD="/a/b"-DD=/a/b-DD='/a/b'-DD=/a/b-DD=\"/a/b\"-DD=\"/a/b\"…but that last form then reaches a plain
cc $(pkg-config --cflags libe3)as an unterminated literal —missing terminating " character. No single form is safe for both consumption styles, so a string-valued macro should not be exported at all.PRIVATEloses nothing.latrec_open_in()applies the fallback fromsrc/core/latrec.c, libe3's own translation unit, so every caller oflatrec_tls_open_as()inherits the compiled-in default without seeing the macro. Nothing intests/,examples/orbenchmarks/references it; the one example that opens a ring (examples/sm_simple/simple_service_model.hpp:209) goes throughlatrec_tls_open_as(); andlatrec.h:461already has an#ifndeffallback to/tmp/latrecfor anyone calling the inline path directly. The build-tree default therefore keeps working for libe3's own tests and examples, which is all it was ever for. Exporting it was wrong on a second count regardless: it is a build-tree path, meaningless to a consumer once installed.LIBE3_ENABLE_LATRECstaysPUBLIC— a bare flag with no quotes to mangle, and a consumer must see it or its ownlatrec_*calls resolve to the no-op stubs while linking a library that has the ring registry compiled in.The
.pcgenerator already failed loudly on a generator expression; it now does the same for a quoted value, so a futurePUBLICstring macro cannot reintroduce this silently.2.
libe3.pcbaked the configure-time prefixlibe3.pc.inwroteprefix=@CMAKE_INSTALL_PREFIX@, so the installed file described where the build expected to go rather than where it ended up.cmake --install --prefix <other>moves every file but cannot rewrite the.pc, and neither can aDESTDIR-staged packaging step. The consumer gets-I,-Landsmdirunder the original prefix — and pkg-config reports success while doing it, so the failure surfaces later as a missing header from a directory nobody asked for.Reproduced on this tree: installing to a scratch prefix produced a
.pcstill claimingprefix=/usr/local, withpkg-config --variable=smdir libe3naming a service-model directory that did not exist while the real one sat under the install prefix.The prefix is now derived from
${pcfiledir}, which pkg-config expands to the directory holding the.pc. The CMake package config has always been relocatable through@PACKAGE_INIT@— this only makes the pkg-config path agree with it.GNUInstallDirspermits any component directory to be absolute; those have nothing relative to derive and keep their absolute value, and an absolutelibdiralso leaves the prefix with no relative form (that is where the.pclands), so it falls back to the absolute prefix. The component loop also readsCMAKE_INSTALL_DATAROOTDIRrather thanCMAKE_INSTALL_DATADIR, whose cache entry is empty untilGNUInstallDirsderives it.Verification
Built with
LIBE3_ENABLE_LATREC=ONandLIBE3_BUILD_TESTS=ON— the configuration that used to break:Cflags: -I${includedir} -DLIBE3_ENABLE_JSON -DLIBE3_ENABLE_ASN1 -DLIBE3_HAS_ZMQ=1 -DLIBE3_ENABLE_LATREC— no quotes left to strip.build/CMakeFiles/libe3.dir/flags.makestill carries the privateLATREC_DEFAULT_DIR, so the internal default is intact.--cflags,--libsand--variable=smdirall resolve under that prefix; a program compiles and links through the shell-splitcc $(pkg-config ...)path; the reportedsmdirreally containssm_simple; aDESTDIR-staged install is relocatable too.ctest: 23/23 passed.scripts/check_commit_trailers.py: passes for both commits.