How to directly execute cmake script without -P? - cmake

I know -P is used to execute a cmake script file. But I don't want create a file for a simple logic. I want to execute statement like this:
cmake "if(foo) do_something endif()"
The reason I want this feature is that I want to use if/else in add_custom_target and add_custom_command to execute some command according to whether a CMAKE variable is defined.

Finally I got a good solution to this. Now describe as follows.
Let's say I want to add two targets, whose aim are to create docker images and upload to remote repository when issuing make images and make publish. I use VERSION , GIT_COMMIT and PATCH to compose an image tag. GIT_COMMIT can be obtained from git log by using execute_process, and I need to pass newer VERSION and PATCH from command line every time I want to create images. (But if I don't plan to create, they won't be given)
So the complete statements are like this:
execute_process(COMMAND git log --pretty=format:%h -n 1 OUTPUT_VARIABLE GIT_COMMIT)
set(DOCKER_CERBERUS_URL "docker-registry.com/db/cerberus:${VERSION}-${GIT_COMMIT}.${PATCH}")
add_custom_target(images
COMMENT "Creating docker images ..."
# clean the cache
COMMAND rm -f CMakeCache.txt # important !
# if VERSION and PATCH are not given, don't build it
COMMAND /bin/sh -c "if [ x${VERSION} != x -a x${PATCH} != x ]; then docker build -t ${DOCKER_CERBERUS_URL} ${CMAKE_CURRENT_SOURCE_DIR}/docker/; fi"
DEPENDS cerberus
VERBATIM )
add_custom_target(publish
COMMENT "Uploading images ..."
COMMAND docker push ${DOCKER_CERBERUS_URL}
DEPENDS images )
Then every time I want to create or publish with newer version number and patch number, I use cmake .. -DVERSION=xxx -DPATCH=xxx, make images or make publish.
There's something which may be strange. When I first issue cmake .. -DVERSION=xxx -DPATCH=xxx, then make images, sh command will be executed. Then I issue make images again, sh command will not be executed. My guess is: in the first run of make images, cmake will first load variable from CMakeCache.txt into memory, then sh command can get variables's value from memory, and at this time CMakeCache.txt has been deleted. So in the second run, the variables have no value.

Related

How to forward output from CMake execute_process to CMake's logs?

In my CMake file, I set up a Python test environment:
execute_process(
COMMAND pip install -U -r ${REQUIREMENTS}
RESULT_VARIABLE STATUS
)
The issue is, I usually don't need its verbose OUTPUT. So I want to optionally hide it. This is what I've done:
if(SHOW_PIP_LOGS)
execute_process(...)
else()
execute_process(... OUTPUT_QUIET)
endif()
The thing is, there is already a way to control what logs are shown in CMake: it's --log-level coupled with message(). This way I don't need to manage any logging-related variables. But the command outputs directly to stdout, without going through CMake log system.
Can I somehow forward the output of a command invocation to CMake's logs?
The output must be printed on-line, without buffering everything to a variable first, so that if a pip takes a long time installing packages, I can see what's going on.

How to run sanitizers on whole project

I'm trying to get familiar with sanitizers as ASAN, LSAN etc and got a lot of useful information already from here: https://developers.redhat.com/blog/2021/05/05/memory-error-checking-in-c-and-c-comparing-sanitizers-and-valgrind
I am able to run all sort of sanitizers on specific files, as shown on the site, like this:
clang -g -fsanitize=address -fno-omit-frame-pointer -g ../TestFiles/ASAN_TestFile.c
ASAN_SYMBOLIZER_PATH=/usr/local/bin/llvm-symbolizer ./a.out >../Logs/ASAN_C.log 2>&1
which generates a log with found issue. Now I would like to extend this to run upon building the project with cmake. This is the command to build it at the moment:
cmake -S . -B build
cd build
make
Is there any way I can use this script with adding the sanitizers, without having to alter the cmakelist.txt file??
For instance something like this:
cmake -S . -B build
cd build
make -fsanitize=address
./a.out >../Logs/ASAN_C.log 2>&1
The reason is that I want to be able to build the project multiple times with different sanitizers (since they cannot be used together) and have a log created without altering the cmakelist.txt file (just want to be able to quickly test the whole project for memory issues instead of doing it for each file created).
You can add additional compiler flags from command line during the build configuration:
cmake -D CMAKE_CXX_FLAGS="-fsanitize=address" -D CMAKE_C_FLAGS="-fsanitize=address" /path/to/CMakeLists.txt
If your CMakeLists.txt is configured properly above should work. If that does not work then try adding flags as environment variable:
cmake -E env CXXFLAGS="-fsanitize=address" CFLAGS="-fsanitize=address" cmake /path/to/CMakeLists.txt

How to use CMake cached variables inside subprocess called by custom target?

