cpm-cmake / CPM.cmake

📦 CMake's missing package manager. A small CMake script for setup-free, cross-platform, reproducible dependency management.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

  1. use a github repo named Catch2
  2. 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 as PROVIDED_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 FetchContent
  • TARGETS check could be added as a feature to ExternalProject instead/later (outside of CPM but could be considered)
  • Is the name TARGETS the best option, some alternatives to consider (I like TARGETS 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