Skip to content

COMP: Wrap build-tree system include dirs in BUILD_INTERFACE - #6465

Closed
hjmjohnson wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:fix-system-include-build-interface
Closed

hjmjohnson wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:fix-system-include-build-interface

Conversation

@hjmjohnson

Copy link
Copy Markdown
Member

A module's <module>_SYSTEM_INCLUDE_DIRS entries are placed in the module target's interface include directories verbatim. When a module fetches a header-only dependency into its build tree, that build-directory path becomes part of install(EXPORT ITKTargets) and CMake 4.x fails generation with "INTERFACE_INCLUDE_DIRECTORIES property contains path which is prefixed in the build directory." This wraps only build-tree-prefixed system include paths in $<BUILD_INTERFACE:>; stable system paths are unchanged.

Why this is needed — full analysis

CMake/ITKModuleMacros.cmake builds <module>_SYSTEM_GENEX_INCLUDE_DIRS from <module>_SYSTEM_INCLUDE_DIRS and applies it to the module targets as target_include_directories(<tgt> SYSTEM PUBLIC ...) (both the library and the <module>Module interface target). Unlike the regular include dirs a few lines above — which are wrapped per entry in $<BUILD_INTERFACE:> plus an explicit $<INSTALL_INTERFACE:> — the system include dirs were appended raw:

# before
foreach(_dir ${${itk-module}_SYSTEM_INCLUDE_DIRS})
  list(APPEND ${itk-module}_SYSTEM_GENEX_INCLUDE_DIRS ${_dir})
endforeach()

The in-code comment states the assumption: "System include directories are assumed to be external dependencies that are not installed, and thus do not have separate install interface paths." That holds for stable system paths (/usr/include/eigen3, a system OpenCL SDK, …) which are valid at both build and install time, so a raw entry is fine.

It breaks when a module's system include dir is a build-tree path. A remote module that pulls a header-only library via FetchContent/ExternalData typically exposes something like ${CMAKE_BINARY_DIR}/.../_deps/include. That path:

  1. is not relocatable (it is meaningless after install / on another machine), and

  2. is rejected outright by install(EXPORT). Because the raw build-tree path lands in INTERFACE_INCLUDE_DIRECTORIES of an exported target, CMake (strict since 3.x, hard-erroring under the 4.x policy set) fails at generate time:

    CMake Error in CMakeLists.txt:
      install(EXPORT "ITKTargets" ...) ... Target "<Mod>" INTERFACE_INCLUDE_DIRECTORIES
      property contains path:
        ".../_deps/include"
      which is prefixed in the build directory.
    

The whole ITK configure then fails generation, even if the consumer never installs, because install(EXPORT ITKTargets) is always set up.

Fix: wrap only the build-tree-prefixed system paths in $<BUILD_INTERFACE:> (apply during build, dropped from the install interface, mirroring how regular include dirs are already handled). Non-build paths pass through unchanged, so the common case is byte-for-byte unaffected.

# after
foreach(_dir ${${itk-module}_SYSTEM_INCLUDE_DIRS})
  if(_dir MATCHES "^${CMAKE_BINARY_DIR}" OR _dir MATCHES "^${PROJECT_BINARY_DIR}")
    list(APPEND ${itk-module}_SYSTEM_GENEX_INCLUDE_DIRS "$<BUILD_INTERFACE:${_dir}>")
  else()
    list(APPEND ${itk-module}_SYSTEM_GENEX_INCLUDE_DIRS ${_dir})
  endif()
endforeach()
How it was found + validation

Surfaced building ITK main with Module_VkFFTBackend=ON (the VkFFT remote module fetches the header-only VkFFT library into <build>/.../_deps/include and adds it to VkFFTBackend_SYSTEM_INCLUDE_DIRS) under CMake 4.2.1. Without this change, generate fails as above; with it, generate and the full build/install succeed.

  • pre-commit run --all-files → clean (gersemi-formatted).
  • Full ITK main configure + generate + build + install with Module_VkFFTBackend=ON (where the build-tree path is wrapped) → success.
  • Clean ITK main configure + generate with no remote modules (where every system path is a stable /usr-style path and the new branch is a no-op) → success, confirming no regression for the common case.

CMake-build-system change only; no new tests.

@github-actions github-actions Bot added type:Compiler Compiler support or related warnings type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots labels Jun 17, 2026
@hjmjohnson
hjmjohnson marked this pull request as ready for review June 17, 2026 21:57
@hjmjohnson
hjmjohnson requested a review from blowekamp June 17, 2026 21:58
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Wraps build-tree-prefixed entries in SYSTEM_INCLUDE_DIRS inside $<BUILD_INTERFACE:> so that install(EXPORT ITKTargets) no longer records unrelocatable build-directory paths — fixing a CMake 4.x generation failure triggered by remote modules (e.g. Module_VkFFTBackend) that fetch header-only dependencies into the build tree.

  • The MATCHES \"^${CMAKE_BINARY_DIR}\" check uses ERE semantics; a . or other metacharacter in the build directory name can produce a false-positive match and silently drop a legitimate system include from the install interface — cmake_path(IS_PREFIX ...) or string(FIND ...) would be safer.
  • The new in-source comment spans two lines, exceeding the repo's 1-line / ≤ 100-char prose-budget cap enforced by AGENTS.md.

