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.

Home Page:https://thelartians.github.io/ModernCppStarter

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

Life4gal opened this issue · comments

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.

  • 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"
)
  • 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.

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.

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.

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.

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
)

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.

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.

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).