TheLartians / ModernCppStarter

🚀 Kick-start your C++! A template for modern C++ projects using CMake, CI, code coverage, clang-format, reproducible dependency management and much more.
https://thelartians.github.io/ModernCppStarter
The Unlicense
4.29k stars 378 forks source link

add a package for a interface(or not executable) library #112

Closed Life4gal closed 3 years ago

Life4gal commented 3 years ago

I noticed the CMakeList.txt file at the top level of your project, the line 55

# Link dependencies
target_link_libraries(Greeter PRIVATE fmt::fmt)

Use target_link_libraries to link an external library which add by

CPMAddPackage(
  NAME fmt
  GIT_TAG 7.1.3
  GITHUB_REPOSITORY fmtlib/fmt
  OPTIONS "FMT_INSTALL YES" # create an installable target
)

I am puzzled why this can be done, as far as I know, this will remind me of an error on my IDE (CLion).

Cannot specify link libraries for target "PROJECT_NAME" which is not built by this project.

This is the structure of my project:

cmake |--- CPM.cmake

exec |--- main.cpp |--- CMakeList.txt

include |--- *.hpp

src |--- *.cpp

CMakeList.txt

The content of CMakeList.txt in top level:

cmake_minimum_required(VERSION 2.8.12...3.17)
project(
        foo
        VERSION 0.0.1
        LANGUAGES CXX
)

include(cmake/CPM.cmake)
CPMAddPackage("gh:TheLartians/PackageProject.cmake#master")

file(GLOB_RECURSE fooHeader CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
file(GLOB_RECURSE fooSource CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")

add_library(
        ${PROJECT_NAME}
        ${fooHeader}
        ${fooSource}
)

set_target_properties(
        ${PROJECT_NAME} PROPERTIES
        LINKER_LANGUAGE CXX
        CXX_STANDARD 17
)

# being a cross-platform target, we enforce standards conformance on MSVC
target_compile_options(${PROJECT_NAME} PUBLIC "$<$<BOOL:${MSVC}>:/permissive->")

# MARK1
# CPMAddPackage("gh:jarro2783/cxxopts#master")
# set(CXXOPTS_BUILD_EXAMPLES off)
# set(CXXOPTS_BUILD_TESTS off)
# target_link_libraries(${PROJECT_NAME} cxxopts)

target_include_directories(
        ${PROJECT_NAME}
        PUBLIC
        $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include/${PROJECT_NAME}-${PROJECT_VERSION}>
)

string(TOLOWER ${PROJECT_NAME}/version.h VERSION_HEADER_LOCATION)

packageProject(
        NAME ${PROJECT_NAME}
        VERSION ${PROJECT_VERSION}
        NAMESPACE ${PROJECT_NAME}
        BINARY_DIR ${PROJECT_BINARY_DIR}
        INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include
        INCLUDE_DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION}
        VERSION_HEADER "${VERSION_HEADER_LOCATION}"
        DEPENDENCIES "foo"
)

The content of CMakeList.txt in exec:

cmake_minimum_required(VERSION 2.8.12...3.17)

project(
        fooExec
        LANGUAGES CXX
)

include(../cmake/CPM.cmake)

CPMAddPackage(
        NAME foo
        SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..
)

add_executable(
        ${PROJECT_NAME}
        main.cpp
)

set_target_properties(
        ${PROJECT_NAME}
        PROPERTIES CXX_STANDARD 17
        OUTPUT_NAME "fooExec"
)

# MARK2
CPMAddPackage("gh:jarro2783/cxxopts#master")
set(CXXOPTS_BUILD_EXAMPLES off)
set(CXXOPTS_BUILD_TESTS off)
target_link_libraries(
        ${PROJECT_NAME}
        PRIVATE
        foo::foo
)

You may have noticed that I have a mark in each of the two files.

At the beginning, I wrote the code that needs cxxopts in the exec folder (the same directory as main.cpp) and the exec folder's structure is similar to the top level (also has include and src folders), I use

target_link_libraries(
        ${PROJECT_NAME}
        PRIVATE
        foo::foo
        cxxopts
)

everything work well

// and if I write
#include <cxx

// It immediately reminds me whether to write
#include <cxxopts.hpp>

// Yes, IDE knows that there is a file called cxxopts.hpp

But soon I discovered that there are some problems with the design of this structure. I need to move the code that uses cxxopts to the include and src folders at the top level. So I tried to delete the cxxopts at MARK2 to

target_link_libraries(
        ${PROJECT_NAME}
        PRIVATE
        foo::foo
)

And added the code at MARK1 (they didn't exist at the beginning).

CPMAddPackage("gh:jarro2783/cxxopts#master")
set(CXXOPTS_BUILD_EXAMPLES off)
set(CXXOPTS_BUILD_TESTS off)

# the code below is also not exist now
# target_link_libraries(${PROJECT_NAME} cxxopts)

then

```c++
// and if I write
#include <cxx

// It reminds me nothing
#include <cxxopts.hpp>

// Yes, IDE does not knows that there is a file called cxxopts.hpp and it tells me file not found

After this, I came back here, I use target_link_libraries like you

CPMAddPackage("gh:jarro2783/cxxopts#master")
set(CXXOPTS_BUILD_EXAMPLES off)
set(CXXOPTS_BUILD_TESTS off)
target_link_libraries(${PROJECT_NAME} cxxopts)

What I didn’t expect was that the error this time was not

Cannot specify link libraries for target "PROJECT_NAME" which is not built by this project.

It output

CMake Error: install(EXPORT "fooTargets" ...) includes target "foo" which requires target "cxxopts" that is not in any export set.

What I didn't expect again is

// and if I write
#include <cxx

// It immediately reminds me whether to write
#include <cxxopts.hpp>

// Yes, IDE knows that there is a file called cxxopts.hpp again!!!

I don't know what to do.

TheLartians commented 3 years ago
CPMAddPackage(
  GITHUB_REPOSITORY jarro2783/cxxopts
  VERSION 2.2.1
  OPTIONS 
    "CXXOPTS_BUILD_EXAMPLES OFF"
    "CXXOPTS_BUILD_TESTS OFF"
    "CXXOPTS_ENABLE_INSTALL ON"
)
Life4gal commented 3 years ago
  • Be sure to define the option before adding the package as cache variables, or simply provide them to CPM.cmake in the OPTIONS parameter, as otherwise the package might ignore them.
  • I think the error is due to the fact that cxxopts isn't installable by default. You should also provide the CXXOPTS_ENABLE_INSTALL parameter. We should probably add it in the starter as well.
  • Also prefer git tags as version identifiers, as otherwise the build won't be reproducible in the future.
CPMAddPackage(
  GITHUB_REPOSITORY jarro2783/cxxopts
  VERSION 2.2.1
  OPTIONS 
    "CXXOPTS_BUILD_EXAMPLES OFF"
    "CXXOPTS_BUILD_TESTS OFF"
    "CXXOPTS_ENABLE_INSTALL ON"
)

Yes this eliminates the error, but can you explain to me why it doesn't work of my code? What puzzles me most is why my code still runs normally despite the error reported by CMake.

TheLartians commented 3 years ago

You're adding cxxopts, which is not configured for installation, to a library that is. As you need to be able install all dependencies, CMake gives an error. By configuring cxxopts for installation this error is resolved.

Life4gal commented 3 years ago

You're adding cxxopts, which is not configured for installation, to a library that is. As you need to be able install all dependencies, CMake gives an error. By configuring cxxopts for installation this error is resolved.

Could you please tell me what should I do if I want to install nlohmann/json(or nlohmann/json's FetchContent)(and others library) in this way? Because it seems that not all libraries have a "FOO_ENABLE_INSTALL YES" option, sorry, I don’t know much about this :)

The example not suitable for me, as mentioned earlier, my top level project is not built by itself.

TheLartians commented 3 years ago

Oh yeah, seems we missed that in the example. You'd have to set JSON_Install to YES in this case. Unfortunately, as far as I know, there is no great way of doing this besides looking at the project's CMakeLists and finding out how to enable the libraries installation from there.

Life4gal commented 3 years ago

Oh yeah, seems we missed that in the example. You'd have to set JSON_Install to YES in this case. Unfortunately, as far as I know, there is no great way of doing this besides looking at the project's CMakeLists and finding out how to enable the libraries installation from there.

# fmt always work well :)
CPMAddPackage(
        NAME fmt
        GIT_TAG master
        GITHUB_REPOSITORY fmtlib/fmt
        # create an installable target, this is necessary
        # https://github.com/fmtlib/fmt/blob/9cb347b4b2e80fc9fbf57b8621746663c3f870f6/CMakeLists.txt#L67
        OPTIONS "FMT_INSTALL YES"
)

CPMAddPackage(
        NAME nlohmann_json
        VERSION 3.9.1
        # the git repo is incredibly large, so we download the archived include directory
        URL https://github.com/nlohmann/json/releases/download/v3.9.1/include.zip
        URL_HASH SHA256=6bea5877b1541d353bd77bdfbdb2696333ae5ed8f9e8cc22df657192218cad91
        # https://github.com/nlohmann/json/blob/823801879ab9a99440b300a02b737c11e806d207/CMakeLists.txt#L34
        # why no effect
        OPTIONS "JSON_Install YES"
)

# todo: If the following code exists, everything work well, but...
# CMake Error: install(EXPORT "StarterTemplateTargets" ...) includes target "StarterTemplate" which requires target "nlohmann_json" that is not in any export set.
if(nlohmann_json_ADDED)
    add_library(nlohmann_json INTERFACE)
    target_include_directories(nlohmann_json SYSTEM INTERFACE ${nlohmann_json_SOURCE_DIR}/include)
endif()

target_link_libraries(
        ${PROJECT_NAME}
        PUBLIC
        fmt
        nlohmann_json
)
TheLartians commented 3 years ago

Oh yeah you're creating your own JSON target, that isn't installable. Try replacing the block

CPMAddPackage(
        NAME nlohmann_json
        VERSION 3.9.1
        # the git repo is incredibly large, so we download the archived include directory
        URL https://github.com/nlohmann/json/releases/download/v3.9.1/include.zip
        URL_HASH SHA256=6bea5877b1541d353bd77bdfbdb2696333ae5ed8f9e8cc22df657192218cad91
        # https://github.com/nlohmann/json/blob/823801879ab9a99440b300a02b737c11e806d207/CMakeLists.txt#L34
        # why no effect
        OPTIONS "JSON_Install YES"
)

# todo: If the following code exists, everything work well, but...
# CMake Error: install(EXPORT "StarterTemplateTargets" ...) includes target "StarterTemplate" which requires target "nlohmann_json" that is not in any export set.
if(nlohmann_json_ADDED)
    add_library(nlohmann_json INTERFACE)
    target_include_directories(nlohmann_json SYSTEM INTERFACE ${nlohmann_json_SOURCE_DIR}/include)
endif()

with something like

CPMAddPackage(
  NAME nlohmann_json
  VERSION 3.9.1
  OPTIONS 
    "JSON_Install ON"
    "JSON_BuildTests OFF"
)

Be sure to set the CPM_SOURCE_CACHE environmental variable (e.g. to ~/.cache/cpm) to allow a shallow clone and avoid redundant downloads of the repo.

Life4gal commented 3 years ago

Oh yeah you're creating your own JSON target, that isn't installable. Try replacing the block

CPMAddPackage(
      NAME nlohmann_json
      VERSION 3.9.1
      # the git repo is incredibly large, so we download the archived include directory
      URL https://github.com/nlohmann/json/releases/download/v3.9.1/include.zip
      URL_HASH SHA256=6bea5877b1541d353bd77bdfbdb2696333ae5ed8f9e8cc22df657192218cad91
      # https://github.com/nlohmann/json/blob/823801879ab9a99440b300a02b737c11e806d207/CMakeLists.txt#L34
      # why no effect
      OPTIONS "JSON_Install YES"
)

# todo: If the following code exists, everything work well, but...
# CMake Error: install(EXPORT "StarterTemplateTargets" ...) includes target "StarterTemplate" which requires target "nlohmann_json" that is not in any export set.
if(nlohmann_json_ADDED)
  add_library(nlohmann_json INTERFACE)
  target_include_directories(nlohmann_json SYSTEM INTERFACE ${nlohmann_json_SOURCE_DIR}/include)
endif()

with something like

CPMAddPackage(
  NAME nlohmann_json
  VERSION 3.9.1
  OPTIONS 
    "JSON_Install ON"
    "JSON_BuildTests OFF"
)

Be sure to set the CPM_SOURCE_CACHE environmental variable (e.g. to ~/.cache/cpm) to allow a shallow clone and avoid redundant downloads of the repo.

This can indeed solve the problem, but it will bring a problem that cannot be ignored: the size of the json library is 250MB. My operating system is KDE (ubuntu20.04) and the IDE I use is CLion. Can you explain what you said [set the CPM_SOURCE_CACHE], or how to do it, I don’t really understand.

TheLartians commented 3 years ago

If you add something like export CPM_SOURCE_CACHE=$HOME/.cache/CPM to your .bash_profile, CPM.cmake will cache downloads and also use shallow clones. In that case the json library still needs around 170mb, but it will be downloaded only once on your system. See the CPM.cmake docs for more info.

Alternatively, you can download only the header as before, but then it's your responsibility to create an installable target. Another option that could work would be to simply add the include to your main (already installable) target target_include_directories(${PROJECT_NAME} PRIVATE ${nlohmann_json_SOURCE_DIR}/include).