Confidence Score: 3/5

The fix is correct for the common case but the regex-based prefix test is fragile, and the in-source comment violates a repo policy that the project treats as a merge blocker.

The MATCHES check against CMAKE_BINARY_DIR interprets the path as an ERE pattern, meaning a . in a directory name silently broadens the match and could strip a valid system include from the install interface without any warning. Separately, the two-line comment exceeds the hard cap the project enforces as a review-blocking defect. Both issues are straightforward to fix, but they should be addressed before merge.

CMake/ITKModuleMacros.cmake — the only changed file; the regex prefix check and the over-budget comment both need attention.

Important Files Changed

Filename Overview
CMake/ITKModuleMacros.cmake Wraps build-tree entries in SYSTEM_INCLUDE_DIRS inside $<BUILD_INTERFACE:>; the regex-based prefix check is fragile when CMAKE_BINARY_DIR contains ERE metacharacters, and the new 2-line comment violates the repo's 1-line prose-budget cap.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["foreach _dir in SYSTEM_INCLUDE_DIRS"] --> B{"_dir MATCHES\n'^CMAKE_BINARY_DIR'\nOR\n'^PROJECT_BINARY_DIR'?"}
    B -- Yes --> C["list APPEND SYSTEM_GENEX_INCLUDE_DIRS\n$<BUILD_INTERFACE:_dir>"]
    B -- No --> D["list APPEND SYSTEM_GENEX_INCLUDE_DIRS\n_dir (verbatim)"]
    C --> E["target_include_directories SYSTEM PUBLIC\n→ active during build only\n→ dropped from install(EXPORT)"]
    D --> F["target_include_directories SYSTEM PUBLIC\n→ active at both build and install time"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["foreach _dir in SYSTEM_INCLUDE_DIRS"] --> B{"_dir MATCHES\n'^CMAKE_BINARY_DIR'\nOR\n'^PROJECT_BINARY_DIR'?"}
    B -- Yes --> C["list APPEND SYSTEM_GENEX_INCLUDE_DIRS\n$<BUILD_INTERFACE:_dir>"]
    B -- No --> D["list APPEND SYSTEM_GENEX_INCLUDE_DIRS\n_dir (verbatim)"]
    C --> E["target_include_directories SYSTEM PUBLIC\n→ active during build only\n→ dropped from install(EXPORT)"]
    D --> F["target_include_directories SYSTEM PUBLIC\n→ active at both build and install time"]
Loading

Reviews (1): Last reviewed commit: "COMP: Wrap build-tree system include dir..." | Re-trigger Greptile

Comment thread CMake/ITKModuleMacros.cmake Outdated
Comment thread CMake/ITKModuleMacros.cmake Outdated
@hjmjohnson

Copy link
Copy Markdown
Member Author

@blowekamp ANTs builds against the latest ITKv6 would not build. This allows it to build (with CMake 4.2) but I'm hoping you know of a less complicated solution.

A module's <module>_SYSTEM_INCLUDE_DIRS entries are added to the module
target's interface include directories verbatim, on the assumption that
they are stable external/system paths present at both build and install
time. A module that fetches a header-only dependency into its own build
tree (for example a remote module using FetchContent) exposes a
build-directory path here. install(EXPORT ITKTargets) then records an
unrelocatable build-directory path in the interface and, under CMake 4.x,
fails generation with "INTERFACE_INCLUDE_DIRECTORIES property contains
path which is prefixed in the build directory".

Wrap only build-tree-prefixed system include paths in $<BUILD_INTERFACE:>
so they apply to the build and are dropped from the install interface.
Stable system paths are passed through unchanged, so behavior for the
common case (e.g. an installed Eigen) is unaffected.
@blowekamp

Copy link
Copy Markdown
Member

I believe you can set ${itk-module}_SYSTEM_GENEX_INCLUDE_DIRS} to the generator expression in the module and it will applied correctly.

@hjmjohnson

Copy link
Copy Markdown
Member Author

Closing in favor of a module-side fix — thanks @blowekamp, your suggestion was exactly right. 🎉

You called it: setting ${itk-module}_SYSTEM_GENEX_INCLUDE_DIRS to the $<BUILD_INTERFACE:> generator expression in the module applies correctly and needs no change to ITKModuleMacros.cmake. I verified it end-to-end: with this core patch reverted, ITK still configures and install(EXPORT ITKTargets) generates cleanly on CMake ≥ 4.x.

Investigating your approach also surfaced a second, latent defect in the module that the core patch alone would not have fixed: itkVkCommon.h is an installed PUBLIC header that #includes the fetched vkFFT.h / metal-cpp headers, but those were never installed — so a relocated ITK install (the ANTs-against-installed-ITKv6 case) failed with Foundation/Foundation.hpp file not found. The module fix handles both: the build-only genex (your suggestion) and installing the fetched headers.

Both are now in ITKVkFFTBackend PR #83, commit fffc8a6. Closing this PR as superseded.

@hjmjohnson hjmjohnson closed this Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:Compiler Compiler support or related warnings type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants