Skip to content

COMP: Declare NrrdIO's Threads dependency in ITK's exported config - #6776

Merged
hjmjohnson merged 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:comp-itkconfig-find-dependency-threads
Aug 21, 2026
Merged

COMP: Declare NrrdIO's Threads dependency in ITK's exported config#6776
hjmjohnson merged 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:comp-itkconfig-find-dependency-threads

Conversation

@hjmjohnson

@hjmjohnson hjmjohnson commented Aug 21, 2026

Copy link
Copy Markdown
Member

ITK::ITKNrrdIO's exported interface names Threads::Threads, which nothing resolves for consumers, so find_package(ITK) fails at generate time for UNIX consumers linking an ITK target. ITKIONRRD is a default module, so main is affected.

Root cause

#6695 added to Modules/ThirdParty/NrrdIO/src/CMakeLists.txt:

find_package(Threads REQUIRED)
list(APPEND NRRDIO_COMPRESSION_LIBRARIES Threads::Threads)

which reaches ITKTargets.cmake as $<LINK_ONLY:Threads::Threads> with no consumer-side declaration. main only; release-5.4 is unaffected.

ITKNrrdIO_EXPORT_CODE_{BUILD,INSTALL} re-runs find_package(Threads) on the consumer side, as ITKTBB does for the same dependency. The variables must be set before itk_module_impl() in the module's top-level CMakeLists.txt; add_subdirectory(src) opens a new scope.

Local validation

Isolated consumer project (posted in a comment, two directories: UseITK + ITK_LIBRARIES, and ITK_INTERFACE_LIBRARIES without UseITK):

ITK under test legacy modern
main FAIL FAIL
this branch PASS PASS

Both styles fail identically without the change: INTERFACE_LINK_LIBRARIES on an imported target resolves at generate time once anything links ITK::ITKNrrdIO transitively. A consumer that calls find_package(ITK) without linking a target passes either way, which matters for any regression test added for this.

Build tree and install tree, macOS arm64, -DITK_BUILD_DEFAULT_MODULES=OFF -DModule_ITKIONRRD=ON -DBUILD_TESTING=ON:

configure  ok
build      ok
ctest      100% tests passed out of 230
install    ok

install-tree consumer probe   PASS legacy  PASS modern
build-tree   consumer probe   PASS legacy  PASS modern
Audit of other externally-namespaced targets in ITKTargets.cmake

An earlier revision of this description listed DCMTK::*, GTest::gtest and MPI::MPI_C as latent leaks of the same class. Each was checked against a real build tree; that was wrong.

target finding
Threads::Threads (via ITKNrrdIO) Live breakage, fixed here
DCMTK::* (19) ITKDCMTK sets export code on both paths
GTest::* Vendored path exports them as imported targets; GoogleTest is build-tree-only by design
VTK::* (10, via ITKVtkGlue) Export code present, both variants
MPI::MPI_C, vendored-HDF5 Threads::Threads Unreachable: inside genexes vendored HDF5 cannot make true in an ITK build
AI assistance
  • Role: root-cause analysis, consumer-probe construction, audit of the other exported targets
  • Evidence of local testing: the table above; pre-commit run --all-files clean on the pushed HEAD

@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 Aug 21, 2026
@blowekamp

Copy link
Copy Markdown
Member

I would be good to a have a small isolated project to check a couple things with this change. If the recommended modern CMake linking interface is the issue still there? Use ITK_INTERFACE_LIBRARIES and no use itk?

https://docs.itk.org/en/latest/migration_guides/itk_6_migration_guide.html#modern-cmake-interface-libraries

@blowekamp

Copy link
Copy Markdown
Member

Also check if there is missing export code for Threads::Threads in the NRRD third party library.

@hjmjohnson

Copy link
Copy Markdown
Member Author

CMake linking interface is the issue still there? Use ITK_INTERFACE_LIBRARIES and no use itk?

I think this is the right place to look. I'll need to do some digging and investigating. NOTE: The current patch fixes the downstream problem I have, but is probably to heavy handed in making it an unconditional find_package rather than binding it to the ITKNrrdIO that needs it.

WIll run some tests as suggested.

@hjmjohnson
hjmjohnson force-pushed the comp-itkconfig-find-dependency-threads branch from 452cf94 to a3488c0 Compare August 21, 2026 16:43
@github-actions github-actions Bot added the area:ThirdParty Issues affecting the ThirdParty module label Aug 21, 2026
@hjmjohnson hjmjohnson changed the title COMP: Resolve Threads::Threads before importing ITK targets COMP: Declare NrrdIO's Threads dependency in ITK's exported config Aug 21, 2026
@hjmjohnson

Copy link
Copy Markdown
Member Author

