Skip to content

Proposed external project scaffold - #2

Draft
Lestropie wants to merge 5 commits into
mainfrom
dev
Draft

Proposed external project scaffold#2
Lestropie wants to merge 5 commits into
mainfrom
dev

Conversation

@Lestropie

Copy link
Copy Markdown
Member

Attempt at providing a comprehensive solution for MRtrix3 external projects, fully resolving MRtrix3/mrtrix3#2901.


From discussion in MRtrix3/mrtrix3#2901 it was very clear that there were different preconceptions about how external projects would work. This was split down the legacy MRtrix3 design vs. cmake philosophies:

  1. User has MRtrix3 built somewhere on their system; an external project lives somewhere else on the filesystem, but "makes use of" the MRtrix core to build & link against. Relies on user ensuring compatibility between external project code and the MRtrix3 version against which it builds & compiles.

  2. Self-contained external project build. In building the external project, MRtrix3 is imported into the build directory as a third-party dependency against which the external project source code is built. The external project gets to nominate the precise version of MRtrix3 referenced. Ideally the external project can also nominate what components of MRtrix3 are required, so that each individual tiny external project does not necessitate its own comprehensive MRtrix3 build.

I don't think there was going to be a quick consensus of which of these to pursue as they serve different users. So given the success I've been having with Claude, I thought I would set it at trying to provide both.

Obviously this means requiring a decent amount of code just to specify the build system, which can then bloat small external projects. So what I've aimed for here is not a template so much as a scaffold. An external project provides within it just a small set of prerequisites, and then bootstraps the cmake build system of the scaffold to do the heavy lifting.

I have made modifications to the MRtrix_demo_code repository, which duplicates the sample code presented in the NeuroImage manuscript, to conform to 3.1 layout, in MRtrix3/MRtrix3_demo_code#1. The CI Actions within this repository then use that code as an exemplar external project, building it under a suite of different use cases:

  • Against a pre-existing MRtrix3 versus utilising as an internal third-party dependency;
  • With different sets of commands in the external project, which may modulate how much of MRtrix3 is built;
  • As container.

Hopefully the suitability of the solution for all invested parties can therefore be determined from this set of demonstrations.

Note that for the case of building against an existing MRtrix3 elsewhere on the filesystem, it has to happen against the MRtrix3 source tree and build directory (for the shared library) rather than the installed location, given we don't copy source code into the installation directory; this precludes use of cmake accepted best practise.

This solution additionally depends on MRtrix3 modifications provided in MRtrix3/mrtrix3#3393.


Far from exhaustive:

  • Update CI set of tests to ensure that all use cases are truly covered (i.e. single C++ command vs. multiple C++ commands vs. Python command w. limited dependencies vs. Python command w. unknown dependencies) and that expectations on the build derivatives are satisfied (eg. presence of MRtrix3 compiled commands, presence of project shared library, size of MRtrix3 shared library)
  • Utilise parallel modifications to Python command build to manually specify MRtrix3 command dependencies of each MRtrix3 Python command and modulate which targets are built accordingly. This will include modification to Modify code and layout for 3.1 MRtrix3_demo_code#1 to restrict build targets of exemplar external project Python command, and should result in removal of external projects
  • Assess features of build system that must act as a "template" for external project builds. This is the "bootstrapping" of the proposed scaffold. While the corresponding requirements will probably be merged into Modify code and layout for 3.1 MRtrix3_demo_code#1, it should also appear as a genuine "template" for people to start from, along with instructions.
  • Investigate whether a CI test of the Apptainer image can be performed.
  • Investigate the current "splicing" mechanism and see whether it can be achieved using exclusively cmake rather than a Bash script.

Lestropie and others added 4 commits June 6, 2026 22:48
Provides the CMake build system, container recipes (Docker/Apptainer) and CI for building C++ and
Python commands written against the MRtrix3 API as a separate project. The repository contains the
build system but no command source: a project repository contributes only its source (cpp/cmd,
cpp/core, python/<project>/commands), which is spliced onto this scaffold at build time via
testing/splice-project.sh (CI splices the MRtrix3_demo_code dev branch).

Supports vendored (FetchContent a pinned MRtrix3) and existing (link an existing build tree) modes,
build-directory and installed execution, single (static binary) and multi-command builds. MRtrix3 is
nested as a self-contained <prefix>/mrtrix3 subtree (via MRTRIX_SUBPROJECT_INSTALL_PREFIX) so its
commands never shadow a separate MRtrix3 installation on the user's PATH.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reworks the external MRtrix3 project template so its build system and CI
match their intended contracts and so the scaffold carries no project-specific
identity. The static-versus-shared decision now follows the total number of
C++ binaries (the project's own commands plus the MRtrix3 commands being
built); when more than one exists a single shared libmrtrix-core.so is linked
by every binary, and a latent install-tree RPATH bug is fixed. The
verification harness gains assertions for linkage form, the exact set of
bundled MRtrix3 commands, PATH non-shadowing with version-matched
run.command() resolution, and dual-version reporting, wired through a
restructured CI matrix. The project name and Python package boilerplate are
now generated from a project.cmake metadata file supplied by the project
repository, with all demo-specific references confined to the CI workflow.

Session prompts:
1. > Changes to CI Actions:
   > 1. Validate and possibly refine static build logic. CI tests should ensure that if there is only a single C++ binary to build (ie. there are not any Python commands that may invoke MRtrix3 binaries that must also be built), then the resulting binary is built static, and there is no shared library created, whereas with more than one C++ binary (whether because the external project includes multiple C++ commands or because the total number of C++ commands combining the external project and the set of MRtrix3 commands to be built exceeds one), ensure that a shared library file is built and utilised. Insert additional tests if required to cover all cases.
   > 2. Have check variants that either specify a subset of MRtrix3 commands to build, or provide no such specification. In each case have the CI check ensure that the correct set of MRtrix3 commands are built.
   > 3. For checks other than containers, include checks that a compiled MRtrix3 command does not erroneously supersede another executable within PATH, and that if invoked via function run.command() it is always the project's version of the MRtrix3 command that is invoked rather than another executable of the same name within PATH.
   > 4. Include within checks verification that the reported versions of both the external project and the version of MRtrix3 against which it is built match expectation.
   > 5. Generalise the external project scaffold to make it applicable to projects beyond the demo code repository. There should be no explicit reference to the demo code repository except for inside the CI Actions. Report on the requisite changes to achieve this and pose question to user if ambiguity in best solution.

Generated-by: Claude Opus 4.8 <noreply@anthropic.com>
Refines the external-project scaffold across three fronts. CI now validates
that an example project whose core is split across a header and a compiled
source produces a project-specific shared library (carrying the project's
own core symbol) over and above the MRtrix3 core library. A new
manual-dispatch scenario covers an MRtrix3 Python command requested as a
dependency, where the unknowable run-time C++ dependencies force the whole
MRtrix3 C++ command set to be built, and the verification mechanics are
extended to assert their presence; a companion document assesses
finer-grained per-command dependency strategies. The generated per-project
app.py re-export shim is eliminated by having each launcher import MRtrix3's
application machinery directly from the nested subtree, with the
reassessment documented.

Session prompts:
1. > Refinements to MRtrix3 external project scaffold:
   > 1. "dev" branch of "demo_code" repository has been modified by splitting part of file cpp/core/algo/geometric_mean.h into a corresponding .cpp file. Modify relevant CI Actions to validate that this results in compilation of a project-specific shared library file over and above the MRtrix3 core shared library file in the MRtrix3 subtree.
   > 2. If one of the MRtrix3 commands to be built for the external project is itself written in Python, this will result in all MRtrix3 C++ binaries being built, given an MRtrix3 Python command may invoke an MRtrix3 C++ binary but cmake is not informed of these dependencies. Add a CI Action that specifies an MRtrix3 Python command as a dependency, and modify the verification mechanics to account for the presence of all MRtrix3 C++ binaries in the MRtrix3 bin/ directory. Additionally generate and assess prospective strategies for requisite modifications to MRtrix3 cmake to explicitly specify the set of MRtrix3 commands upon which each MRtrix3 Python command depends.
   > 3. Re-assess whether, given the fixed MRtrix3 subtree structure, it is now possible to avoid the need for cmake/templates/app.py.in, modulating behaviour of MRtrix3's own Bash executable generation script so that for external project Python commands the generated executable will point explicitly into the MRtrix3 subtree. Modifications to MRtrix3 code to facilitate such is permitted (utilise ~/src/worktrees/external_projects/ for assessment & testing).

Generated-by: Claude Opus 4.8 <noreply@anthropic.com>
A project's C++ commands already report the project's own version, but
its Python commands printed "== <cmd> unknown ==": the version was
derived from a run-time `git describe` in mrtrix3.app, which fails in a
cmake-staged tree with no .git.

Bake the project version at build time and feed it to mrtrix3.app via the
generated launcher, the Python analogue of the C++ project_version.cpp:

- New cmake/templates/version.py.in -> generated <project>/version.py
  (VERSION/BUILD_DATE), mirroring MRtrix3's own mrtrix3/version.py and
  populated from the same PROJECT_VERSION_STRING/PROJECT_BUILD_DATE the
  C++ commands compile in, so C++ and Python report identical versions.
- python/CMakeLists.txt: configure, stage and install version.py
  alongside the package boilerplate.
- testing/verify.sh: assert a Python command reports the project version
  (matching the C++ banner, never "unknown") and that the launcher is
  wired to the generated version.py.
- Bump MRTRIX3_GIT_TAG to the matching MRtrix3 commit, which teaches the
  launcher generator to import and pass <package>.version to _execute().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a CI job that builds the Apptainer image from containers/apptainer.def
and runs the project's full verification suite (testing/verify.sh) inside
the container, exercising the same contracts as the vendored and existing
jobs against the installed /opt/project tree: dual-version reporting for one
C++ and one Python command, shared linkage, the requested MRtrix3 subset,
and the no-shadow/resolution rules. A thin CI-only image layers binutils
onto the deliberately lean runtime so verify.sh's ELF introspection works
without bloating the published image, and the build/exec run as root to
sidestep Ubuntu 24.04's unprivileged-user-namespace restriction. The recipe
gains a %arguments build-arg for the MRtrix3 revision (mirroring the
Dockerfile's ARG) so CI pins the same SHA as the rest of the workflow while
standalone builds keep working.

Session prompts:
1. > Add a Continuous Integration check that builds an Apptainer image from the existing containers/apptainer.def file, including 1 x C++ command and 1 x Python command as defined in the demo_code repository, then applies the same set of verification checks within the container as are applied in other CI checks.

Generated-by: Claude Opus 4.8 <noreply@anthropic.com>
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