Skip to content

codex: add CMake integration - #68

Merged
saidctb merged 15 commits into
mainfrom
cmake
Sep 13, 2026
Merged

saidctb merged 15 commits into
mainfrom
cmake

Conversation

@saidctb

@saidctb saidctb commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.23553% with 89 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
prik/cmake.py 80.38% 26 Missing and 15 partials ⚠️
prik/pipeline/build.py 80.48% 17 Missing and 7 partials ⚠️
prik/cli.py 76.71% 10 Missing and 7 partials ⚠️
prik/installation.py 87.27% 4 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

saidctb and others added 14 commits September 10, 2026 17:32
Two narrow correctness fixes in the standalone CLI -> CMake translation and
its CMake target properties; the pipeline stages themselves are unchanged.

generate --cmake resolved prebuilt objects, archives, and shared libraries to
absolute paths and then rewrote every absolute item back to a relative string,
so a real file path reached target_link_libraries() as a bare token and the
linker read it as a library name (-l../lib/libnative.a). Path-backed items now
render as "${CMAKE_CURRENT_LIST_DIR}/<relative>", keeping the three categories
distinct - object/archive/shared library to a path, named library to a name,
arg: to a linker argument - in their original order.

--native-library-dir became LINK_OPTIONS "-L<dir>", which carries no runtime
meaning, so an extension could link and then fail to import. prik_add_module()
gains LIBRARY_DIRS, mapped to target_link_directories() plus the target's
BUILD_RPATH, and generate --cmake emits LIBRARY_DIRS instead of a raw -L.
INSTALL_RPATH stays under project control.

Also fixes a bare Python float in the installed-wheel CMake test: PRIK's
documented scalar policy requires numpy.float64, and CI reached that assertion
once the isolated wheel install succeeded.

Changed: prik/cmake.py (link-item rendering, LIBRARY_DIRS emission),
cmake/UsePRIK.cmake (LIBRARY_DIRS argument, link directories, BUILD_RPATH),
CMake guide and CLI reference docs, CHANGELOG.

Tests: two end-to-end regressions in tests/fortran/infrastructure/building/
end_to_end/test_cmake_builds.py that configure, build, import, and call - one
linking a real prebuilt .o and .a from a sibling directory, one importing an
extension backed by a shared library in a non-system directory with no loader
path able to resolve it. The ordered-link-item ordering test is kept.

Verified: all five suite roots green (fortran -m "not real_library" 2534, c
646, docs 745, tools/workflows 64) plus the CI toolchain-smoke lane (8) and a
CMake-file rerun with LD_LIBRARY_PATH set. Static gate clean: ruff check and
format, version check, bandit, vulture, radon policy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NrtPVjqMKNLJ1cYsteWiKG
Three narrow fixes in the packaged CMake helper; the pipeline stages are
unchanged.

The native OBJECT target filtered LINK_LIBRARIES through if(TARGET ...), which
forwarded "debug Foo optimized Bar" as plain Foo and Bar with their keywords
stripped, so both configurations' usage requirements reached the native
sources at once, and dropped generator-expression entries entirely because
they are not targets. PRIK_LINK_LIBRARIES now passes through unchanged, so
CMake applies the caller's own selection rules. The extension target keeps the
same LINK_LIBRARIES for the final link.

CMake compiles .C as C++ while PRIK plans such a source as C, so the two would
disagree about the compiler. prik_add_module() and generate --cmake now reject
that suffix with a message naming the reason, and generation fails before a
project is written rather than at CMake configure time.

A module contributing native Fortran sources now checks that CMake's Fortran
language is enabled, instead of failing later with a less direct error.

Changed: cmake/UsePRIK.cmake (native link forwarding, suffix validation,
Fortran language check), prik/cmake.py (fail-fast .C rejection), CMake guide
docs, CHANGELOG.

Tests: five regressions in tests/fortran/infrastructure/building/end_to_end/
test_cmake_builds.py - a parametrized Debug/Release pair whose native source
#errors when the wrong configuration's definition arrives or the right one is
missing, .C rejection at both the helper and CLI level, and the Fortran
language diagnostic. All five fail against the pre-fix helper and pass with
it.