My project contains a custom target which generates some output via .cmake script. It looks like this:
add_custom_target(TargetName
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake/script.cmake
BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/generated/output
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
VERBATIM
)
But now I want to set come cache variables inside the script. I tried doing like that:
message("MY_CACHE_VARIABLE = ${MY_CACHE_VARIABLE}")
set(MY_CACHE_VARIABLE "VALUE" CACHE INTERNAL "")
And I faced with the problem that cache variables are not saved. It always prints me empty output:
MY_CACHE_VARIABLE =
I already tried setting working directory as CMAKE_BINARY_DIR, or passing CMAKE_BINARY_DIR of the last argument of cmake command, or passing -B ${CMAKE_BINARY_DIR} or -C ${CMAKE_BINARY_DIR}/CMakeCache.txt as arguments and etc. None of these worked.
So is there any way to reuse existing cache inside CMake subprocess or I just should write my own cache inside the script?
You have to distinguish between running CMake to generate build files (for Make, Ninja, etc.) and running CMake in script mode:
Script mode simply runs the commands in the given CMake Language source file and does not generate a build system. It does not allow CMake commands that define build targets or actions.
-- cmake-language(7)
No configure or generate step is performed and the cache is not modified.
-- cmake(1)
So in script mode (-P), CMake is not aware of the cache or any variable/target/etc. defined in your regular CMakeLists.txt files. It is more similar to executing a bash/shell script than to processing a "usual" CMakeLists.txt.
But don't worry, there is still a solution to your problem. You can simply pass your arguments as -D options to your script:
add_custom_target(TargetName
COMMAND ${CMAKE_COMMAND}
-DMY_VAR="..."
-DANOTHER_VAR="..."
-P ${CMAKE_SOURCE_DIR}/cmake/script.cmake
...
)
Note however:
If variables are defined using -D, this must be done before the -P argument.
-- cmake(1)

From CMake setup 'make' to use '-j' option by default

I want my CMake project to be built by make -j N, whenever I call make from the terminal. I don't want to set -j option manually every time.
For that, I set CMAKE_MAKE_PROGRAM variable to the specific command line. I use the ProcessorCount() function, which gives the number of procesors to perform build in parallel.
When I do make, I do not see any speed up. However if I do make -j N, then it is built definitely faster.
Would you please help me on this issue? (I am developing this on Linux.)
Here is the snippet of the code that I use in CMakeList.txt:
include(ProcessorCount)
ProcessorCount(N)
message("number of processors: " ${N})
if(NOT N EQUAL 0)
set(CTEST_BUILD_FLAGS -j${N})
set(ctest_test_args ${ctest_test_args} PARALLEL_LEVEL ${N})
set(CMAKE_MAKE_PROGRAM "${CMAKE_MAKE_PROGRAM} -j ${N}")
endif()
message("cmake make program" ${CMAKE_MAKE_PROGRAM})
Thank you very much.
In case you want to speed up the build you can run multiple make processes in parallel but not cmake.
To perform every build with predefined number of parallel processes you can define this in MAKEFLAGS.
Set MAKEFLAGS in your environment script, e.g. ~/.bashrc as you want:
export MAKEFLAGS=-j8
On Linux the following sets MAKEFLAGS to the number of CPUs - 1: (Keep one CPU free for other tasks while build) and is useful in environments with dynamic ressources, e.g. VMware:
export MAKEFLAGS=-j$(($(grep -c "^processor" /proc/cpuinfo) - 1))
New from cmake v3.12 on:
The command line has a new option --parallel <JOBS>.
Example:
cmake --build build_arm --parallel 4 --target all
Example with number of CPUs- 1 using nproc:
cmake --build build_arm --parallel $(($(nproc) - 1)) --target all
Via setting the CMAKE_MAKE_PROGRAM variable you want to affect the build process. But:
This variable affects only the build via cmake --build, not on native tool (make) call:
The CMAKE_MAKE_PROGRAM variable is set for use by project code. The value is also used by the cmake(1) --build and ctest(1) --build-and-test tools to launch the native build process.
This variable should be a CACHEd one. It is used in such way by make-like generators:
These generators store CMAKE_MAKE_PROGRAM in the CMake cache so that it may be edited by the user.
That is, you need to set this variable with
set(CMAKE_MAKE_PROGRAM <program> CACHE PATH "Path to build tool" FORCE)
This variable should refer to the executable itself, not to a program with arguments:
The value may be the full path to an executable or just the tool name if it is expected to be in the PATH.
That is, value "make -j 2" cannot be used for that variable (splitting arguments as list
set(CMAKE_MAKE_PROGRAM make -j 2 CACHE PATH "Path to build tool" FORCE)
wouldn't help either).
In summary, you may redefine the behavior of cmake --build calls with setting the CMAKE_MAKE_PROGRAM variable to the script, which calls make with parallel options. But you may not affect the behavior of direct make calls.
You may set the env variable MAKEFLAGS using this command
export MAKEFLAGS=-j$(nproc)
My solution is to have a small script which will run make include all sorts of other features, not just the number of CPUs.
I call my script mk and I do a chmod 755 mk so I can run it with ./mk in the root of my project. I also have a few flags to be able to run various things with a simple command line. For example, while working on the code and I get many errors, I like to pipe the output to less. I can do that with ./mk -l without having to retype all the heavy duty Unix stuff...
As you can see, I have the -j4 in a couple of places where it makes sense. For the -l option, I don't want it because in this case it would eventually cause multiple errors to be printed at the same time (I tried that before!)
#!/bin/sh -e
#
# Execute make
case "$1" in
"-l")
make -C ../BUILD/Debug 2>&1 | less -R
;;
"-r")
make -j4 -C ../BUILD/Release
;;
"-d")
rm -rf ../BUILD/Debug/doc/lpp-doc-?.*.tar.gz \
../BUILD/Debug/doc/lpp-doc-?.*
make -C ../BUILD/Debug
;;
"-t")
make -C ../BUILD/Debug
../BUILD/Debug/src/lpp tests/suite/syntax-print.logo
g++ -std=c++14 -I rt l.cpp rt/*.cpp
;;
*)
make -j4 -C ../BUILD/Debug
;;
esac
# From the https://github.com/m2osw/lpp project
With CMake, it wouldn't work unless, as Tsyvarev mentioned, you create your own script. But I personally don't think it's sensible to call make from your make script. Plus it could break a build process which would not expect that strange script. Finally, my script, as I mentioned, allows me to vary the options depending on the situation.
I usually use alias in linux to set cm equal to cmake .. && make -j12. Or write a shell to specify make and clean progress ...
alias cm='cmake .. && make -j12'
Then use cm to make in a single command.

Adding home-brew to PATH

I just installed Home-brew and now I'm trying to insert the home-brew directory at the top of my path environment variable by typing in two commands inside my terminal. My questions are these:
What is a path environment variable?
Are the two codes provided me correct?
echo "export Path=/usr/local/bin:$PATH" >> ~/.bash_profile && source ~/.bash_profile
After this I am to type in brew doctor. Nothing is happening as far as I can see.
Can anyone offer me some advice or direction?
I installed brew in my new Mac M1 and ask me to put /opt/homebrew/bin in the path, so the right command for this case is:
echo "export PATH=/opt/homebrew/bin:$PATH" >> ~/.bash_profile && source ~/.bash_profile
TL;DR
echo "export PATH=/usr/local/bin:$PATH" >> ~/.bash_profile && source ~/.bash_profile
is what you want.
To answer your first question; in order to run (execute) a program (executable) the shell must know exactly where it is in your filesystem in order to run it. The PATH environment variable is a list of directories that the shell uses to search for executables. When you use a command that is not built into the shell you are using the shell will search through these directories in order and will execute the first matching executable it finds.
For example when you type: mv foo bar the shell is almost certainly actually using an executable located in the /bin directory. Thus fully the command is
/bin/mv foo bar
The PATH environment variable therefore saves you some extra typing. You can see what is in your PATH currently (as you can with all environment variables) by entering:
echo $<NAME OF VARIABLE>
So in this instance:
echo $PATH
As I mentioned earlier, ordering is important. Adding /usr/local/bin to the beginning of PATH means that the shell will search there first and so if you have an executable foo in that folder it will be used in preference to any other foo executables you may have in the folders in your path. This means that any executables you install with brew will be used in preference to the system defaults.
On to your second question. What the command you have provided is trying to do is add a line to your .bash_profile and then source it. The .bash_profile is a text file stored in your home directory that is sourced (read) every time bash (your shell) starts. The mistake in the line you've provided is that only the first letter of PATH is capitalised. To your shell Path and PATH are very different things.
To fix it you want:
echo "export PATH=/usr/local/bin:$PATH" >> ~/.bash_profile && source ~/.bash_profile
To explain
echo "export PATH=/usr/local/bin:$PATH"
simply prints or echoes what follows to stdout, which in the above instance is the terminal. (stdout, stderr and stdin are very important concepts on UNIX systems but rather off topic) Running this command produces the result:
export PATH=/usr/local/bin:/opt/local/sbin:/opt/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin
on my system because using $PATH within double quotes means bash will substitute it with its value. >> is then used to redirect stdout to the end of the ~/.bash_profile file. ~ is shorthand for your home directory. (NB be very careful as > will redirect to the file and overwrite it rather than appending.)
&& means run the next command is the previous is successful and
source ~/.bash_profile
simply carries out the actions contained in that file.
As per the latest documentation, you need to do this:
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/dhruv/.bashrc
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
Now you should be able to run brew from anywhere.
When you type in a program somewhere and click enter, it checks certain locations to see if that program exists there.
Linux brew uses locations different from the normal linux programs, so we are adding these locations to the ~/.profile file which sets the paths.
Run this in your terminal, and it will place the correct code in the .profile file, automatically.
echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.profile
Don't use .bash_profile because when you use something different from bash, like zsh, it may not work. .profile is the correct location.