Both answered, and the PR is reworked to your second point. Isolated package attached (itk-threads-export-probe.tar.gz), also inlined below.

1. Modern interface libraries — the failure is identical. Two consumer projects against the same ITK build tree, one include(${ITK_USE_FILE}) + ${ITK_LIBRARIES}, one no-UseITK + ${ITK_INTERFACE_LIBRARIES}:

$ ./run-probe.sh <ITK-build @ upstream/main 9f127d9231>
FAIL  legacy
          The link interface of target "ITK::ITKNrrdIO" contains:
            Threads::Threads
        CMake Generate step failed.
FAIL  modern
          <same>
exit=1

$ ./run-probe.sh <ITK-build @ this PR>
PASS  legacy
PASS  modern
exit=0

CMake resolves INTERFACE_LINK_LIBRARIES on an imported target at generate time as soon as anything links ITK::ITKNrrdIO transitively, so how the target reached the link line does not matter. Worth noting for a regression test: a probe that only calls find_package(ITK) without linking a target passes even against broken ITK.

2. Missing export code — yes, that was the root cause. Modules/ThirdParty/NrrdIO/CMakeLists.txt set no ITKNrrdIO_EXPORT_CODE_{BUILD,INSTALL}, while ITKTBB already re-runs find_package(Threads REQUIRED) on the consumer side for this same reason and ITKHDF5 does the equivalent for HDF5. #6695 added the imported target to the export without the matching declaration. The PR now sets those two variables and drops the ITKConfig.cmake.in change entirely.

One implementation note: the vars have to be set in the module's top-level CMakeLists.txt before itk_module_impl()add_subdirectory(src) opens a new scope, so setting them next to the find_package(Threads) in src/ would silently not propagate.

Verified on macOS arm64 against a build tree. The install-tree path mirrors ITKTBB but I have not exercised it locally.

Happy to file a follow-up issue applying the same pattern to the remaining leaked targets (DCMTK::*, GTest::gtest, MPI::MPI_C), plus the CI gap that let this ship green.

Isolated package, inlined

README.md

# `find_package(ITK)` + `Threads::Threads` export probe

Minimal external project reproducing the generate-time failure reported in
ITK PR #6776: `ITK::ITKNrrdIO`'s exported link interface names
`Threads::Threads`, but `ITKConfig.cmake` never brings that imported target
into the consumer's scope.

## Run

```bash
./run-probe.sh /path/to/ITK-build     # or an ITK install's lib/cmake/ITK-6.0

Exit status 0 means both consumer styles configure; non-zero means at least
one failed to resolve ITK's exported interface.

What it covers

Two directories, one per documented consumer style:

  • legacy/include(${ITK_USE_FILE}) + ${ITK_LIBRARIES}
  • modern/ — no UseITK, ${ITK_INTERFACE_LIBRARIES}

Both must link a target. A probe that only calls find_package(ITK) passes
even against a broken ITK: CMake never resolves an interface nobody consumes.
That is worth knowing for any regression test added upstream.

Expected results

ITK under test legacy modern
main before the fix FAIL FAIL
main with the fix PASS PASS

The failure is identical in both styles. INTERFACE_LINK_LIBRARIES on an
imported target is resolved at generate time as soon as anything links
ITK::ITKNrrdIO transitively, so how the target reached the link line does
not matter.

Failure text:

The link interface of target "ITK::ITKNrrdIO" contains:
    Threads::Threads
  but the target was not found.
CMake Generate step failed.

Configuring the ITK under test

ITKIONRRD is a default module, so a stock build reproduces it. A minimal
one is enough:

cmake -G Ninja -S ITK -B ITK-build \
    -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF \
    -DITK_BUILD_DEFAULT_MODULES=OFF -DModule_ITKIONRRD=ON

The probe is a configure-time check, so ITK does not have to be built.


`run-probe.sh`

```bash
#!/usr/bin/env bash
# Configure both consumer styles against one ITK build/install tree.
# Exit 0 = ITK's exported link interface resolves; non-zero = it does not.
#
#   ./run-probe.sh /path/to/ITK-build
#
set -u
ITK_DIR="${1:?usage: run-probe.sh <ITK_DIR>}"
HERE="$(cd "$(dirname "$0")" && pwd)"
WORK="$(mktemp -d)"
trap 'rm -rf "${WORK}"' EXIT

rc=0
for style in legacy modern; do
    log="${WORK}/${style}.log"
    if cmake -S "${HERE}/${style}" -B "${WORK}/${style}" -DITK_DIR="${ITK_DIR}" \
        >"${log}" 2>&1; then
        echo "PASS  ${style}"
    else
        echo "FAIL  ${style}"
        awk '/The link interface of target/{n=4} n-->0; /Generate step failed/' \
            "${log}" | sed 's/^/        /'
        rc=1
    fi