Verified: test_cmake_builds.py plus tests/docs 782 passed, CI toolchain-smoke
lane 8 passed, static gate clean (ruff check and format, version check,
bandit, vulture, radon policy).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NrtPVjqMKNLJ1cYsteWiKG
CMake configuration ran PRIK's whole semantic pipeline -- preprocessing,
parsing, semantic IR, policy completion, wrapper planning, C and Fortran
lowering, and a compiled Fortran type probe -- purely to discover which files
it would later generate, then ran all of it again at build time to actually
write them. Configuration now asks a structural question instead, and the
pipeline runs exactly once, during the build.

The structural query derives everything CMake needs from the module name and
its declared shape: the generated filenames, the link driver, and the
mandatory ABI flags its compiler profile states. Vendor detection is a lookup
on the driver's name, so no compiler runs. Querying a 300-procedure module's
plan drops from 2.54s to 0.41s, and the query no longer grows with source
size.

Configuration cannot read a source, so the generated file list must not depend
on what analysis finds. PRIK now names the optional compilation units up front
and always writes them: a module needing no collision adapter still gets
<module>_adapters.c, and a Fortran module needing no bridge still gets
bind_c_<module>_wrapper.f90. An unused unit holds a placeholder that defines
no symbol and compiles clean under -Wall -Wextra -pedantic -Werror. A semantic
edit therefore changes a file's contents, not the build graph, and never needs
a reconfigure. Configure-time and build-time calls share one function, so the
files CMake declares are exactly the files generation fills.

Transitive semantic inputs -- nested C headers, Fortran INCLUDE files,
imported contracts -- now reach CMake through a depfile written during
generation, replacing the CMAKE_CONFIGURE_DEPENDS wiring that existed because
a bridge source could appear or disappear. That uses add_custom_command
DEPFILE, so the packaged helper requires CMake 3.21; verified working under
both Ninja and Unix Makefiles.

UsePRIK.cmake drops from 560 to 482 lines: gone are the plan-tree path
rebasing, the per-compilation-unit JSON application, the semantic dependency
wiring, and every semantic JSON read. Compile flags now use compile-language
generator expressions, so no source properties remain and cross-target flag
leakage is structurally impossible. The internal heavy --plan mode had no
other consumer and is removed.

Changed: prik/naming/generated_files.py (new, sole owner of generated
filenames, replacing inline spellings in planning, codegen, and build
integration), prik/cmake.py (StructuralLayout), prik/cli.py (--cmake-plan,
--declared-layout, --depfile), prik/pipeline/build.py (placeholder units,
depfile writer, --plan removal), cmake/UsePRIK.cmake, CMake guide, CHANGELOG.

Tests: configure succeeds for a source PRIK cannot parse and fails only at
build time; the bridge and adapter filenames stay fixed while their contents
round-trip between placeholder and real without a reconfigure; placeholders
compile under strict conformance flags; an unchanged rebuild reruns nothing;
and --lto reaches both target kinds' compile lines and the link. The existing
nested-header and nested-INCLUDE tests now exercise the depfile. The obsolete
"reconfigures when a bridge appears" test is replaced by its inverse.

Verified: full CI-shaped suite 3990 passed, CMake suite 42 passed, static gate
clean. The installed-wheel test cannot run on this machine and is unchanged;
packaging picks up the new module through packages.find.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NrtPVjqMKNLJ1cYsteWiKG
Package the CMake helper inside the distribution as prik/cmake_modules so an
entry point can name it, and register that directory under cmake.module. A
scikit-build-core build then puts it on CMAKE_MODULE_PATH itself, so a project
needs only include(UsePRIK). A new PRIKConfig.cmake beside the helper loads it
for find_package(PRIK CONFIG REQUIRED), and both modules still install to
share/prik/cmake, so cmake_module_dir() and that prefix layout are unchanged.

Add `prik cmake-dir` and `prik install-dir` so a shell can substitute the
packaged module directory or PRIK's installation prefix into -DPRIK_DIR= and
-DCMAKE_PREFIX_PATH=. An installation prefix is not a CMake fact, so it lives
in the new prik/installation.py, and it reports that PRIK is not installed
rather than naming a prefix that holds none of its data.

Fail the Ubuntu and macOS unit-test jobs when CMake is missing, since the CMake
integration tests would otherwise deselect themselves and leave the job green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMz3kPhNruZUmNkDAMT9KJ
`pip install --user` writes data files under the user base rather than under
sys.prefix, so install_dir() missed that installation entirely and reported
PRIK as uninstalled. data_roots() now searches the user base as well, and
_helper_path() reaches a user-installed share/prik/cmake through the same list.

The root is only searched while the interpreter would import from the user site
at all. A virtual environment, -s, and -I all switch that off, and the data
there then belongs to an installation this interpreter cannot use, so naming
its prefix would point CMake at a different PRIK than the one answering.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMz3kPhNruZUmNkDAMT9KJ
examples/cmake/ builds one Fortran module through every route a CMake project
can use to reach PRIK: scikit-build-core's cmake.module entry point,
CMAKE_MODULE_PATH, PRIK_DIR, and an installation prefix. One CMakeLists.txt
serves all four; only the line that loads PRIK changes, selected by the
PRIK_DISCOVERY cache option.

check_discovery_routes.sh builds and calls the extension once per route and
reports a route whose prerequisite is missing rather than failing it, so the
script is also how a contributor checks a route in their own environment.

The CMake guide keeps the route documentation and gains the scikit-build-core
build-requirement snippet, rather than repeating the routes in a tutorial page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMz3kPhNruZUmNkDAMT9KJ
install_dir() searched candidate prefixes and returned the first holding
share/prik, so a global installation could answer for a `pip install --user`
one: -DCMAKE_PREFIX_PATH="$(prik install-dir)" would then point CMake at a
different PRIK than the one that ran. cmake-dir never had that problem, because
it starts from the imported package.

The prefix now comes from the running distribution's recorded data file,
located through importlib.metadata and resolved to the prefix above
share/prik/cmake. Both commands are exact about the same installation.

A distribution answers only when it provides the package running here, either
by recording it or by being an editable install over the tree containing it.
An editable install records no data files at all, so it reports that rather
than a prefix, and data_roots() remains only the last-resort search that finds
a packaged UsePRIK.cmake.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMz3kPhNruZUmNkDAMT9KJ
The section opened with the execute_process() lookup, which is the longest of
the routes and the one fewest projects need. It now opens with
find_package(PRIK CONFIG REQUIRED) configured by -DPRIK_DIR="$(prik cmake-dir)",
and the remaining routes follow under "Discovery alternatives": an installation
prefix, a module path, scikit-build-core, and asking a specific interpreter.

That last route keeps its example and gains the reason to reach for it: the
others answer for whichever prik the shell resolves, while it matches the
interpreter CMake itself selected. Its command becomes `-m prik cmake-dir`
rather than an inline import of cmake_module_dir().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMz3kPhNruZUmNkDAMT9KJ
The page had four headings, one of which carried three quarters of it: the
minimal project, every discovery route, the whole prik_add_module() surface as
continuous prose, and the regeneration contract all sat under "Existing CMake
project". It now moves from a minimal working project, to finding PRIK's CMake
modules, to the common options, to languages and flags, to why rebuilds behave,
to generating a standalone project.

Both option surfaces become tables: the discovery routes, and every
prik_add_module() keyword. The keyword table is checked against the helper's
own cmake_parse_arguments sets, which is how MODULE_DIRS and LINK_OPTIONS gain
a stated purpose rather than only a mention.

The front matter keeps the two-phase contract and defers the fixed file list
and dependency file to the new regeneration section, and the opening paragraph
now links the CLI build guide it assumes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMz3kPhNruZUmNkDAMT9KJ
"Every route above answers for whichever prik the shell resolves" was wrong for
scikit-build-core: that route never consults the shell. The backend reads the
cmake.module entry point from the PRIK installed in its own build environment,
which is the distribution named in build-system.requires and need not be the
one on PATH. The sentence now scopes the claim to the three command-line routes
and states what scikit-build-core uses instead.

The discovery table also gains the distinction a large project needs: PRIK_DIR
is package-specific, while CMAKE_PREFIX_PATH is the search path every
find_package() call shares.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMz3kPhNruZUmNkDAMT9KJ
@saidctb
saidctb merged commit 78e7d1f into main Sep 13, 2026
4 checks passed
@saidctb
saidctb deleted the cmake branch September 13, 2026 10:39
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