Feature-Idea: `target_link_cpmpackage` to simplify declare+add+link multiple package dependencies
CraigHutchinson opened this issue · comments
The premise is it would be really powerful to have a target_link_libraries
alternative that instead of just linking existing packages could pull the respective packages using CPM when the library isn't yet available.
This is based on the general usage pattern of find-package+link it as a dependency. By flipping the usage for end-user convenience it could later smooth core Cmake integration.
Example A
single-argument fetch+link syntax
target_link_cpmpackage( myApp
PRIVATE
"gh:catchorg/Catch2@2.5.0{Catch2::Catch2}"
PUBLIC
"gh:ericniebler/range-v3#0.12.0{range-v3::range-v3}"
"gh:jbeder/yaml-cpp#yaml-cpp-0.6.3@0.6.3{yaml-cpp::yaml-cpp}"
)
Main issue = selecting target library from package
This initial example extends the single-argument syntax to include the <target>
extension to make it possible to choose which target to link from the package:
<single-arg-syntax>{<target>}
Pros:
- Works as a simplification to multiple calls to
CpmAddPackage
{<target>}
could be omitted as the package ALIAS name can be used-by-default i.e. {yaml-cpp::yaml-cpp} for most modern CMake compliant packages (ALL the examples on the main readme infact!)- Could also support to list multiple targets from package
Cons
- Its a little verbose, albeit less than currently
- Can only use the single-argument syntax!
Example B
Declare separate then fetch+link using <package>::<alias_target>
CPMDeclarePackage("gh:catchorg/Catch2@2.5.0")
CPMDeclarePackage("gh:ericniebler/range-v3#0.12.0")
CPMDeclarePackage("gh:jbeder/yaml-cpp#yaml-cpp-0.6.3@0.6.3")
target_link_cpmpackage( myApp
PRIVATE
Catch2::Catch2
PUBLIC
range-v3::range-v3
yaml-cpp::yaml-cpp
)
Main issue = Cmake packages that don't conform to common naming practices
This example is closer to the ideal. The issue is that if the package e.g. Catch2
didn't use either:
- use a github repo named
Catch2
- define an alias target
Catch2::Catch2
Most example (All that I checked) repos appear to conform to these requirements.
To solve this in Example B we would allow a more verbose alternative syntax also e.g.<package>{<target>}
which could be used in the non-conforming cases.
Note this example below uses the nice-and-consistent named packages where the redundancy is clear showing the reduced syntax above being valuable:
target_link_cpmpackage( myApp
PRIVATE
Catch2{Catch2::Catch2}
PUBLIC
range-v3{range-v3::range-v3}
yaml-cpp{yaml-cpp::yaml-cpp}
)
Further, it may be possible to omit the CPMDeclarePackage
by using the existing CPM cache package of the supplied name if we stored some information about the newest package in the cache etc.. Not sure this is a good idea.
An even more powerful option would be to support a .cpmpackages
file would be super powerful so we can just reference packages from that by name. I See CPMUsePackageLock()
sort of fulfills this but its not implicitly used and serves a subtly different use-case than defining a list of package modules in one place.
UPDATE: A good alternative is provided in comment: #404 (comment)
Actions:
- Decide additional parameter name to
CPMDeclarePackage
such asPROVIDED_TARGETS
- Define how single-argument target syntax may look to define the provided targets
- Consider list of provided targets should be updated and/or validated when package is populated
- Consider that recursive dependencies and the targets they create are not available - i.e. document limitation or maybe we could fetch the targets somehow ahead of time..
I would expand on option B.
Instead of having a special syntax for the new macro, allow listing of targets in CPMDeclarePackage
.
Here's some proposed code to illustrate my addition:
CPMDeclarePackage("gh:catchorg/Catch2@2.5.0") # implicitly adding known target Catch2::Catch2
CPMDeclarePackage(
NAME multilib
VERSION 1.2.3
GITHUB_REPOSITORY example/multilib
TARGETS
multilib::a
multilib::b
multilib-examplelib # allow unscoped
) # now we have three more known targets
cpm_target_link_libraries(myapp
Catch2::Catch2 # OK. Implicitly defined from first package
multilib::a # OK. Explicitly defined in second package
multilib-examplelib # OK. Explicitly defined in second package
foobar # OK. Unknown target. Pass down to target_link_libraries
foo::bar # OK. Unknown target. Pass down to target_link_libraries
cpm{multilib::b} # OK. Explicitly defined in second package
cpm{baz} # ERROR. baz is an unknown target and it was expected to come from a cpm package (not passed down)
)
Thus the added packages will depend on the known targets. Known targets come from CPMDeclarePackage
.
This, by the way, ties in well with a feature I'm working on (though not very actively) which, combined with my addition would allow code like:
CPMDeclarePackage("cpm:Catch2") # declare package from remote repo
# ...
cpm_target_link_libraries(myapp
Catch2::Catch2 # target from remote package
)
@iboB I love the addition of TARGETS
declared for the Package, really solves using unscoped
targets. This also acts as a validation point on package compatibility. For example target_link_libraries
links "Foo::Bar" which may not be defined which would be a generic-error, but by addition of TARGETS
the error can be more informative so the developer knows the source of the issue.
e.g.
"Package "Foo" does not define target "Foo::Bar"
is much more insightful than the current
Foo::Bar is not built by this project
Related to the 'CPM:<package>' source type its not very clear, do you have a PR or issue link to discuss on this aspect?
💡 To decide when considering the new parameter:
TARGETS
could overlap with options for in ExternalProject or FetchContentTARGETS
check could be added as a feature toExternalProject
instead/later (outside of CPM but could be considered)- Is the name
TARGETS
the best option, some alternatives to consider (I likeTARGETS
for now):IMPORTS
IMPORT_TARGETS
USE_TARGETS
I agree that TARGETS
is not the best name. I'm thinking PROVIDES
or PRODUCES
.
To be honest I'm not in love with any of the proposed names for this argument
This feature depends on #414, or something similar if not exactly that, which will allow a clear distinction between a package's lifecycle phases: declared, defined, and added
This feature needs to take into account new feature for CMake v3.24 FetchContent override of find_package
https://cmake.org/cmake/help/latest/module/FetchContent.html#command:fetchcontent_declare