Skip to content

Comments

Allowed named header sets in beman install#19

Open
steve-downey wants to merge 4 commits intobemanproject:mainfrom
steve-downey:squash
Open

Allowed named header sets in beman install#19
steve-downey wants to merge 4 commits intobemanproject:mainfrom
steve-downey:squash

Conversation

@steve-downey
Copy link
Member

Add an optional argument for named header sets in the beman install function.

@ClausKlein
Copy link

ClausKlein commented Jan 31, 2026

FYI: https://discourse.cmake.org/t/how-to-install-multiple-file-set-for-a-library-in-a-generic-way/15489/7

Argument parsing is a good idea, but it is needed to install multiple targets!

The FILE_SET may be get from target properties:

        get_target_property(
            header_sets
            ${target}
            INTERFACE_HEADER_SETS
        )

see bemanproject/execution#208

@steve-downey
Copy link
Member Author

FYI: https://discourse.cmake.org/t/how-to-install-multiple-file-set-for-a-library-in-a-generic-way/15489/7

Argument parsing is a good idea, but it is needed to install multiple targets!

The FILE_SET may be get from target properties:

        get_target_property(
            header_sets
            ${target}
            INTERFACE_HEADER_SETS
        )

see bemanproject/execution#208

Will the INTERFACE_HEADER_SETS work for a target that isn't an interface?

I don't care terribly much about how the install finds the correct set to install, just that naming them all HEADER may cause collisions in what we've been calling "workspace" projects, groups of projects aggregated together into a single larger CMake project.

If we can make everything work from just the target name to install, I'll happily rebase optional and fix that side up.

@ClausKlein
Copy link

ClausKlein commented Feb 1, 2026

The FILE_SET are target properties, and we should only use the default names. Only one per target!

I have a running test here: ClausKlein/exemplar#2
and bemanproject/exemplar#286

@ClausKlein
Copy link

@steve-downey I would like to use the beman install cmake module like here:
ClausKlein/execution#3

@steve-downey
Copy link
Member Author

I don't remember at the moment why I arrived at needing names for filesets instead of just HEADERS, but it wasn't just because there was a separate TYPE added.
I will see if I can reconstruct how I arrived here.
It does look like recovering the file sets from a target by property is well supported, and should not count as "manipulation". If "public" isn't added to the interface header set property, I don't see a problem with having a strict rule that installed headers must be declared as interface for a library.

I tend to avoid "private" implementation only headers in my practice, but I can see the utility of the distinction. Although it may break down in the face of modules, if they are needed internally to construct the BMI. Pure implementation partitions are just library TU and don't contribute to the built module interface, and don't need to be shipped to package consumers needing to rebuild the BMI.

1 similar comment
@steve-downey
Copy link
Member Author

I don't remember at the moment why I arrived at needing names for filesets instead of just HEADERS, but it wasn't just because there was a separate TYPE added.
I will see if I can reconstruct how I arrived here.
It does look like recovering the file sets from a target by property is well supported, and should not count as "manipulation". If "public" isn't added to the interface header set property, I don't see a problem with having a strict rule that installed headers must be declared as interface for a library.

I tend to avoid "private" implementation only headers in my practice, but I can see the utility of the distinction. Although it may break down in the face of modules, if they are needed internally to construct the BMI. Pure implementation partitions are just library TU and don't contribute to the built module interface, and don't need to be shipped to package consumers needing to rebuild the BMI.

@ClausKlein
Copy link

I have a fully working prototype that handles all what must be at least an option IMHO to install a project:

bemanproject/exemplar#286

@ClausKlein
Copy link

ClausKlein commented Feb 4, 2026

my proposal:

Installing a Library

Installation is handled by the beman_install_library() helper.

Function Signature

beman_install_library(name
    TARGETS <target1> [<target2> ...]
    [DEPENDENCIES <dep1> [<dep2> ...]]
    [NAMESPACE <namespace>]
    [EXPORT_NAME <export-name>]
    [DESTINATION <module-destination>]
)

Arguments

  • name
    Logical package name (e.g. "beman.utility").
    Used to derive config file names and cache variable prefixes.

  • TARGETS
    Targets to install and export.

  • DEPENDENCIES (optional)
    Semicolon-separated list, one dependency per entry.
    Each entry is a valid find_dependency() argument list.

    NOTE: you must use the bracket form for quoting if not only a package name is used!

    "[===[beman.inplace_vector 1.0.0]===] [===[beman.scope 0.0.1 EXACT]===] fmt"

  • NAMESPACE (optional)
    Namespace for imported targets.
    Defaults to "beman::".

  • EXPORT_NAME (optional)
    Name of the generated CMake export set.
    Defaults to "-targets".

  • DESTINATION (optional)
    Installation directory for C++ module interface units.
    Defaults to ${CMAKE_INSTALL_INCLUDEDIR}/beman/modules.

This function installs the specified project TARGETS and its FILE_SET TYPE HEADERS to the default CMAKE install destination.

It also handles the installation of the CMake config package files if
needed. If the given targets has FILE_SET TYPE CXX_MODULE, it will also
installed to the given DESTINATION

  • *Used Cache variables

BEMAN_INSTALL_CONFIG_FILE_PACKAGES
List of package names for which config files should be installed.

<PREFIX>_INSTALL_CONFIG_FILE_PACKAGE
Per-package override to enable/disable config file installation.
is the uppercased package name with dots replaced by underscores.

Caveats

Only one FILE_SET CXX_MODULES is yet supported to install with this function!


Which form would you prefer for dependencies:

include(./cmake/beman-install-library.cmake)
beman_install_library(${PROJECT_NAME} TARGETS ${PROJECT_NAME} beman.exemplar_headers #
    # FIXME: DEPENDENCIES [===[beman.inplace_vector 1.0.0]===] [===[beman.scope 0.0.1 EXACT]===] fmt
    # TODO(CK): XXX OR XXX DEPENDENCIES "beman.inplace_vector 1.0.0;beman.scope 0.0.1 EXACT;fmt"
)

@ClausKlein ClausKlein self-assigned this Feb 6, 2026
@ClausKlein
Copy link

@ednolan which solution should be use, to install CXX_MODULES, multiple targets, FILE_SET, describe the Target dependencies, ...

@steve-downey
Copy link
Member Author

I don't think I want to constrain the name of the FILE_SET. If the header file sets for a target can always be read out from a target, that is OK, but I think it may fail in the face of pure implementation header files, that is ones only used in an implementation file, and not installed, but still necessary to be found during compilation of a target. This might be a distinction between the visibility of the header file set?
It is my impression that using the type as the name is not the intended cmake direction, considering that names were added after.

@ClausKlein
Copy link

ClausKlein commented Feb 11, 2026

I don't think I want to constrain the name of the FILE_SET. If the header file sets for a target can always be read out from a target, that is OK, but I think it may fail in the face of pure implementation header files, that is ones only used in an implementation file, and not installed, but still necessary to be found during compilation of a target. This might be a distinction between the visibility of the header file set? It is my impression that using the type as the name is not the intended cmake direction, considering that names were added after.

Named FILE_SET are needed if more than one FILE_SET is associated with a target.

I will check it, but AFAIK PRIVATE FILE_SET will not be installed with my implementation.
see INTERFACE_HEADER_SETS

I is based on a suggestion and reviewed from Ben Boeckel

https://discourse.cmake.org/t/how-to-install-multiple-file-set-for-a-library-in-a-generic-way/15489/24?u=clausklein

@ClausKlein
Copy link

What do you think about this cmake config packages install tree:

tree _CPack_Packages/Darwin/TGZ/beman.exemplar-0.1.0-Darwin
_CPack_Packages/Darwin/TGZ/beman.exemplar-0.1.0-Darwin
├── include
│   └── beman-0.1.0
│       └── beman
│           └── exemplar
│               └── identity.hpp
└── lib
    ├── beman-0.1.0
    │   └── libbeman.exemplar.a
    └── cmake
        └── beman.exemplar-0.1.0
            ├── beman.exemplar-config-version.cmake
            ├── beman.exemplar-config.cmake
            ├── beman.exemplar-targets-relwithdebinfo.cmake
            ├── beman.exemplar-targets.cmake
            ├── cxx-modules
            │   ├── cxx-modules-beman.exemplar-targets-RelWithDebInfo.cmake
            │   ├── cxx-modules-beman.exemplar-targets.cmake
            │   └── target-exemplar-RelWithDebInfo.cmake
            └── modules
                └── identity.cppm

