23

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?

2
  • 1
    Also what are the alternatives for system that does not support symbolic links? - Copy files instead of rename. Or create script/tiny program with old name, which just executes program with new name.
    – Tsyvarev
    Commented Mar 3, 2016 at 7:22
  • @Tsyvarev If we're talking about MS-Windows, Symbolic links were available since Win 7. Anything older than that should not be used (even Win 8 should not be used anymore...) The Win 7 symbolic link was only documented in the actually command line that one could use to create such... and since I haven't used Windows for a long time, I don't remember what the command was. Commented Dec 30, 2021 at 20:09

9 Answers 9

32

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.

1
31

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.

3
  • This way symlink will be overridden every build. How could I avoid it?
    – bam
    Commented Oct 29, 2020 at 1:42
  • @bam no it won't. make all is "the build" — while make install is, depending how you cook it: deployment, packaging step, distribution step, or formation of build artifacts, etc. You could use a trivial IF(NOT EXISTS ${target}) no-brainer to skip symlink re-creation at make install time — but I strongly advise against doing that. It'd be "unexpected smartness" violating KISS; exceedingly likely you'll get bitten by stale symlinks, much later, more likely your colleagues and/or successors than you. Don't do it, find another solution.
    – ulidtko
    Commented Feb 5, 2021 at 11:22
  • @ulidtko I'd have to try, but running make install multiple times (after some changes to the source...) is likely to create a bigger problem than a staled symlink unless the cmake create_symlink command re-creates the link when it already exists? Commented Dec 30, 2021 at 20:21
21

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)
0
4

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()
2

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

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()" )
1

There is file(CREATE_LINK <original> <linkname> SYMBOLIC) since CMake 3.14. It creates a symlink at configure time.

Not sure this is what OP asked for, but this is what I was looking for when found this question.

0

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.

0

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:

Not the answer you're looking for? Browse other questions tagged or ask your own question.