done
exit "${rc}"

probe.cxx

#include "itkImageFileReader.h"
#include "itkImage.h"

int
main()
{
  using ImageType = itk::Image<float, 3>;
  const auto reader = itk::ImageFileReader<ImageType>::New();
  return reader.IsNull() ? 1 : 0;
}

legacy/CMakeLists.txt

# Legacy consumer style: UseITK + ${ITK_LIBRARIES}
cmake_minimum_required(VERSION 3.22)
project(itk_threads_probe_legacy CXX)

find_package(ITK REQUIRED COMPONENTS ITKIONRRD)
include(${ITK_USE_FILE})

add_executable(probe_legacy ../probe.cxx)
target_link_libraries(probe_legacy PRIVATE ${ITK_LIBRARIES})

modern/CMakeLists.txt

# Modern consumer style: no UseITK, ${ITK_INTERFACE_LIBRARIES}
# https://docs.itk.org/en/latest/migration_guides/itk_6_migration_guide.html#modern-cmake-interface-libraries
cmake_minimum_required(VERSION 3.22)
project(itk_threads_probe_modern CXX)

find_package(ITK REQUIRED COMPONENTS ITKIONRRD)

add_executable(probe_modern ../probe.cxx)
target_link_libraries(probe_modern PRIVATE ${ITK_INTERFACE_LIBRARIES})

expected-output.txt

Recorded on macOS arm64, cmake 4.4.2.
Both ITK trees configured from the same source, configure-only, with:
  -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF -DITK_BUILD_DEFAULT_MODULES=OFF -DModule_ITKIONRRD=ON

$ ./run-probe.sh <ITK-build @ upstream/main 9f127d9231>
FAIL  legacy
          The link interface of target "ITK::ITKNrrdIO" contains:
        
            Threads::Threads
        
        CMake Generate step failed.  Build files cannot be regenerated correctly.
FAIL  modern
          The link interface of target "ITK::ITKNrrdIO" contains:
        
            Threads::Threads
        
        CMake Generate step failed.  Build files cannot be regenerated correctly.
exit=1

$ ./run-probe.sh <ITK-build @ this PR>
PASS  legacy
PASS  modern
exit=0

@dzenanz dzenanz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good on a glance.

Trying here and maybe upstreaming later? Or would this fix be irrelevant upstream? Even so, having synchronization would be good.

@hjmjohnson

hjmjohnson commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

Looks good on a glance.

Trying here and maybe upstreaming later? Or would this fix be irrelevant upstream? Even so, having synchronization would be good.

@dzenanz This is ITK's CMake file, and it does not exist in the NrrdIO upstream.

@hjmjohnson

This comment was marked as off-topic.

@hjmjohnson
hjmjohnson marked this pull request as ready for review August 21, 2026 17:14
@greptile-apps

This comment was marked as resolved.

ITKNrrdIO's exported link interface names Threads::Threads with
nothing bringing that imported target into a consumer's scope, so
find_package(ITK) failed at generate time for any UNIX consumer
linking an ITK target.

ITKTBB is the precedent for re-running find_package(Threads) from
the module's own export code.
@hjmjohnson
hjmjohnson force-pushed the comp-itkconfig-find-dependency-threads branch from a3488c0 to a6ddb92 Compare August 21, 2026 18:19
@hjmjohnson

Copy link
Copy Markdown
Member Author

Force-push is commit-message rewrap only; the diff is unchanged from the version @dzenanz approved.

Install-tree path is now exercised, which closes the gap both greptile and the previous description flagged as untested. -DITK_BUILD_DEFAULT_MODULES=OFF -DModule_ITKIONRRD=ON -DModule_ITKGoogleTest=ON -DBUILD_TESTING=ON, macOS arm64:

configure ok / build ok / ctest 100% tests passed out of 230 / install ok
install-tree consumer probe   PASS legacy  PASS modern
build-tree   consumer probe   PASS legacy  PASS modern

Module_ITKGoogleTest=ON is required there because ITKIONRRD calls CreateGoogleTestDriver without declaring TEST_DEPENDS ITKGoogleTest; six other modules have the same omission. Per your note on #6778 the declaration is the fix, and I will send that separately rather than mixing it in here.

@blowekamp
blowekamp self-requested a review August 21, 2026 21:42
@hjmjohnson
hjmjohnson merged commit 313b523 into InsightSoftwareConsortium:main Aug 21, 2026
18 checks passed
@hjmjohnson
hjmjohnson deleted the comp-itkconfig-find-dependency-threads branch August 22, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ThirdParty Issues affecting the ThirdParty module 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.

3 participants