I have a git repository with another repository as a submodule. I wish to symlink single files from the submodule into my repository, instead of maintaining file(COPY ...) lines in CMakeLists.txt. As it is a repository, the symlinks have to be relative.

Is it possible to tell cmake, to either resolve the symlink to an absolute path, before copying it or copy the linked file instead?

I am looking for a general option. If I have to add specific commands for each file, I might as well use file(COPY ...)

The cmake version used is 3.16.3

Some Background:

We have a server application, which can be build against various front-ends. Each front-end requires its own configuration, but they share some files.

To automate the configuration we use a config-generator-script. This script consumes a set of files and directories, which are prepared by cmake from a source.

I want to share some files between these configurations, by adding the configuration repository, maintained by the developer of the application, as a submodule to my configuration repository.

To me, symlinks are easier to maintain and better visible. Partly because the CMakeLists.txt is usually not touched during updates, except to change the version. Some of the files-to-be-linked are referenced to in other source files and might be needed for reference during development. If a developer does not know, that this file is copied from someplace else during build generation, the missing file might lead to some confusion.

Example:

<repository>/shared/dir/file
<repository>/dir
  • Generate symlink in repository/dir/ via:
cd <repository>/dir
ln -s ../shared/dir/file .
  • Add CMakeLists.txt with the following content:
project(example NONE)

set(${PROJECT_NAME}_MAJOR_VERSION 00)
set(${PROJECT_NAME}_MINOR_VERSION 01)
set(${PROJECT_NAME}_PATCH_VERSION 00)
include(cmake/set_version_numbers.cmake)

include(cmake/config_generator_project.cmake)
  • Call cmake in some build directory
cmake <path_to_repository>

Result: Broken symlink <build-dir>/dir/file, instead of a file or symlink.

1

There are 1 best solutions below

0
Amadrath On

The short answer is: Yes, it is possible, but not easy.

The longer answer:

First, I have to explain a misunderstanding of mine. I am so used to working with this project template, that I assumed, copying the source files into a build directory is an innate process of cmake. It is not. It is actually described in one of the cmake-scripts in the project template.

Second, there is no single command or option, to tell cmake to resolve a link by copying the linked file (At least not up till cmake version 3.16). Instead I replaced the following command:

file(COPY "${PROJECT_SOURCE_DIR}/config_dir" DESTINATION "${PROJECT_BINARY_DIR}")

with this function:

function(copy_with_resolved_links source_dir target_dir)
  file(GLOB_RECURSE file_list LIST_DIRECTORIES true "${source_dir}/*")
  file(MAKE_DIRECTORY "${target_dir}/temp")
  foreach(file ${file_list})
    file(RELATIVE_PATH rpath_file "${PROJECT_SOURCE_DIR}" ${file})
    get_filename_component(rpath_dir ${rpath_file} DIRECTORY)
    if(IS_DIRECTORY ${file})
      file(MAKE_DIRECTORY "${target_dir}/${rpath_file}")
    elseif(IS_SYMLINK ${file})
      file(READ_SYMLINK "${file}" link)
      get_filename_component(fn_target "${file}" NAME)
      get_filename_component(fn_link "${link}" NAME)
      if(NOT IS_ABSOLUTE ${link})
        get_filename_component(dir "${file}" DIRECTORY)
        file(COPY "${dir}/${link}" DESTINATION "${target_dir}/temp")
        file(RENAME "${target_dir}/temp/${fn_link}" "${target_dir}/${rpath_file}")
      else()
        file(COPY "${link}" DESTINATION "${target_dir}/temp")
        file(RENAME "${target_dir}/temp/${fn_link}" "${target_dir}/${rpath_file}")
      endif()
    else()
      file(COPY ${file} DESTINATION "${target_dir}/${rpath_dir}")
    endif()
  endforeach()
  file(REMOVE_RECURSE "${target_dir}/temp")
endfunction()

This function copies all files in a directory and its sub-directories to a destination, but if the file is a relative symlink, instead of the symlink-files it copies the target file, but changes its name to the one of the symlink-file.

This way I can use symlinks in the project to point to shared resources outside of the project directory, as well as for symlinking to files in the same directory, i.e. chain-linking (a symlink, which contains just a filename, is also considered non-absolute).

This function could be easily modified, to generate symbolic or hard links to the target, instead of copying the target file or to work on a single file instead of a directory.

Update

Since the answer above, I learned of an alternative and easier way, to solve my problem. file(COPY) has the flag FOLLOW_LINK_CHAIN, which function is described in the documentation as follows:

If FOLLOW_SYMLINK_CHAIN is specified, COPY will recursively resolve the symlinks at the paths given until a real file is found, and install a corresponding symlink in the destination for each symlink encountered. For each symlink that is installed, the resolution is stripped of the directory, leaving only the filename, meaning that the new symlink points to a file in the same directory as the symlink. This feature is useful on some Unix systems, where libraries are installed as a chain of symlinks with version numbers, with less specific versions pointing to more specific versions. FOLLOW_SYMLINK_CHAIN will install all of these symlinks and the library itself into the destination directory.

This led me to believe, that it only maintains chain links, as they are common for libraries. But after testing it, I learned, that it works for me as well. Here is, what it does:

  • If there is a symlink with the same name as the file, it copies the file in its stead (which is, what I was looking for)
  • If the symlinks filename differs from the targets filename, the target file is copied, and a new symlink, pointing to the copied file is generated.
  • If the target file already is in the same directory, just the symlinks are created.

So this is my solution:

file(COPY "${PROJECT_SOURCE_DIR}/config_dir" DESTINATION "${PROJECT_BINARY_DIR}" FOLLOW_SYMLINK_CHAIN)