Skip to content

feat: conditional compilation, first feature — REGISTRATIONS builds out end to end - #65

Merged
kzangeli merged 2 commits into
mainfrom
feat/conditional-compilation-registrations
Sep 5, 2026
Merged

feat: conditional compilation, first feature — REGISTRATIONS builds out end to end#65
kzangeli merged 2 commits into
mainfrom
feat/conditional-compilation-registrations

Conversation

@kzangeli

@kzangeli kzangeli commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

The COR_FEATURE_* options have emitted -D flags since they were added, and no source read one — grep -c "ifdef COR_FEATURE" src was 0. The flags were a table of intentions. This makes the first of them real.

-DCOR_FEATURE_REGISTRATIONS=OFF now builds, links, starts and passes its suite. It drops the Context Source Registration, registration-subscription and EntityMap service routines, the forwarding library and both DB plugins' registration code: 23,936 bytes off .text.

What a compiled-out endpoint answers

The fifteen routes keep their table entry and lose their handler, through a macro that DISCARDS its argument:

#if COR_FEATURE_REGISTRATIONS
#  define REGS(handler)  handler
#else
#  define REGS(handler)  corNotInThisBuild
#endif

so the handler symbol is never referenced and its .c can leave the build. That is what makes the binary shrink; gating only the routing would keep every handler linked in.

The entry stays so the URL still matches, and the answer is 501, not the 404 an absent route would give. A 404 says the resource is not there and invites the client to fix its URL; 501 with the verb and path says the deployment declined the capability, and the client's move is a different deployment.

{
  "type":   "https://coraine.readthedocs.io/errors/NotAvailableInThisDeployment",
  "title":  "Not Available In This Build",
  "status": 501,
  "detail": "'POST /ngsi-ld/v1/csourceRegistrations' is not included in this build of coraine"
}

TS 104-176 § 6.3.2 registers no error type for that — the one 501 in the table, NoMultiTenantSupport, is reserved for a single capability — so the type URI is ours, carrying the name spec-doubt #124 proposes. One #define changes the day it is registered.

Two things the plan had not accounted for

The DB plugins needed the same treatment: mongoc.so failed dlopen with undefined symbol: mongocRegistrationUpdate, because its driver table assigned five functions its own CMakeLists had just excluded. The slots now stay NULL — the existing convention, and every db.registration* call site already checks it. And forwardingHttpRegister() in coraine.c.

#if, not #ifdef

CMakeLists now defines every feature to 1 or 0 and never leaves one absent, and -Wundef is on: a misspelt feature name is a compile error rather than a silently-false test that quietly compiles the code out. The same 0/1 makes a flag usable as a C value, which is how coraineFeatures.c reports the set without fifteen #ifdefs.

Asking a binary what it is

$ coraine --version
coraine 0.4.0
features: SUBSCRIPTIONS=1 REGISTRATIONS=0 GEOQ=1 ...

and GET /version gains a matching features object — a build with an endpoint compiled out is otherwise indistinguishable from a full one, and a client that gets a 501 can read the reason instead of guessing. Every feature is listed with its value; a missing name would not distinguish "off" from "too old to know the name".

The suite reads the same line and turns it into COR_TEST_FEATURES for corTest's REQUIRE_FEATURE / SKIP_FEATURE (SEAMWARE/corTest#2). 184 of the 640 cases need REGISTRATIONS; registrations_not_in_this_build.test carries SKIP_FEATURE and is the one that can only run without it. It pins all fifteen 501s, that an unknown URL is still 404, and that request validation runs first — a body that does not parse gets its 400 on a compiled-out endpoint too, so the 501 is the answer to a well-formed request rather than a blanket answer to the URL. Its last step is the point of the exercise: everything not gated by the feature still works.

Test

build result
full 640 tests, 640 passed
REGISTRATIONS=OFF 457 tests, 457 passed

457 = 640 − 184 + 1, and nothing outside the 184 failed.

make di CMAKE_FEATURES=-DCOR_FEATURE_REGISTRATIONS=OFF BUILD_DEBUG=<dir>

builds a reduced tree without turning the ordinary one into it — CMake caches what it is given, per build directory.

Merge SEAMWARE/corTest#2 first: without it REQUIRE_FEATURE is an unknown comment and the reduced build's suite runs the 184.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TGatXwrHx1CreL49sCuS37

…ut end to end

The COR_FEATURE_* options have emitted -D flags since they were added, and no
source read one: `grep -c "ifdef COR_FEATURE" src` was 0. The flags were a
table of intentions. This makes the first of them real.

`-DCOR_FEATURE_REGISTRATIONS=OFF` now builds, links, starts and passes its
suite. It drops the Context Source Registration, registration-subscription and
EntityMap service routines, the forwarding library and both DB plugins'
registration code: 23,936 bytes off .text.

WHAT A COMPILED-OUT ENDPOINT ANSWERS

The fifteen routes keep their table entry and lose their handler, through a
macro that DISCARDS its argument:

    #if COR_FEATURE_REGISTRATIONS
    #  define REGS(handler)  handler
    #else
    #  define REGS(handler)  corNotInThisBuild
    #endif

so the handler symbol is never referenced and its .c can leave the build. That
is what makes the binary shrink; gating only the routing would keep every
handler linked in.

The entry stays so the URL still MATCHES, and the answer is 501, not the 404 an
absent route would give. A 404 says the resource is not there and invites the
client to fix its URL; 501 with the verb and path says the deployment declined
the capability, and the client's move is a different deployment. TS 104-176
§ 6.3.2 registers no error type for that — the one 501 in the table,
NoMultiTenantSupport, is reserved for a single capability — so the type URI is
ours, carrying the name spec-doubt #124 proposes. One #define changes the day
it is registered.

TWO THINGS THE PLAN HAD NOT ACCOUNTED FOR

The DB plugins needed the same treatment: mongoc.so failed dlopen with
"undefined symbol: mongocRegistrationUpdate", because its driver table
assigned five functions its own CMakeLists had just excluded. The slots now
stay NULL, which is the existing convention and which every db.registration*
call site already checks. And forwardingHttpRegister() in coraine.c.

#if, NOT #ifdef

CMakeLists now defines every feature to 1 or 0 and never leaves one absent, and
-Wundef is on: a misspelt feature name is a compile error rather than a
silently-false test that quietly compiles the code out. The same 0/1 makes a
flag usable as a C value, which is how coraineFeatures.c reports the set
without fifteen #ifdefs.

ASKING A BINARY WHAT IT IS

`coraine --version` gains a "features: NAME=0|1 ..." line and GET /version a
"features" object — a build with an endpoint compiled out is otherwise
indistinguishable from a full one, and a client that gets a 501 can now read
the reason instead of guessing. Every feature is listed with its value; a
missing name would not distinguish "off" from "too old to know the name".

The suite reads the same line (corTestParams.sh) and turns it into
COR_TEST_FEATURES for corTest's REQUIRE_FEATURE / SKIP_FEATURE. 184 of the 640
cases need REGISTRATIONS and leave the run set on a build without it;
registrations_not_in_this_build.test carries SKIP_FEATURE and is the one that
can only run there. It pins all fifteen 501s, that an unknown URL is still 404,
and that request validation still runs FIRST — a body that does not parse gets
its 400 on a compiled-out endpoint too, so the 501 is the answer to a
well-formed request rather than a blanket answer to the URL. Its last step is
the point of the exercise: everything not gated by the feature still works.

    full build     640 tests, 640 passed
    REGISTRATIONS=OFF   457 tests, 457 passed

The 457 is 640 - 184 + 1, and nothing outside the 184 failed.

`make di CMAKE_FEATURES=-DCOR_FEATURE_REGISTRATIONS=OFF BUILD_DEBUG=<dir>`
builds a reduced tree without turning the ordinary one into it — CMake caches
what it is given, per build DIRECTORY.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGatXwrHx1CreL49sCuS37
@read-the-docs-community

read-the-docs-community Bot commented Sep 4, 2026

Copy link
Copy Markdown

Documentation build overview

📚 coraine | 🛠️ Build #34405599 | 📁 Comparing 0b8272c against latest (a64fe16)

  🔍 Preview build  

1 file changed
± building.html

The features object went onto /version because that was the endpoint that
existed. It does not belong there: /version answers "what am I talking to" and
is the small stateless health probe it was designed as, and the feature set is
the answer to a different question. So /version is back to product, version and
the linked-library commits — byte for byte what it was — and the new endpoint
answers the other one properly.

    GET /build

    {
      "product": "coraine", "version": "0.4.0",
      "build":    { "gitSha": …, "builtAt": …, "type": "Debug", "compiler": "GNU 15.2.0" },
      "features": { "REGISTRATIONS": false, … },
      "plugins":  { "directory": …, "built": [ … ], "loaded": { … } },
      "runtime":  { "distributed": false, "splitEntities": true, "httpEndpoint": … }
    }