11 directories, 10 files
bash-5.3$ 

@steve-downey
Copy link
Member Author

This tends to break the principle that everything within an install prefix is part of a consistent whole. It's mixing opt style discreet packaging with shared FSHS packaging.

In a shared install, there's a -I include pointing at the top, but Beman/exemplar/identity.hpp is not available that way, meaning that the installed include directory is not performing its function.

We also don't version Beman as a project, unlike Boost, so beman-0.1.0 isn't meaningful. But in any case it should not be installed in parallel with a hypothetical beman-1.2.5. A third package can not depend on both of them.

If you want, or need, multiple package versions generally visible, the would be in, e.g., /opt/Beman/beman-optional-1.0.0/{include, lib, share} , although you still have the problem of the installed lib possibly not using the correct deps.

This is of course why closed source libraries can't have dependencies beyond a specific std lib, and why library ABI is such a problem for vendors.

@ClausKlein
Copy link

ClausKlein commented Feb 12, 2026

It was recommended from Ben Boeckel (KITWARE)!

It works: https://github.com/bemanproject/exemplar/actions/runs/21921464251

There is no beman library until today, that depends on another one and this is really tested!

We can install inplace_vector and scope independently, and later install executer with this:
DEPENDENCIES [===[beman.inplace_vector 1.0.0]===] [===[beman.scope 0.0.1 EXACT]===] fmt

That will work, i promise!

@steve-downey
Copy link
Member Author

Boost doesn't do just the same, though, they install into a shared boost/ under include, and all the .a into lib/ . They do seem to be putting version numbers into the exported cmake, and that will probably cause some packaging issues. share/boost-1.91.0 is also a bit strange.

Task currently depends on Execution. But in any case, a PREFIX ought not to be a random collection of stuff, it is intended to be coherent at least within the PREFIX.

@ClausKlein
Copy link

OK, from the cmake build site, everything works fine.
But for dynamic linked binaries it becomes complicated:

bash-5.3$ _CPack_Packages/Darwin/TGZ/runtime_destination_dependency-1.5.0-Darwin/bin/PackageProjectTest-1.0/main 
dyld[93924]: Library not loaded: @rpath/libdependency.dylib
  Referenced from: <5E289461-23E1-39F9-8475-94B16B1FD661> /Users/clausklein/cmake/TheLartians/PackageProject/test/build/_CPack_Packages/Darwin/TGZ/runtime_destination_dependency-1.5.0-Darwin/bin/PackageProjectTest-1.0/main
  Reason: tried: '/Users/clausklein/cmake/TheLartians/PackageProject/test/build/_CPack_Packages/Darwin/TGZ/runtime_destination_dependency-1.5.0-Darwin/bin/PackageProjectTest-1.0/libdependency.dylib' (no such file), '/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/gcc/x86_64-apple-darwin23/15/libdependency.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/gcc/x86_64-apple-darwin23/15/libdependency.dylib' (no such file), '/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/gcc/libdependency.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/gcc/libdependency.dylib' (no such file), '/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/libdependency.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/libdependency.dylib' (no such file), '/Users/clausklein/cmake/TheLartians/PackageProject/test/build/_CPack_Packages/Darwin/TGZ/runtime_destination_dependency-1.5.0-Darwin/bin/PackageProjectTest-1.0/libdependency.dylib' (no such file), '/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/gcc/x86_64-apple-darwin23/15/libdependency.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/gcc/x86_64-apple-darwin23/15/libdependency.dylib' (no such file), '/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/gcc/libdependency.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/gcc/libdependency.dylib' (no such file), '/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/libdependency.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/libdependency.dylib' (no such file)
