How to get a relative path to a target with CMake? - bash

I have a project that uses CMake to generate build scripts, and each platform puts the executable that are generated in a different place. We have a non-programmer on our team who does not have build tools and I want to be able to bundle up all the files, scripts and executables necessary to run the project so that he can run the project.
To do that, I've added a custom target that takes all the necessary files and zips them up. It also generates a very simple script (which is included in the zip file) that he can just click on that should run a script then launch the executable, like so:
add_custom_target(runcommand
COMMAND echo '\#!/bin/bash -eu' > run.command &&
echo 'cd `dirname $$0`' >> run.command &&
echo './scripts/prerun_script.sh && ${MY_EXECUTABLE}' >> run.command &&
chmod +x run.command)
The problem with this is that MY_EXECUTABLE is a hardcoded path to the executable on my system. I would like it to be a relative path so that I can just take this resultant zip file, unzip it anywhere and run it from there. What I would like is to get the path to MY_EXECUTABLE relative to the root directory of my project so that this script can be run from anywhere.

You can use:
file(RELATIVE_PATH variable directory file)
Concretely, assume that MY_ROOT contains the root directory (there might be a suitable predefined CMake variable for this), this will set rel to the relative path of MY_EXECUTABLE:
file(RELATIVE_PATH rel ${MY_ROOT} ${MY_EXECUTABLE})

In case where you want to get path relative to project root, you may use PROJECT_SOURCE_DIR CMake variable:
file(RELATIVE_PATH rel ${PROJECT_SOURCE_DIR} ${MY_EXECUTABLE})
Remember, that MY_EXECUTABLE must contain full path to file.

