Skip to content

Explicit MRtrix3 dependencies of Python commands - #3394

Open
Lestropie wants to merge 4 commits into
devfrom
python_dependencies
Open

Explicit MRtrix3 dependencies of Python commands#3394
Lestropie wants to merge 4 commits into
devfrom
python_dependencies

Conversation

@Lestropie

Copy link
Copy Markdown
Member

Intended to work in conjunction with MRtrix3/external-project-template#2.

I want for it to be possible for an external MRtrix3 project that provides one or more Python commands to be able to selectively build and package with it only those MRtrix3 commands that need to be built in order for that external Python command to function. In addition, during development if one is working on a Python command it would be beneficial if a targeted build of that command would only recompile the minimum set of other MRtrix3 commands that need to be built for the manifest behaviour of the command to reflect code changes.

There are multiple ways to do this. I decided on defining within the source code files themselves a set of strings encoding the commands the code in that file depends upon, which can be parsed by cmake (without invoking the Python interpreter) to build the dependency graph. This will hopefully mean that the same mechanism can be extended to the building of external projects.

New command check_python_dependencies (which will probably eventually be moved into python/ along with run_pylint in #3083) is a linter of limited capability: it will catch obvious omissions of commands from that list in CI, but it's not guaranteed to catch all (command names could be stored in variables).

Lestropie added 3 commits June 8, 2026 11:07
Previously, building any MRtrix3 Python command forced compilation of
every C++ binary, because CMake had no knowledge of which commands a
Python script might invoke. Each Python command now declares an
MRTRIX_DEPENDENCIES constant listing the MRtrix3 commands it may call,
and new CMake helpers parse and recursively resolve these declarations at
configure time to attach a minimal set of build targets to each command,
falling back to the full command set when a declaration is absent. A
linter run within the secondary-checks CI action verifies that every
command declares its dependencies and that statically-determinable
run.command() invocations and image header accesses are covered.

Session prompts:
1. > Currently, cmake is instructed that if at least one MRtrix3 Python command is to be built, then all MRtrix3 C++ binary commands must also be built, as cmake is not provided with information about which MRtrix3 commands an MRtrix3 Python script may invoke. The purpose of this session is to provide this information.
   > 1. Within each MRtrix3 Python command (python/mrtrix3/commands/*.py if single source file, python/mrtrix3/commands/*/__init__.py if source directory), a constant list-of-strings MRTRIX_DEPENDENCIES is to be defined, which lists the names of all MRtrix3 commands that may be invoked. For each existing command, perform a search through the source code for reference to MRtrix3 commands and write that constant. Anything in a comment can be ignored, but do not restrict search to invocations of run.command() as a command name may be embedded within a variable.
   > 2. Write a cmake function, to exist in a standalone file in the cmake/ directory, that takes as input a Python command name and yields a list of MRtrix3 commands that must also be built if that Python command is to be built. This is to be based on direct parsing of the relevant source code file, without invoking the Python interpreter.
   > 3. Write a cmake function, to exist in a standalone file in the cmake/ directory, that recursively builds the set of command compilation dependencies. A Python command may itself invoke another MRtrix3 Python command, in which circumstance it is necessary for all dependencies of that invoked Python command to also be included in the compilation target list.
   > 4. Modify the cmake identification of compile targets. No longer establish "all C++ binaries" as a compile target dependency for all Python commands (though leave that target defined). Instead for each Python command establish at configure time the set of corresponding build targets utilising the functions described in steps 2 and 3.
   > 5. If a specific Python command fails to define the MRTRIX_DEPENDENCIES constant, then this must be appropriately reported by the function defined in step 2, and the result of step 3 being "determine the set of compile target dependencies for this Python command" (whether that specific function is invoked or skipped) must be the comprehensive set of all MRtrix3 commands (not just C++ binaries), since without knowledge of those dependencies compilation of all targets is the only safe option
   > 6. Add a script that acts as a linter for explicit Python command dependencies and is invoked by CI Action "secondary-checks". The script must first generate a list of all MRtrix3 commands present in the worktree. Then, it must loop over all Python commands. If constant MRTRIX_DEPENDENCIES is not defined, an error is reported. If an invocation of `run.command()` is found, where the invoked command is trivially parsed statistically (ie. no variable substitution) (noting that run.command() can take as first input argument a string, f-string, or list-of-strings), but that MRtrix3 command is not defined in MRTRIX_DEPENDENCIES, an error is reported. If function image.mrinfo() is called, or image.Header class is constructed, but command "mrinfo" is not present in MRTRIX_DEPENDENCIES, an error is reported.

Generated-by: Claude Opus 4.8 <noreply@anthropic.com>
Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
Move per-command MRtrix3 dependency declarations to a per-file model so
that commands whose source spans multiple files distribute their
MRTRIX_DEPENDENCIES across those files rather than aggregating them in
__init__.py. Each package __init__.py now seeds the constant as an empty
set, and every constituent source file augments it via "|=" with the
commands it itself invokes (importing the shared set so the augmented
assignment is valid in the submodule namespace); single-file commands
declare a set literal directly. The cmake parser is rescoped to a single
.py file (ParsePythonFileDependencies), while the resolver unions the
per-file declarations across all of a command's sources before closing
over transitive Python-to-Python dependencies. The dependency linter is
updated to parse the set/"|=" syntax and verify each source file against
its own declaration.

Session prompts:
1. > In HEAD~1, Python commands were given the ability to specify the set of MRtrix3 commands on which they depend, so that for selectively targeted builds only those other commands requisite for operation of that Python command are built. For Python commands whose code is spread across multiple source files, prior instruction was to aggregate these dependencies into the corresponding __init__.py file. This is however sub-optimal for some Python commands that operate as an interface to a range of different algorithms, each with their own CLI sub-parser. It is instead preferred that constant MRTRIX_DEPENDENCIES be manipulated by every source code file. The build system must then aggregate these dependencies to produce a list of build target dependencies for that Python command.
   > 1. Modify cmake: ParsePythonCommandDependencies must parse an individual .py file, and be renamed accordingly; ResolvePythonCommandDependencies must produce for a single Python command a list of build dependencies for cmake that deals not only with transitive dependencies but also receiving a list of all dependencies of all files constituting the source code of that command.
   > 2. Modify script check_python_dependencies to reflect the fact that for Python commands whose source code is spread across multiple files, the dependencies will be provided on a per-file basis rather than in the command's __init__.py file.
   > 3. MRTRIX_DEPENDENCIES is to be defined as a set rather than a list.
   > 4. For Python commands whose source code is distributed across multiple .py files, file __init__.py is to define it as an empty set (assuming no MRtrix3 commands are invoked from inside __init__.py); each individual .py file is then to update it using the union update operator "|=" based on the set of MRtrix3 commands that are dependencies of the source code within that file.
   > 5. One all tasks are completed execute the /squash-session skill.

Generated-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Lestropie Lestropie self-assigned this Jun 8, 2026
@Lestropie Lestropie added the build label Jun 8, 2026
Two CI regressions on the per-command Python dependency branch are
addressed. The CMake logic that enumerates Python commands selected
entries by blocklisting known non-commands, so an in-source build's
generated "CMakeFiles" directory was mistaken for a command and aborted
configuration; it now allow-lists genuine commands (a "<name>.py" file,
or a directory containing "__init__.py"), which also immunises against
other build artefacts deposited alongside the sources. Separately, the
population_template command disabled too-many-positional-arguments
inline, but that check exists only in newer pylint, so CI's older pylint
rejected the directive as an unknown option value. The two affected
functions now make their optional parameters keyword-only, dropping the
positional-argument count below the threshold and removing the need for
any version-specific disable.

Session prompts:
1. > Compilation on CI Actions failed due to the error message pasted below. Logic that parses the set of Python commands for which MRtrix3 command dependencies must be discovered needs to exclude CMakeFiles.txt.
2. > Investigate pylint errors in population_template files in CI. Note that pylint versions may differ between CI and local machine.
3. > Generate plan for refactoring the two problematic lines to satisfy the too-many-positional-arguments check so that supporting check disabling across incompatible pylint versions is no longer necessary.
4. > Apply edits, perform any changes required to pass ./run_pylint, then execute the /squash-session skill.

Generated-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

clang-tidy review says "All clean, LGTM! 👍"

@Lestropie
Lestropie marked this pull request as ready for review June 8, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant