Skip to content

Fix exported CMake usage requirements and relocatable dependencies #1120

Description

@IvanaGyro

Summary

Cytnx::cytnx currently mixes several different kinds of CMake target properties:

  1. true public API requirements that downstream targets must inherit;
  2. compiler/build-policy flags that should affect only Cytnx itself;
  3. dependency include/library paths that must remain relocatable;
  4. coverage instrumentation requirements for a static archive.

These should be separated explicitly instead of broadly making properties PUBLIC or hiding them with $<BUILD_INTERFACE:...>.

Current problems

Build-policy flags leak to downstream consumers

The main target currently exposes flags such as:

target_compile_definitions(cytnx PUBLIC _LIBCPP_DISABLE_AVAILABILITY)
target_compile_options(cytnx PUBLIC -Wformat=0 -w -fsized-deallocation)

A target linking Cytnx::cytnx therefore inherits them. In particular, -w disables warnings in downstream code, and _LIBCPP_DISABLE_AVAILABILITY changes libc++ behavior globally for the consumer translation unit.

Dependency paths may be embedded as build-machine absolute paths

Examples include manual ${Boost_INCLUDE_DIRS}, ${LAPACKE_INCLUDE_DIRS}, and plain library lists such as ${LAPACK_LIBRARIES} / ${LAPACKE_LIBRARIES}. Exporting these directly can make the installed package non-relocatable.

Coverage handling is asymmetric between build-tree and install-tree consumers

Coverage currently uses:

target_compile_options(cytnx PUBLIC $<BUILD_INTERFACE:--coverage>)
target_link_options(cytnx PUBLIC $<BUILD_INTERFACE:--coverage>)

Cytnx is a static library. If libcytnx.a contains objects compiled with coverage instrumentation, the final consumer link needs the coverage runtime whether the consumer uses the build-tree export or the installed package. $<BUILD_INTERFACE:...> removes that required link option from the install-tree interface.

LAPACKE is a public header dependency

include/backend/lapack_wrapper.hpp is installed and directly includes:

#include <lapacke.h>
#include <cblas.h>

Therefore LAPACKE headers are a real public usage requirement. Hiding ${LAPACKE_INCLUDE_DIRS} behind only $<BUILD_INTERFACE:...> would make build-tree consumers work while install-tree consumers fail.

Proposed changes

1. Keep actual public header configuration requirements public

Definitions that change installed header contents must continue to propagate through INTERFACE_COMPILE_DEFINITIONS, including the applicable build configuration macros such as:

BACKEND_TORCH
UNI_MKL
UNI_GPU
UNI_CUTENSOR
UNI_CUQUANTUM
UNI_DEBUG

For example, UNI_GPU makes installed headers include CUDA headers and expose CUDA-dependent types. A consumer linking Cytnx::cytnx should automatically compile with -DUNI_GPU; this is the intended behavior of PUBLIC compile definitions.

Similarly, keep:

target_compile_features(cytnx PUBLIC cxx_std_20)

because installed headers require C++20.

2. Make Cytnx-only compiler policy private

Change the warning/compiler flags to PRIVATE:

target_compile_options(cytnx PRIVATE
  -Wformat=0
  -w
  -fsized-deallocation
)

These should ideally also be compiler-ID guarded. Longer term, removing the global -w suppression would be preferable.

Make _LIBCPP_DISABLE_AVAILABILITY private unless an installed header demonstrably requires downstream translation units to use it:

target_compile_definitions(cytnx PRIVATE
  _LIBCPP_DISABLE_AVAILABILITY
)

A search of the installed include/ tree currently finds no std::format use that would justify exporting this libc++ policy macro.

3. Remove duplicated manual Boost include propagation

Cytnx already links the imported target:

target_link_libraries(cytnx PUBLIC Boost::boost)

Remove both duplicated blocks equivalent to:

target_include_directories(cytnx SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})

Boost::boost should own and propagate its include directories. CytnxConfig.cmake already re-finds Boost for consumers.

4. Express coverage correctly for a static library

If coverage-instrumented Cytnx builds are allowed to be exported or installed, use:

target_compile_options(cytnx PRIVATE --coverage)
target_link_options(cytnx INTERFACE --coverage)

Rationale:

  • PRIVATE --coverage instruments Cytnx source objects only; it does not unnecessarily instrument consumer sources.
  • A static archive has no final linker step, so its meaningful requirement is INTERFACE_LINK_OPTIONS.
  • Both build-tree and install-tree final consumers must link the coverage runtime when using the instrumented archive.
  • Do not wrap the link requirement in $<BUILD_INTERFACE:...> if the same instrumented archive can be installed.

Alternative policy: if coverage builds are strictly internal and must never be distributed, explicitly prevent installing/exporting that configuration. Do not install an instrumented archive while silently removing its runtime link requirement.

5. Model LAPACKE through the imported target provided by Morse

The vendored Morse FindLAPACKE.cmake already documents and creates this target when LAPACKE is found:

MORSE::LAPACKE

It is created by:

morse_create_imported_target(LAPACKE)

and carries the detected include directories, link directories/libraries, compile options, and link options. Cytnx should therefore consume the target directly:

find_package(LAPACKE REQUIRED)
target_link_libraries(cytnx PUBLIC MORSE::LAPACKE)

Remove the direct propagation of build-machine paths:

target_include_directories(cytnx PUBLIC ${LAPACKE_INCLUDE_DIRS})
target_link_libraries(cytnx PUBLIC ${LAPACKE_LIBRARIES})

