cpm-cmake / CPM.cmake

📦 CMake's missing package manager. A small CMake script for setup-free, cross-platform, reproducible dependency management.
MIT License
3.07k stars 188 forks source link

Feature-Idea: `target_link_cpmpackage` to simplify declare+add+link multiple package dependencies #404

Open CraigHutchinson opened 2 years ago

CraigHutchinson commented 2 years ago

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:

Cons

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: https://github.com/cpm-cmake/CPM.cmake/issues/404#issuecomment-1256545915

Actions:

iboB commented 2 years ago

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
)
CraigHutchinson commented 2 years ago

@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?

CraigHutchinson commented 2 years ago

💡 To decide when considering the new parameter:

iboB commented 2 years ago

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

iboB commented 2 years ago

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

CraigHutchinson commented 2 years ago

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