Abort trap: 6              _CPack_Packages/Darwin/TGZ/runtime_destination_dependency-1.5.0-Darwin/bin/PackageProjectTest-1.0/main
bash-5.3$ !tree
tree _CPack_Packages/Darwin/TGZ/runtime_destination_dependency-1.5.0-Darwin/
_CPack_Packages/Darwin/TGZ/runtime_destination_dependency-1.5.0-Darwin/
├── bin
│   └── PackageProjectTest-1.0
│       └── main
├── include
│   ├── dependency-1.2
│   │   └── dependency
│   │       ├── dependency.h
│   │       └── version.h
│   ├── header_only-1.0
│   │   └── header_only
│   │       ├── adder.h
│   │       └── version.h
│   ├── namespaced_dependency-4.5.6
│   │   └── namespaced_dependency
│   │       ├── namespaced_dependency.h
│   │       └── version.h
│   └── runtime_destination_dependency-1.5
│       └── runtime_destination_dependency
│           ├── dependency.h
│           └── version.h
├── lib
│   ├── cmake
│   │   ├── dependency-1.2
│   │   │   ├── dependencyConfig.cmake
│   │   │   ├── dependencyConfigVersion.cmake
│   │   │   ├── dependencyTargets-noconfig.cmake
│   │   │   └── dependencyTargets.cmake
│   │   ├── namespaced_dependency-4.5.6
│   │   │   ├── namespaced_dependencyConfig.cmake
│   │   │   ├── namespaced_dependencyConfigVersion.cmake
│   │   │   ├── namespaced_dependencyTargets-noconfig.cmake
│   │   │   └── namespaced_dependencyTargets.cmake
│   │   └── runtime_destination_dependency-1.5
│   │       ├── runtime_destination_dependencyConfig.cmake
│   │       ├── runtime_destination_dependencyConfigVersion.cmake
│   │       ├── runtime_destination_dependencyTargets-noconfig.cmake
│   │       └── runtime_destination_dependencyTargets.cmake
│   ├── dependency-1.2
│   │   └── libdependency.dylib
│   ├── namespaced_dependency-4.5.6
│   │   └── libnamespaced_dependency.dylib
│   └── runtime_destination_dependency-1.5
│       └── libruntime_destination_dependency.dylib
└── share
    └── cmake
        └── header_only-1.0
            ├── header_onlyConfig.cmake
            ├── header_onlyConfigVersion.cmake
            └── header_onlyTargets.cmake

23 directories, 27 files
bash-5.3$ 

@steve-downey
Copy link
Member Author

@ClausKlein Do you have an alternate patch that just makes the changes to install that you'd like to see? I can then try that out against Optional, where I'm using this patch now.

@ClausKlein
Copy link

@ClausKlein Do you have an alternate patch that just makes the changes to install that you'd like to see? I can then try that out against Optional, where I'm using this patch now.

https://github.com/ClausKlein/exemplar/blob/feature/add_cxx_module/cmake/beman-install-library.cmake
https://github.com/ClausKlein/exemplar/blob/feature/add_cxx_module/cmake/Config.cmake.in

steve-downey added a commit to steve-downey/optional that referenced this pull request Feb 16, 2026
Iterate over the target property for header sets to gather which
header sets are installed.
Extracted from
https://github.com/ClausKlein/exemplar/blob/feature/add_cxx_module/cmake/beman-install-library.cmake
and discussed in bemanproject/infra#19
Install based on the INTERFACE_HEADER_SETS for the target. Accept a list of
TARGETS to install if the package is not also the installable target, or not
the only target.
Comment on lines 56 to 59
if(NOT TARGET "${name}")
message(FATAL_ERROR "Target '${name}' does not exist and TARGETS not specified.")
endif()
set(BEMAN_INSTALL_LIBRARY_TARGETS ${name})

Choose a reason for hiding this comment

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

[pre-commit] reported by reviewdog 🐶

Suggested change
if(NOT TARGET "${name}")
message(FATAL_ERROR "Target '${name}' does not exist and TARGETS not specified.")
endif()
set(BEMAN_INSTALL_LIBRARY_TARGETS ${name})
if(NOT TARGET "${name}")
message(
FATAL_ERROR
"Target '${name}' does not exist and TARGETS not specified."
)
endif()
set(BEMAN_INSTALL_LIBRARY_TARGETS ${name})