get the relative path to the current source directory
file(RELATIVE_PATH relative ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
Test:
message(STATUS "source base: \"${CMAKE_SOURCE_DIR}\"")
message(STATUS "current: \"${CMAKE_CURRENT_SOURCE_DIR}\"")
message(STATUS "relative: \"${relative}\"")
Result:
--source base: "C:/Project/Software/Test/source"
--current: "C:/Project/Software/Test/source/unittest/libs/fsm
--relative: "unittest/libs/fsm"

Something like this would work:
THISDIR=`pwd`
MY_EXECUTABLE=$THISDIR/test/someexec
REL_EXECUTABLE=${MY_EXECUTABLE##$THISDIR/}
echo $REL_EXECUTABLE
Basically, this chops off the base path from $MY_EXECUTABLE. In this example, I just set up MY_EXECUTABLE as a test to give it a full path. The one-line equivalent for what you want is:
${MY_EXECUTABLE##`pwd`/}
I guess this assumes pwd is the root of your project. Otherwise, see the Cmake variable doc.
(As an aside, this makes me nostalgic for the ksh days.)

Related

cmake 'add_custom_command' to pre-process header files?

i'm working on a project requiring cmake. i'd like to add some custom rules to my makefile, but can't quite get my head around how to do it.
both c source files and header files are in the same directory. also in this same directory are a number of .def files, which are the sources for some of the header files #included in the source during compilation.
if i were to do this in a makefile, i'd use a simple rule like
.SUFFIXES: .def
.def.h:
$(PREPROC) $< > $#
how can i do this with cmake ??
i've tried various permutations of the following, both with and without cmake working directory specifications :
add_custom_command(
OUTPUT vvr_const.h
PRE_BUILD
COMMAND preproc vvr_const.def > vvr_const.h
DEPENDS vvr_const.def
)
add_custom_target(vvr_const.h DEPENDS vvr_const.def)
but the header file isn't generated by the time the c source file is compiled, so the compile fails. i've also tried a variation where i replace the last line above with
set_property(SOURCE main.c APPEND PROPERTY OBJECT_DEPENDS vvr_const.h)
in this case, the header file is correctly generated in advance, but make can't find it, and complains that there's no rule to make the target .h.
ideally this would be a general rule, like the make rule above, but i'm not opposed to making a separate rule for each of the .def files if that's what it takes.
cheers.
There are 2 problems with the add_custom_command approach you present:
You did not specify a working directory; by default the command is run in the build directory, not in the source directory.
You rely on shell functionality here (the redirect to a file). Even though this probably still works. You should go with an approach that does not rely on the shell.
To solve issues 1 and 2 I recommend creating a seperate cmake script file receiving the absolute paths to input and output files and using those in the custom command. This allows you to use execute_process to specify the file to write without relying on the platform.
preprocess_def.cmake
# preprocess def file
# parameters INPUT_FILE and OUTPUT_FILE denote the file to use as source
# and the file to write the results to respectively
# use preproc tool to get data to write to the output file
execute_process(COMMAND preproc "${INPUT_FILE}"
RESULT_VARIABLE _EXIT_CODE
OUTPUT_FILE "${OUTPUT_FILE}")
if (_EXIT_CODE)
message(FATAL_ERROR "An error occured when preprocessing the file ${INPUT_FILE}")
endif()
CMakeLists.txt
set(_INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vvr_const.def")
set(_OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vvr_const.h")
# not necessary to use build event here, if we mark the output file as generated
add_custom_command(OUTPUT "${_OUTPUT_FILE}"
COMMAND "${CMAKE_BUILD_TOOL}" -D "OUPUT_FILE=${_OUTPUT_FILE}" -D "INPUT_FILE=${_INPUT_FILE}" -P "${CMAKE_CURRENT_SOURCE_DIR}/preprocess_def.cmake"
DEPENDS "${_INPUT_FILE}")
add_executable(my_target vvr_const.h ...)
set_source_files_properties(vvr_const.h PROPERTIES GENERATED 1)
Documentation from cmake:
PRE_BUILD
On Visual Studio Generators, run before any other rules are executed within the target. On other generators, run just before PRE_LINK commands.
So possibly your command is just running too late.

Cmake doesn't recognize custom command as valid source

I have a problem modifying existing CMake based build-system. All I want to do is add different build rule for some of .c files. For the sake of this problem, I will focus on only one file.
The simplified directories tree looks like this
Project:
./src
- file_of_interest.c
- CmakeFiles.txt
other_files.c
CmakeFiles.txt
So in order to compile file_of_interest.c in a different way I do:
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.s
COMMAND xt-xcc -S ${CMAKE_CURRENT_SOURCE_DIR}/file_of_interest.c
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/file.c
COMMENT "Generating file_of_interest.s"
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.c.o
COMMAND xt-xcc ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.s -o file_of_interest.c.o -c
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.s
COMMENT "Generating file_of_interest.c.o"
)
message(" ${CMAKE_CURRENT_BINARY_DIR} \n ${CMAKE_CURRENT_SOURCE_DIR}")
target_sources(target_name PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.c.o)
As you can see I used CMake's message() to print paths to be sure all is set up properly. It should work, but it doesn't! I expect CMake to register file_of_interest.c.o as source of target_name (and this is probably working), then "tie" it with my custom command which produces OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.c.o and then to tie again ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.c.o with OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.s and this certainly doesn't happen as CMake shows error message saying CMake Error at CMakeLists.txt:170 (add_executable): Cannot find source file: path/file_of_interest.c.o
The path is OK. So the question is why does CMake cannot recognize recipes for that file?
It seems that you create an executable (call add_executable) in the top-level CMakeLists.txt, but add_custom_command are called from the subdirectory (src/CMakeLists.txt).
This doesn't work: when CMake processes add_executable and searches for the commands generating its sources, it sees only add_custom_commands created in the same CMakeLists.txt.
From the add_custom_command documentation:
A target created in the same directory (CMakeLists.txt file) that specifies any output of the custom command as a source file is given a rule to generate the file using the command at build time.
It is unrelated that target_sources is called from the same directory as add_custom_command: the target is created by add_executable command, and target_sources just modifies its properties.

Getting the fulll path of an included file in the Makefile

A Makefile contains
include ../../common/common.mk
at the end of the file. I want to see the full path during the invocation. How can I do that?
$(info full path to common.mk: $(abspath ../../common/common.mk))
include ../../common/common.mk
Usually, make does not change working directory during execution. Most likely, you can check for ../../common/common.mk from command line just before running **make*. But if you use make -C some/directory then the working directory will be whatever you specify after -C (relative to your current directory).

Automake: Remove configure script checks with make dist

I'm wondering if there is a way to remove dependency checks from a configure script when make dist is run. The purpose is that the version of the package in the repository uses emacs to execute lisp files and generate .c files, whereas those generated source files are included in the tarball when make dist is run. Automake supports including built files in the output tarball simply by setting EXTRA_DIST in the Makefile. I would like to be able to check for emacs only when someone tries to compile from the repository, but not when someone compiles from the tarball.
More concretely, in configure.ac I have:
AM_PATH_LISPDIR
AS_IF([test "$EMACS" = no], [AC_MSG_ERROR([cannot find emacs])])
but I would like that check not to occur after make dist is run. Any way to do this? If not, how is this case normally handled?
The best method I've come up with is to check for the presence of the Bootstrap file. Bootstrap is removed from the tarball after make dist is run.
AC_CHECK_FILE([Bootstrap])
if test "$ac_cv_file_Bootstrap" == yes ; then
<check for emacs>
fi
I'd normally set a conditional if I find the tool in configure.ac:
AM_PATH_LISPDIR
AM_CONDITIONAL([HAVE_EMACS_IN_REPOSITORY], [test "x$EMACS" != xno -a -d ".git"])
AS_IF([test "$EMACS" = no -a -d ".git"], [AC_MSG_ERROR([cannot find emacs])])
Then in Makefile.am
if HAVE_EMACS_IN_REPOSITORY
Execute lisp files...
Generate .c files...
endif
Usually configure ends up in the same location as .git. If not, the path can be adjusted.

Make gcc put relative filenames in debug information

The project I'm compiling uses CMake, which loves absolute pathnames.
When I compile with debugging information enabled, gcc puts those long names into .debug_str sections, which is bad for debugging. I'd like to have short relative-to-project-root pathnames there instead.
Is there some option to tell gcc to strip some part of pathname before emitting debug data? Or, maybe, there is some tool that could do that on compiled binaries?
I've tried using SET(CMAKE_USE_RELATIVE_PATHS ON) (which seems to be frowned upon by devs) option, but as I'm using out-of-source builds, pathnames are still not in the form I'd want them to be. I.e. they're ./../src/mod_foo/foo.c instead of mod_foo/foo.c.
You can use the -fdebug-prefix-map flag to remap the debugging information paths. For example, to make the paths relative to the build location use: -fdebug-prefix-map=/full/build/path=.
You can set the RULE_LAUNCH_COMPILE property of a CMake target to have CMake invoke a shell script which transforms the source file path to a project relative path before invoking gcc. Use the CMake function configure_file to generate a shell script which knows about the PROJECT_SOURCE_DIR and PROJECT_BINARY_DIR of your project.
In your outermost CMakeLists.txt add the following code:
configure_file(
"${PROJECT_SOURCE_DIR}/gcc_debug_fix.sh.in"
"${PROJECT_BINARY_DIR}/gcc_debug_fix.sh"
#ONLY)
add_executable (MyExecutable ...)
set_target_properties(MyExecutable PROPERTIES
RULE_LAUNCH_COMPILE "${PROJECT_BINARY_DIR}/gcc_debug_fix.sh")
The following template shell script gcc_debug_fix.sh.in needs to go to the root directory of the CMake project:
#!/bin/sh
PROJECT_BINARY_DIR="#PROJECT_BINARY_DIR#"
PROJECT_SOURCE_DIR="#PROJECT_SOURCE_DIR#"
# shell script invoked with the following arguments
# $(CXX) $(CXX_DEFINES) $(CXX_FLAGS) -o OBJECT_FILE -c SOURCE_FILE
# extract parameters
SOURCE_FILE="${#: -1:1}"
OBJECT_FILE="${#: -3:1}"
COMPILER_AND_FLAGS=${#:1:$#-4}
# make source file path relative to project source dir
SOURCE_FILE_RELATIVE="${SOURCE_FILE:${#PROJECT_SOURCE_DIR} + 1}"
# make object file path absolute
OBJECT_FILE_ABSOLUTE="$PROJECT_BINARY_DIR/$OBJECT_FILE"
cd "$PROJECT_SOURCE_DIR"
# invoke compiler
exec $COMPILER_AND_FLAGS -c "${SOURCE_FILE_RELATIVE}" -o "${OBJECT_FILE_ABSOLUTE}"
The shell script uses the information from the variables PROJECT_BINARY_DIR and PROJECT_SOURCE_DIR to transform the path of the source file to a path relative to the project root and the object file's path to an absolute path. Because gcc gets passed a project relative path now, .debug_str should use that path, too.
The following caveats apply:
Be sure to set the executable bit of gcc_debug_fix.sh.in.
For the script to work CMAKE_USE_RELATIVE_PATHS has to set to OFF again.
The script makes assumptions about the location of the file paths on the command line. This may not work if CMake uses a different rule to invoke the compiler. A more robust solution would be to scan the script arguments for the -o and -c flags.
If I really couldn't fix the make file/tool to do this properly, I would write a wrapper script for gcc that recognises absolute pathnames and converts then to relative ones.
It might look something like this in bash:
#!/bin/bash
out=()
for arg; do
out=("${out[#]}" $(echo "$arg" | sed 's:/my/absolute/directory/:../:'))
done
exec gcc "${out[#]}"
If your source directory has subdirectories then you'll need to handle those carefully, but the above should work for a flat source directory. I've not tested it though, and I wouldn't be surprised if I've got the quoting wrong, but that'll only be a problem if you have pathnames with spaces in. It also doesn't handle parameters like -I/whatever/include, but you can fix that.

Resources