THREE GROUPS, BECAUSE THEY CHANGE AT THREE DIFFERENT MOMENTS

That is the whole design, and it is what an operator chasing "why does this
deployment not do X" needs to know before deciding where to look:

  features  fixed when the source was compiled. If REGISTRATIONS is false, no
            flag, plugin or restart brings the registry API back.
  plugins   `built` is compile-time too, `loaded` is decided at startup from a
            directory this binary does not own.
  runtime   the command line, changed by a restart.

The last group earns its place: a broker with REGISTRATIONS compiled IN and
--distributed off accepts registrations and forwards nothing. Two separate
switches, and this is the only place both are visible at once.

BUILT IS NOT INSTALLED

`built` means the cmake run that produced this binary also produced that plugin
— not that the file is on disk. They are separate .so files loaded from a
directory the broker does not own, so a build can carry a plugin nobody
installed and an install can hold one from another build. The directory is
reported so `ls` answers the other half; scanning it from C would have reported
the INSTALL under a key that reads like the build.

NOT UNDER /admin

The admin API is itself a compile-time feature. An endpoint that reports what a
build contains must not be one of the things a build can leave out.

WHERE EACH FACT COMES FROM

Only cmake knows the flavour and the compiler it chose, so it passes those as
-D. Only the makefile knows the sha and the moment, so it generates
coraineBuild.h — the sibling of coraineStack.h, split from it because "what is
linked in" and "where this binary came from" are different questions. Neither
guesses at the other's half.

--version keeps its features line: the test harness needs an answer before a
broker starts, and that is the one question a broken installation still has to
be able to answer.

TEST

build_endpoint.test restarts the broker with --distributed --apiPlugins admin
and asserts the report follows. Without that step `runtime` could be four
constants and nothing would notice. registrations_not_in_this_build.test now
asks /build for its own REGISTRATIONS: false.

    full build          641 tests, 641 passed
    REGISTRATIONS=OFF   458 tests, 458 passed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGatXwrHx1CreL49sCuS37
@kzangeli

kzangeli commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator Author

Added GET /build — a dedicated endpoint for all of this, rather than riding on /version.

/version is back to product + version + stack, byte for byte what it was. It answers "what am I talking to"; the feature set answers a different question and now has its own place.

GET /build

{
  "product": "coraine", "version": "0.4.0",
  "build":    { "gitSha": "", "builtAt": "", "type": "Debug", "compiler": "GNU 15.2.0" },
  "features": { "REGISTRATIONS": false, },
  "plugins":  { "directory": "/opt/seamware/plugins", "built": [ ], "loaded": { } },
  "runtime":  { "distributed": false, "splitEntities": true, "httpEndpoint": "" }
}

Three groups, because they change at three different moments — which is what an operator chasing "why does this deployment not do X" needs to know before deciding where to look. A feature is fixed when the source was compiled; loaded is decided at startup from a directory the binary does not own; runtime changes with a restart. That last group earns its place: a broker with REGISTRATIONS compiled in and --distributed off accepts registrations and forwards nothing, and this is the only place both switches are visible at once.

built is not installed. It means the cmake run that produced this binary also produced that plugin — not that the file is on disk. directory is reported so ls answers the other half; scanning it from C would have reported the install under a key that reads like the build.

Not under /admin: the admin API is itself a compile-time feature, and the endpoint reporting what a build contains must not be one of the things a build can leave out.

Each fact comes from whoever knows it — cmake passes the flavour and compiler as -D, the makefile generates coraineBuild.h with the sha and timestamp (sibling of coraineStack.h, split because "what is linked in" and "where this came from" are different questions). --version keeps its features: line: the harness needs an answer before a broker starts.

build_endpoint.test restarts the broker with --distributed --apiPlugins admin and asserts the report follows — without that step runtime could be four constants and nothing would notice.

build result
full 641 tests, 641 passed
REGISTRATIONS=OFF 458 tests, 458 passed

The earlier red CI was the ordering: that run started nine hours before corTest#2 merged, so COR_TEST_FEATURES went unset, the markers were inert (feature-blind means run everything), and the SKIP_FEATURE test ran against a full build. Re-run against merged corTest main: green.

@kzangeli
kzangeli merged commit 91fb0b2 into main Sep 5, 2026
7 checks passed
@kzangeli
kzangeli deleted the feat/conditional-compilation-registrations branch September 5, 2026 09:25
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