Comment on lines 74 to 77
if(NOT TARGET "${_tgt}")
message(
WARNING
"beman_install_library(${name}): '${_tgt}' is not a target"

Choose a reason for hiding this comment

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

[pre-commit] reported by reviewdog 🐶

Suggested change
if(NOT TARGET "${_tgt}")
message(
WARNING
"beman_install_library(${name}): '${_tgt}' is not a target"
if(NOT TARGET "${_tgt}")
message(
WARNING
"beman_install_library(${name}): '${_tgt}' is not a target"
)
continue()
endif()
set(target_name "${_tgt}")
set(install_component_name "${_tgt}")
set(export_name "${name}")
set(package_name "${name}")
list(GET name_parts -1 component_name)
set(_install_header_set_args)
get_target_property(
_available_header_sets
${_tgt}
INTERFACE_HEADER_SETS

Comment on lines 79 to 97
continue()
endif()

set(target_name "${_tgt}")
set(install_component_name "${_tgt}")
set(export_name "${name}")
set(package_name "${name}")
list(GET name_parts -1 component_name)

set(_install_header_set_args)
get_target_property(
_available_header_sets
${_tgt}
INTERFACE_HEADER_SETS
)
if(_available_header_sets)
message(
VERBOSE
"beman-install-library(${name}): '${_tgt}' has INTERFACE_HEADER_SETS=${_available_header_sets}"

Choose a reason for hiding this comment

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

[pre-commit] reported by reviewdog 🐶

Suggested change
continue()
endif()
set(target_name "${_tgt}")
set(install_component_name "${_tgt}")
set(export_name "${name}")
set(package_name "${name}")
list(GET name_parts -1 component_name)
set(_install_header_set_args)
get_target_property(
_available_header_sets
${_tgt}
INTERFACE_HEADER_SETS
)
if(_available_header_sets)
message(
VERBOSE
"beman-install-library(${name}): '${_tgt}' has INTERFACE_HEADER_SETS=${_available_header_sets}"
if(_available_header_sets)
message(
VERBOSE
"beman-install-library(${name}): '${_tgt}' has INTERFACE_HEADER_SETS=${_available_header_sets}"
)
foreach(_install_header_set IN LISTS _available_header_sets)
list(
APPEND
_install_header_set_args
FILE_SET
"${_install_header_set}"
COMPONENT
"${install_component_name}"
)
endforeach()
else()
set(_install_header_set_args FILE_SET HEADERS)
endif()
install(
TARGETS "${target_name}"
EXPORT "${export_name}"
${_install_header_set_args}
)
set_target_properties(
"${target_name}"
PROPERTIES EXPORT_NAME "${component_name}"

Comment on lines 99 to 121
foreach(_install_header_set IN LISTS _available_header_sets)
list(
APPEND _install_header_set_args
FILE_SET
"${_install_header_set}"
COMPONENT
"${install_component_name}"
)
endforeach()
else()
set(_install_header_set_args FILE_SET HEADERS)
endif()

install(
TARGETS "${target_name}"
COMPONENT "${install_component_name}"
EXPORT "${export_name}"
FILE_SET HEADERS
)
${_install_header_set_args}
)

set_target_properties(
set_target_properties(
"${target_name}"
PROPERTIES EXPORT_NAME "${component_name}"
)
)

Choose a reason for hiding this comment

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

[pre-commit] reported by reviewdog 🐶

Suggested change
foreach(_install_header_set IN LISTS _available_header_sets)
list(
APPEND _install_header_set_args
FILE_SET
"${_install_header_set}"
COMPONENT
"${install_component_name}"
)
endforeach()
else()
set(_install_header_set_args FILE_SET HEADERS)
endif()
install(
TARGETS "${target_name}"
COMPONENT "${install_component_name}"
EXPORT "${export_name}"
FILE_SET HEADERS
)
${_install_header_set_args}
)
set_target_properties(
set_target_properties(
"${target_name}"
PROPERTIES EXPORT_NAME "${component_name}"
)
)

@steve-downey
Copy link
Member Author

@ClausKlein I think I pulled out the interface header part of your work, putting the module parts to the side for now. This fixes my concern about naming and collecting the header set(s) to install.

I've used this now in beman.optional without having to change the calling site with the assumption that the "name" is also the target name if the list of targets is empty.

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.

2 participants