I have a cmake project somewhere that I want to use in several other projects. Let's call it projA located at path /projA. I have built it in /projA/build. In this build folder there is some library /projA/build/lib.a.
Now if I want to create a new project B using project A in the folder /projB I know two options for the CMakeLists:
Solution A
cmake_minimum_required(VERSION 3.0)
project(projB)
add_executable(${PROJECT_NAME} projB.cpp)
add_subdirectory(/projA /projA/build)
target_link_libraries(${PROJECT_NAME} projA)
The problem is that this solution will create new make files in /projA/build and the project A will be built again. Furthermore each time I will switch to a new project using projA, projA will be built again. So that's not a good solution. I would like to not overwrite all the build folder each time I switch between two project using projA.
Solution B
cmake_minimum_required(VERSION 3.0)
project(projB)
add_executable(${PROJECT_NAME} projB.cpp)
link_libraries(${PROJECT_NAME} /projA/build/lib.a)
target_include_directories(${PROJECT_NAME} PRIVATE /projA/include)
# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE /projA/deps/depA/include)
target_include_directories(${PROJECT_NAME} PRIVATE /projA/deps/depB/include)
...
This solution works, but it's not very beautiful. I have to add a line for each include directory of all dependencies of project A.
So my question is: Is there a way to do it properly?
If you want to build them entirely separately, you will need to go through the find_package infrastructure. Here's a complete example:
Project A
lib.h
#ifndef LIB_H
#define LIB_H
int lib();
#endif
lib.c
#include "lib.h"
int lib() { return 42; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(projA)
add_library(lib lib.c)
target_include_directories(lib PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>")
# Export the target named lib for use in other projects
# from the build tree of this project.
export(TARGETS lib FILE projA-config.cmake)
Project B
main.c
#include <stdio.h>
#include <lib.h>
int main () {
printf("%d\n", lib());
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(projB)
# Import the targets defined by projA
find_package(projA REQUIRED)
add_executable(projB main.c)
target_link_libraries(projB PRIVATE lib)
Compilation steps:
I'm imagining the two directories are next to each other, like so:
$ tree
.
├── projA
│ ├── CMakeLists.txt
│ ├── lib.c
│ └── lib.h
└── projB
├── CMakeLists.txt
└── main.c
Now we can build project A:
$ cmake -S projA -B projA/build -DCMAKE_BUILD_TYPE=Release
...
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/projA/build
$ cmake --build projA/build/ -- -v
[1/2] /usr/bin/cc -I/path/to/projA -O3 -DNDEBUG -MD -MT CMakeFiles/lib.dir/lib.c.o -MF CMakeFiles/lib.dir/lib.c.o.d -o CMakeFiles/lib.dir/lib.c.o -c /path/to/projA/lib.c
[2/2] : && /usr/bin/cmake -E rm -f liblib.a && /usr/bin/ar qc liblib.a CMakeFiles/lib.dir/lib.c.o && /usr/bin/ranlib liblib.a && :
And now we'll build project B, and set the projA_ROOT variable to /path/to/projA/build so that find_package(projA) will succeed.
$ cmake -S projB -B projB/build -DCMAKE_BUILD_TYPE=Release -DprojA_ROOT=$PWD/projA/build
...
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/projB/build
$ cmake --build projB/build
$ cmake --build projB/build/ -- -v
[1/2] /usr/bin/cc -isystem /path/to/projA -O3 -DNDEBUG -MD -MT CMakeFiles/projB.dir/main.c.o -MF CMakeFiles/projB.dir/main.c.o.d -o CMakeFiles/projB.dir/main.c.o -c /path/to/projB/main.c
[2/2] : && /usr/bin/cc -O3 -DNDEBUG CMakeFiles/projB.dir/main.c.o -o projB /path/to/projA/build/liblib.a && :
Clearly, this is non-obvious, and it's also sort of minimal. To build your own packages correctly, you should carefully read these CMake documentation pages:
https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html
https://cmake.org/cmake/help/latest/command/install.html
https://cmake.org/cmake/help/latest/command/export.html
I would also suggest watching Craig Scott's talk "Deep CMake for Library Authors", here: https://youtu.be/m0DwB4OvDXk
Related
Consider the following example:
CmakeLists.txt
cmake_minimum_required(VERSION 3.13)
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
project(my_project)
message("PROJECT_NAME is '${PROJECT_NAME}'")
set(my_incls "D:/msys64/usr/include" "D:/msys64/usr/lib/glib-2.0/include")
message("my_incls ${my_incls}")
add_library(test main.c)
target_include_directories(test PRIVATE ${my_incls})
get_target_property(libincl test INCLUDE_DIRECTORIES)
message("libincl ${libincl}")
main.c
#include <stdio.h>
const char greeting[] = "hello world";
int main() {
printf("%s!\n", greeting);
return 0;
}
I do this in MSYS2 bash shell on Windows 10, relative to the folder the above files are in:
mkdir build
cd build
cmake ../ -G "Unix Makefiles"
The output of cmake is:
$ cmake ../ -G "Unix Makefiles"
-- The C compiler identification is GNU 11.3.0
...
PROJECT_NAME is 'my_project'
my_incls D:/msys64/usr/include;D:/msys64/usr/lib/glib-2.0/include
libincl /tmp/cmake_test/D:/msys64/usr/include;/tmp/cmake_test/D:/msys64/usr/lib/glib-2.0/include
-- Configuring done
...
So, you can see that in my_incls the folder paths are listed exactly as specified - but once they have been set as target_include_directories and are read back for printing, they have been changed: namely /tmp/cmake_test (the current working directory - the folder where I placed the files given above) has been prepended to the paths as specified, making them totally unusable.
How can I prevent CMake from modifying paths that I've specified for target_include_directories?
I'm trying to create a cmake function that automatically recompiles glsl to spirv upon changes to the shader files. Right now direct dependencies work, ie the shaders I use as compile arguments. However I make heavy use of #include feature that glslc provides, and by default I can't get changes in that stuff to trigger recompile. I made sure that I'm using the Ninja
Right now I have the following CMake function and arguments:
cmake -DCMAKE_BUILD_TYPE=Debug "-DCMAKE_MAKE_PROGRAM=JETBRAINSPATH/bin/ninja/win/ninja.exe" -G Ninja "PATH_TO_CURRENT_DIRECTORY"
function
set(GLSLC "$ENV{VULKAN_SDK}/Bin/glslc")
function(target_shader_function SHADER_TARGET)
foreach (SHADER_SOURCE_FILEPATH ${ARGN})
get_filename_component(SHADER_SOURCE_FILENAME ${SHADER_SOURCE_FILEPATH} NAME)
get_filename_component(SHADER_SOURCE_DIRECTORY ${SHADER_SOURCE_FILEPATH} DIRECTORY)
set(SHADER_TARGET_NAME "${SHADER_TARGET}_${SHADER_SOURCE_FILENAME}")
set(SHADER_BINARY_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/spirv")
set(SHADER_FINAL_BINARY_FILEPATH "${SHADER_BINARY_DIRECTORY}/${SHADER_SOURCE_FILENAME}.spv")
#we can use depfiles instead
#https://stackoverflow.com/questions/60420700/cmake-invocation-of-glslc-with-respect-to-includes-dependencies
add_custom_command(
OUTPUT ${SHADER_FINAL_BINARY_FILEPATH}
DEPENDS ${SHADER_SOURCE_FILEPATH}
DEPFILE ${SHADER_SOURCE_FILEPATH}.d
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_BINARY_DIRECTORY}
COMMAND ${GLSLC} -MD -MF ${SHADER_SOURCE_FILEPATH}.d -O ${SHADER_SOURCE_FILEPATH} -o ${SHADER_FINAL_BINARY_FILEPATH} --target-env=vulkan1.2 -I ${CMAKE_SOURCE_DIR}/shaderutils
DEPENDS ${SHADER_SOURCE_FILEPATH}
# BYPRODUCTS ${SHADER_FINAL_BINARY_FILEPATH} ${SHADER_SOURCE_FILEPATH}.d causes ninja to no longer work
COMMENT "Compiling SPIRV for \nsource: \n\t${SHADER_SOURCE_FILEPATH} \nbinary: \n\t${SHADER_FINAL_BINARY_FILEPATH} \n"
)
add_custom_target(${SHADER_TARGET_NAME} DEPENDS ${SHADER_FINAL_BINARY_FILEPATH} ${SHADER_SOURCE_FILEPATH}.d)
add_dependencies(${SHADER_TARGET} ${SHADER_TARGET_NAME})
endforeach (SHADER_SOURCE_FILEPATH)
endfunction()
and I use it like this:
cmake_minimum_required(VERSION 3.21)
cmake_policy(SET CMP0116 NEW)
project(my_workspace)
add_executable(my_target main.cpp)
...
target_shader_function(my_target
${CMAKE_CURRENT_SOURCE_DIR}/shaders/example.comp
)
main.cpp
#include <iostream>
int main(){
std::cout << "hello world!" << std::endl;
return 0;
}
Again, everything works fine if I change, for example, example.comp.
However, lets say I have the following shader (lets say that this is example.comp):
#version 460
#include "fooutils.glsl"
#define WORKGROUP_SIZE 1024
layout (local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) buffer MyBufferBlock{
float data[];
}
void main(){
uint tidx = gl_GlobalInvocationID.x;
data[tidx] += foo(tidx);
}
and I include the following:
#ifndef FOOUTILS_GLSL
#define FOOUTILS_GLSL
float foo(uint tidx){
return mod(tidx, 4.51);
}
#endif //FOOUTILS_GLSL
and I change fooutils.glsl after everything is compiled once (for example in a way that stops it from compiling),
#ifndef FOOUTILS_GLSL
#define FOOUTILS_GLSL
float foo(uint tidx){
return x;
return mod(tidx, 4.51);
}
#endif //FOOUTILS_GLSL
I don't get a recompile triggered. I had assumed that ninja would use this info to accomplish this, but I haven't seen it happen.
How do I use this depfile to force a recompile when an include dependency changes?
Here's my working implementation. But first, here's my terminal output so you can see it's working:
$ tree
.
├── CMakeLists.txt
├── main.cpp
├── shaders
│ └── example.comp
└── shaderutils
└── fooutils.glsl
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
...
$ cmake --build build/
[1/3] Compiling SPIRV: shaders/example.comp -> spirv/example.spv
[2/3] Building CXX object CMakeFiles/my_target.dir/main.cpp.o
[3/3] Linking CXX executable my_target
$ cmake --build build/
ninja: no work to do.
$ touch shaderutils/fooutils.glsl
$ cmake --build build/
[1/1] Compiling SPIRV: shaders/example.comp -> spirv/example.spv
$ cat build/spirv/example.d
spirv/example.spv: /path/to/shaders/example.comp /path/to/shaderutils/fooutils.glsl
$ cat build/CMakeFiles/d/*.d
spirv/example.spv: \
../shaders/example.comp \
../shaderutils/fooutils.glsl
Now on to the implementation
cmake_minimum_required(VERSION 3.22)
project(test)
function(target_shader_function TARGET)
find_package(Vulkan REQUIRED)
if (NOT TARGET Vulkan::glslc)
message(FATAL_ERROR "Could not find glslc")
endif ()
foreach (source IN LISTS ARGN)
cmake_path(ABSOLUTE_PATH source OUTPUT_VARIABLE source_abs)
cmake_path(GET source STEM basename)
set(depfile "spirv/${basename}.d")
set(output "spirv/${basename}.spv")
set(dirs "$<TARGET_PROPERTY:${TARGET},INCLUDE_DIRECTORIES>")
set(include_flags "$<$<BOOL:${dirs}>:-I$<JOIN:${dirs},;-I>>")
add_custom_command(
OUTPUT "${output}"
COMMAND "${CMAKE_COMMAND}" -E make_directory spirv
COMMAND Vulkan::glslc -MD -MF "${depfile}" -O "${source_abs}"
-o "${output}" --target-env=vulkan1.2 "${include_flags}"
DEPENDS "${source_abs}"
BYPRODUCTS "${depfile}"
COMMENT "Compiling SPIRV: ${source} -> ${output}"
DEPFILE "${depfile}"
VERBATIM
COMMAND_EXPAND_LISTS
)
set(shader_target "${TARGET}_${basename}")
add_custom_target("${shader_target}"
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${output}")
add_dependencies("${TARGET}" "${shader_target}")
endforeach ()
endfunction()
add_executable(my_target main.cpp)
target_shader_function(my_target shaders/example.comp)
target_include_directories(
my_target PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/shaderutils")
With a CMake minimum version of 3.20 or greater, CMP0116 will be set, which adjusts depfiles that were generated with relative paths to be relative to the top-level binary directory. You can see this in action in the last two command outputs.
For compatibility with this policy, the command to invoke glslc is careful to use only absolute paths or paths relative to ${CMAKE_CURRENT_BINARY_DIR}.
To increase the reusability of this function, I had it reuse the include paths from the TARGET rather than hard-coding shaderutils.
Also remember to always pass absolute paths to the DEPENDS arguments of add_custom_{command,target} to avoid surprising path resolution behaviors.
Finally, since CMake actually comes with a FindVulkan module that can locate glslc, we use that to get the Vulkan::glslc target. Per the documentation, it can be overridden by setting Vulkan_GLSLC_EXECUTABLE.
Terminal logs for VS2022 on Windows with MSVC:
> cmake -S . -B build
...
> cmake --build build --config Release
Checking Build System
Compiling SPIRV: shaders/example.comp -> spirv/example.spv
Building Custom Rule D:/test/CMakeLists.txt
Building Custom Rule D:/test/CMakeLists.txt
main.cpp
my_target.vcxproj -> D:\test\build\Release\my_target.exe
Building Custom Rule D:/test/CMakeLists.txt
> cmake --build build --config Release -- -noLogo
my_target.vcxproj -> D:\test\build\Release\my_target.exe
> notepad shaderutils\fooutils.glsl
> cmake --build build --config Release -- -noLogo
Compiling SPIRV: shaders/example.comp -> spirv/example.spv
my_target.vcxproj -> D:\test\build\Release\my_target.exe
> cmake --build build --config Release -- -noLogo
my_target.vcxproj -> D:\test\build\Release\my_target.exe
and again with Ninja instead of msbuild:
> cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release ^
-DVulkan_ROOT=C:/VulkanSDK/1.2.198.1
...
> powershell "cmake --build build | tee output.txt"
[1/3] Compiling SPIRV: shaders/example.comp -> spirv/example.spv
[2/3] Building CXX object CMakeFiles\my_target.dir\main.cpp.obj
[3/3] Linking CXX executable my_target.exe
> powershell "cmake --build build | tee output.txt"
ninja: no work to do.
> notepad shaderutils\fooutils.glsl
> powershell "cmake --build build | tee output.txt"
[1/1] Compiling SPIRV: shaders/example.comp -> spirv/example.spv
The little powershell + tee trick is just to keep the Ninja command log from overwriting itself. I could use --verbose, but then the full command lines would be printed, rather than the tidy summaries.
I have a project whose directory layout looks like:
- src/ #Contains main source code
- ext/ #Contains external libraries and headers from GitHub
- CMakeLists.txt
The problem is that no matter what I do, CMake always seems to pass ext/ to the compiler as a relative path, like this:
/usr/bin/c++ -I../ext mysrc.cpp
I've tried doing both:
include_directories("${PROJECT_SOURCE_DIR}/ext")
include_directories("/home/user/project/ext")
But it doesn't seem to matter. The directory is always passed to -I as ../ext.
Why does this matter? At the end of my build I invoke gcov -r <source file> which tells gcov to generate coverage reports from my source file and any relative paths found within. As a result, gcov is going into ext/ and generating reports for tons of stuff I don't care about and it's taking up a lot of time. If CMake would instead pass in -I/home/user/project/ext then gcov -r would ignore everything in ext/.
As far as I can tell from:
https://cmake.org/cmake/help/v3.13/command/include_directories.html ... this isn't possible, but maybe I'm just missing something?
Edit: This appears to be a problem with specifically the ninja generator. When using the Unix Makefiles generator, everything is passed via absolute paths.
https://gitlab.kitware.com/cmake/cmake/issues/18666
Edit2:
user#antimony:~/cmake_test$ ls
CMakeLists.txt ext src
user#antimony:~/cmake_test$ cat CMakeLists.txt
project(Hello)
add_subdirectory(src)
user#antimony:~/cmake_test$ cat src/CMakeLists.txt
include_directories(
.
${PROJECT_SOURCE_DIR}/ext
)
add_executable(hello_world hello.cpp)
user#antimony:~/cmake_test$ cat src/hello.cpp
#include <useless.h>
int main()
{
hello h;
return 0;
}
user#antimony:~/cmake_test$ cat ext/useless.h
struct hello {
int x;
};
user#antimony:~/cmake_test$ ~/Downloads/cmake-3.13.1-Linux-x86_64/bin/cmake --version
cmake version 3.13.1
CMake suite maintained and supported by Kitware (kitware.com/cmake).
user#antimony:~/cmake_test$ mkdir build && cd build
user#antimony:~/cmake_test/build$ ~/Downloads/cmake-3.13.1-Linux-x86_64/bin/cmake .. -G Ninja
-- The C compiler identification is GNU 7.3.0
-- The CXX compiler identification is GNU 7.3.0
...
-- Build files have been written to: /home/user/cmake_test/build
user#antimony:~/cmake_test/build$ ninja -v
[1/2] /usr/bin/c++ -I../src/. -I../ext -MD -MT src/CMakeFiles/hello_world.dir/hello.o -MF src/CMakeFiles/hello_world.dir/hello.o.d -o src/CMakeFiles/hello_world.dir/hello.o -c ../src/hello.cpp
[2/2] : && /usr/bin/c++ -rdynamic src/CMakeFiles/hello_world.dir/hello.o -o src/hello_world && :
user#antimony:~/cmake_test/build$ cat build.ninja
# CMAKE generated file: DO NOT EDIT!
# Generated by "Ninja" Generator, CMake Version 3.13
# This file contains all the build statements describing the
# compilation DAG.
...
#############################################
# Order-only phony target for hello_world
build cmake_object_order_depends_target_hello_world: phony || src/CMakeFiles/hello_world.dir
build src/CMakeFiles/hello_world.dir/hello.o: CXX_COMPILER__hello_world ../src/hello.cpp || cmake_object_order_depends_target_hello_world
DEP_FILE = src/CMakeFiles/hello_world.dir/hello.o.d
INCLUDES = -I../src/. -I../ext
OBJECT_DIR = src/CMakeFiles/hello_world.dir
OBJECT_FILE_DIR = src/CMakeFiles/hello_world.dir
TARGET_COMPILE_PDB = src/CMakeFiles/hello_world.dir/
TARGET_PDB = src/hello_world.pdb
# =============================================================================
# Link build statements for EXECUTABLE target hello_world
The example shows what may be considered an in-source build. That is when the build directory is the same or a sub-directory of the src folder (not that there is a hard definition or anything, but this does trigger the ninja issue of using relative paths on the command line). Try mkdir ~/cmake_build && cd ~/cmake_build && cmake ~/cmake_test then it should use absolute paths for everything.
Either way there really isn't a specific way to force one or the other. In general cmake generators will use absolute paths for everything that ends up used on the command line. There seems to be issues with Ninja that prevent the generator from using absolute paths for in-source builds (https://github.com/ninja-build/ninja/issues/1251).
I want to rename certain executables in CMakeLists.txt but also want symbolic links from the older names to new files for backward compatibility. How can this be accomplished on systems that support symbolic links?
Also what are the alternatives for system that does not support symbolic links?
Another way to do it:
INSTALL(CODE "execute_process( \
COMMAND ${CMAKE_COMMAND} -E create_symlink \
${target} \
${link} \
)"
)
This way the symlinking will be done during make install only.
You can create a custom target and use CMake to create symlinks
ADD_CUSTOM_TARGET(link_target ALL
COMMAND ${CMAKE_COMMAND} -E create_symlink ${target} ${link})
This will only work on systems that support symlinks, see the documentation.
Before CMake v3.14, this did not work on Windows. In v3.13, support for Windows was added.
Another method that is a bit more verbose and only runs on install:
macro(install_symlink filepath sympath)
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${filepath} ${sympath})")
install(CODE "message(\"-- Created symlink: ${sympath} -> ${filepath}\")")
endmacro(install_symlink)
Use it like this (similar to ln -s):
install_symlink(filepath sympath)
Lets say you need to create a link in binary dir to a target located in source directory.
You can try file CREATE_LINK since version 3.14
${CMAKE_COMMAND} -E create_symlink is accessible at Windows since 3.17
You can use execute_process since early cmake versions:
if(WIN32)
get_filename_component(real_path "${dirname}" REALPATH)
string(REPLACE "/" "\\" target_path "${real_path}")
execute_process(
COMMAND cmd /C mklink /J ${dirname} "${target_path}"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
)
else()
execute_process(COMMAND "ln -s ${CMAKE_CURRENT_SOURCE_DIR}/${dirname} ${CMAKE_CURRENT_BINARY_DIR}/${dirname}")
endif()
I've added the check to #ulidtko's approach, so symlink doesn't overridden on every rebuild unconditionally:
install(CODE "if (NOT EXISTS ${link})
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink \
${target} \
${link})
endif()" )
When I build, I create a symlink to the compile_commands.json. This helps clangd and vscode's intellisense.
Here's a full working example of what worked for me.
add_custom_command is what creates the symlink.
cmake_minimum_required(VERSION "3.1.0")
# project name
project("a-s-i-o")
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -std=c++17 -pthread -Wall -Wextra -Werror -pedantic -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-unused-private-field"
)
# Create `compile_commands.json` file.
#
# This is required by `clangd` to find the header files. For this to work, this
# file must be in the root of the project. Therefore, we create a symbolic link in the root that
# points to the `compile_commands.json` file created by CMake in the `build` directory.
#
# This specific commands creates the file in the build directory. The command `add_custom_command`
# creates the symbolic link in the root directory.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_executable(x main.cpp)
target_include_directories(x PUBLIC "/usr/local/include/asio-1.20.0/include")
# When target `x` is built, a symlink will be created to
# `build/compile_commands.json`. This is required for clangd to be able to find
# the header files included in the project.
add_custom_command(TARGET x
COMMAND ${CMAKE_COMMAND} -E create_symlink "${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json" "../compile_commands.json"
DEPENDS compile_commands.json
VERBATIM ON
)
# Print success message to the console
add_custom_command(TARGET x POST_BUILD
COMMAND echo "Created symlink pointing to `compile_commands.json`"
DEPENDS compile_commands.json
VERBATIM ON
)
I've struggled with this a few different ways with the above responses in order to install '.so.#' files that refer to other '.so.#.#' files. I've had success by not introducing a link to the file, but by installing the '.so.#.#' file as the '.so.#' file.
I.e. instead of
install(
FILES
.../libmpi.so.12.0
DESTINATION lib
)
install(CODE
"EXECUTE_PROCESS( ${CMAKE_COMMAND} -E create_symlink lib/libmpi.so.12.0 lib/libmpi.so.12)")
Which didn't quite work for me even. I have instead had success by doing this.
install(FILES
.../libmpi.so.12.0
RENAME libmpi.so.12
DESTINATION lib
)
Not 'exactly' the same, but sufficient. Don't defeat the problem, solve the problem.
If what you are looking for is links for your executables and library files based on version numbers you can let CMake take care of that for you by setting the appropriate properties SOVERSION and VERSION on your targets:
The following CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(version_links C)
add_library(mylib SHARED lib.c)
set_target_properties(mylib PROPERTIES
PUBLIC_HEADER lib.h
VERSION 3.2.1
SOVERSION 3.2
)
add_executable(exec exec.c)
target_link_libraries(exec PRIVATE mylib)
set_target_properties(exec PROPERTIES
VERSION 3.2.1
)
install(TARGETS mylib exec)
produces the following tree inside CMAKE_INSTALL_PREFIX:
${CMAKE_INSTALL_PREFIX}
├── bin
│ ├── exec -> exec-3.2.1
│ └── exec-3.2.1
├── include
│ └── lib.h
└── lib
├── libmylib.so -> libmylib.so.3.2
├── libmylib.so.3.2 -> libmylib.so.3.2.1
└── libmylib.so.3.2.1
References:
https://cmake.org/cmake/help/latest/command/set_target_properties.html
https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#target-properties
https://cmake.org/cmake/help/latest/prop_tgt/SOVERSION.html
I'm encountering an annoying error in CMake where the following conditions hold:
A target name is identical to name of the directory defining the target.
I am using the RUNTIME_OUTPUT_DIRECTORY property for the target.
Under these conditions, I get the errors:
Linking CXX executable .
/usr/bin/ld: cannot open output file .: Is a directory
CMake seems to be trying to build a target named . , apparently trying to refer to the current directory name rather than the desired target name.
Here's a trivial example. My file tree is:
/tmp/example$ tree
.
├── build
└── src
├── CMakeLists.txt
└── hello_world
├── CMakeLists.txt
└── HelloWorld.cpp
src/CMakeLists.txt:
set (ARBITRARY_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
add_subdirectory(hello_world)
src/hello_world:
add_executable(hello_world HelloWorld.cpp)
set_property(TARGET hello_world PROPERTY RUNTIME_OUTPUT_DIRECTORY ${ARBITRARY_OUTPUT_DIR})
...and HelloWorld.cpp itself is a trivial Hello World program, with a main() method.
I run:
/tmp/example/build$ cmake ../src/ -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc-4.8 -DCMAKE_CXX_COMPILER=g++-4.8 -G"Unix Makefiles"
/tmp/example/build$ make VERBOSE=1
And I get:
[ 50%] Building CXX object hello_world/CMakeFiles/hello_world.dir/HelloWorld.o
cd /tmp/example/build/hello_world && /usr/bin/g++-4.8 -O3 -DNDEBUG -o CMakeFiles/hello_world.dir/HelloWorld.o -c /tmp/example/src/hello_world/HelloWorld.cpp
[100%] Linking CXX executable .
cd /tmp/example/build/hello_world && /usr/bin/cmake -E cmake_link_script CMakeFiles/hello_world.dir/link.txt --verbose=1
/usr/bin/g++-4.8 -O3 -DNDEBUG CMakeFiles/hello_world.dir/HelloWorld.o -o . -rdynamic
/usr/bin/ld: cannot open output file .: Is a directory
collect2: error: ld returned 1 exit status
As you can see, CMakeFiles/hello_world.dir/link.txt sets the linking target as -o . , which obviously won't work.
Is this a bug in CMake, or am I doing something wrong? Is there some workaround for this?
My tools are:
cmake version 3.5.1
Ubuntu 16.04 LTS (Xenial Xerus)
g++-4.8
Your top-level build directory contains hello_world directory because of command add_subdirectory(hello_world).
By setting RUNTIME_OUTPUT_DIRECTORY property to top-level build directory, you want to create executable file with name hello_world there.
But in single directory it is impossible to have both file and directory with the same name.
You need to rename either subdirectory or executable or change directory to place executable.