The exported Cytnx::cytnx target should refer to MORSE::LAPACKE by target name. The consumer must recreate that imported target in its own environment before CytnxTargets.cmake is loaded.

The Morse helper morse_install_finds() is intended for this case. It installs the requested Find*.cmake modules together with the shared Morse support files and MORSE-Copyright.txt. Install the required finder dependency closure, at least the normal LAPACKE path:

set(CYTNX_MORSE_FINDS
  LAPACKE
  LAPACKEXT
)

morse_install_finds(
  CYTNX_MORSE_FINDS
  "${INSTALL_CONFIGDIR}/morse"
)

If Cytnx enables the optional LAPACKE TMG component, include its finder and any further transitive Morse finder dependencies as well.

In CytnxConfig.cmake.in, temporarily expose the installed modules and recreate MORSE::LAPACKE before including the exported Cytnx targets:

include(CMakeFindDependencyMacro)

block(SCOPE_FOR VARIABLES)
  list(PREPEND CMAKE_MODULE_PATH
    "${CMAKE_CURRENT_LIST_DIR}/morse"
  )
  find_dependency(LAPACKE MODULE REQUIRED)
endblock()

if(NOT TARGET Cytnx::cytnx)
  include("${CMAKE_CURRENT_LIST_DIR}/CytnxTargets.cmake")
endif()

The same finder bundle must also be available beside the build-tree CytnxConfig.cmake, because Cytnx supports build-tree consumption through export(EXPORT ...) as well as installed-package consumption.

Do not use morse_export_imported_target(MORSE::LAPACKE ...) for this purpose. That helper serializes the currently detected imported target properties, including build-machine absolute paths, and would recreate the relocatability problem. The consumer should rerun the finder instead.

Also do not use only:

$<BUILD_INTERFACE:${LAPACKE_INCLUDE_DIRS}>

because installed consumers compiling lapack_wrapper.hpp still need LAPACKE headers. Do not place the build machine's absolute LAPACKE path in INSTALL_INTERFACE either.

6. Apply the imported-target rule to BLAS/LAPACK and optional SDKs

For LAPACKE, use the target actually provided by the current finder:

MORSE::LAPACKE

For BLAS/LAPACK and other dependencies, prefer their available imported targets and matching find_dependency(...) calls rather than exporting raw absolute path lists. The exact target names depend on the finder or package configuration in use; do not invent a second LAPACKE target name when MORSE::LAPACKE is already available.

For cuTENSOR and cuQuantum, either:

  1. expose proper imported targets and re-find them in CytnxConfig.cmake, because installed headers conditionally include their headers; or
  2. refactor installed headers so those third-party headers/types are no longer part of the public API.

Simply hiding their absolute paths with $<BUILD_INTERFACE:...> is insufficient while installed headers still require them.

Existing patterns that should remain

The following current designs are appropriate:

target_include_directories(cytnx
  PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/src
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

The CUDA CCCL handling is also appropriate: use a build-tree-only detected include path and re-detect the consumer's own toolkit path in CytnxConfig.cmake, rather than exporting the build machine's CUDA path.

The HPTT/OpenMP arrangement is conceptually correct for the static archive: HPTT can remain a private implementation dependency, while the final consumer must receive the OpenMP link requirement and any referenced static HPTT target must be present in the export set.

Suggested implementation sketch

# Public API requirements
target_compile_features(cytnx PUBLIC cxx_std_20)

# Cytnx-only policies
target_compile_definitions(cytnx PRIVATE _LIBCPP_DISABLE_AVAILABILITY)
target_compile_options(cytnx PRIVATE -Wformat=0 -w -fsized-deallocation)

# Imported public dependencies
find_package(LAPACKE REQUIRED)
target_link_libraries(cytnx PUBLIC
  Boost::boost
  MORSE::LAPACKE
)

# Do not separately export ${LAPACKE_INCLUDE_DIRS} or
# ${LAPACKE_LIBRARIES}; MORSE::LAPACKE owns those requirements.

# Install the finder infrastructure required to recreate the imported target.
set(CYTNX_MORSE_FINDS LAPACKE LAPACKEXT)
morse_install_finds(
  CYTNX_MORSE_FINDS
  "${INSTALL_CONFIGDIR}/morse"
)

# Coverage configuration for an exportable/installable static archive
if(RUN_TESTS AND
   (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
  target_compile_options(cytnx PRIVATE --coverage)
  target_link_options(cytnx INTERFACE --coverage)
endif()

The exact BLAS/LAPACK dependency target set may differ between MKL/OpenBLAS/Torch configurations, but the principle should remain target-based and relocatable.

Acceptance criteria

  • A normal downstream target linking Cytnx::cytnx no longer inherits -w, -Wformat=0, -fsized-deallocation, or _LIBCPP_DISABLE_AVAILABILITY.
  • Downstream targets still automatically inherit public header configuration such as UNI_GPU and C++20.
  • Build-tree and install-tree consumers both configure, compile, link, and run.
  • A coverage-enabled build-tree consumer links successfully.
  • After installing the same coverage-enabled static archive, an install-tree consumer also links successfully and receives the coverage runtime option.
  • A consumer can directly include backend/lapack_wrapper.hpp with LAPACKE installed under a non-system prefix.
  • CytnxConfig.cmake recreates MORSE::LAPACKE from the consumer's own environment before loading CytnxTargets.cmake.
  • The required Morse finder files, helper modules, and copyright notice are available to both build-tree and install-tree consumers.
  • The installed CytnxTargets.cmake does not contain source-tree paths or build-environment dependency include paths.
  • CPU and CUDA package-consumer tests continue to pass, including CCCL re-detection for CUDA builds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions