How to call a function at start and end of building a target in cmake - cmake

I'm looking for a way to execute shell code when starting and finishing the build of a target in cmake. The final goal is to send a message to a data tracking tool indicating when builds start and finish.
So for example, if "make" build targets alpha, beta and gamma, I'd like to call foo_begin() when alpha starts building and foo_end when target alpha is successfully built, and so on for all the targets.
Is this possible?

It is not possible to call arbitrary CMake functions at build time. Once the build system is generated, all the normal variable state and function definitions are discarded (i.e. they are not in cache). The COMMAND argument to add_custom_command takes a shell command, not a CMake function.
However, you can use CMake's script mode, so if what you want is to use CMake as a scripting language just to implement some build hooks, you can do this:
Let's create a file called proc_target.cmake with these contents:
cmake_minimum_required(VERSION 3.23)
if (PRE_BUILD)
message(STATUS "PRE_BUILD: ${TARGET}")
else ()
message(STATUS "POST_BUILD: ${TARGET}")
endif ()
Now in the CMakeLists.txt we can write:
cmake_minimum_required(VERSION 3.23)
project(example)
add_executable(foo main.cpp)
add_custom_command(
TARGET foo PRE_BUILD
COMMAND "${CMAKE_COMMAND}"
-DTARGET=foo
-DPRE_BUILD=1
-P "${CMAKE_CURRENT_SOURCE_DIR}/proc_target.cmake"
)
add_custom_command(
TARGET foo POST_BUILD
COMMAND "${CMAKE_COMMAND}"
-DTARGET=foo
-DPRE_BUILD=0
-P "${CMAKE_CURRENT_SOURCE_DIR}/proc_target.cmake"
)
Then when you run this build, you'll see the following commands:
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release
...
$ cmake --build build -- -nv
[1/2] /usr/bin/c++ -O3 -DNDEBUG -MD -MT CMakeFiles/foo.dir/main.cpp.o -MF CMakeFiles/foo.dir/main.cpp.o.d -o CMakeFiles/foo.dir/main.cpp.o -c /path/to/main.cpp
[2/2] cd /path/to/build && /usr/bin/cmake -DTARGET=foo -DPRE_BUILD=1 -P /path/to/proc_target.cmake && cd /path/to/build && /usr/bin/c++ -O3 -DNDEBUG CMakeFiles/foo.dir/main.cpp.o -o foo && cd /path/to/build && /usr/bin/cmake -DTARGET=foo -DPRE_BUILD=0 -P /path/to/proc_target.cmake
You can see how proc_target.cmake is called twice: once just before and once just after invoking the linker for foo. If you want proc_target to run before any of the sources are compiled, then you would want to write:
add_custom_command(
OUTPUT pre-foo.stamp
COMMAND "${CMAKE_COMMAND}"
-DTARGET=foo
-DPRE_BUILD=1
-P "${CMAKE_CURRENT_SOURCE_DIR}/proc_target.cmake"
COMMAND "${CMAKE_COMMAND}" -E touch pre-foo.stamp
DEPENDS "$<TARGET_PROPERTY:foo,SOURCES>"
)
add_custom_target(pre-foo DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/pre-foo.stamp")
add_dependencies(foo pre-foo)
instead of the PRE_BUILD custom command above. Then you would get:
[1/3] cd /path/to/build && /usr/bin/cmake -DTARGET=foo -DPRE_BUILD=1 -P /path/to/proc_target.cmake
[2/3] /usr/bin/c++ -O3 -DNDEBUG -MD -MT CMakeFiles/foo.dir/main.cpp.o -MF CMakeFiles/foo.dir/main.cpp.o.d -o CMakeFiles/foo.dir/main.cpp.o -c /path/to/main.cpp
[3/3] : && /usr/bin/c++ -O3 -DNDEBUG CMakeFiles/foo.dir/main.cpp.o -o foo && cd /path/to/build && /usr/bin/cmake -DTARGET=foo -DPRE_BUILD=0 -P /path/to/proc_target.cmake
And now you can see that the custom target pre-foo is processed before foo.
This is most likely not what you want, however. It's over-engineered. If you want to generate a source file for a target, you should use add_custom_command(OUTPUT) directly and attach the output to the target as a source file (i.e. when you call add_executable or via target_sources).

Per #botje's comment, it seems I need cmake's add_custom_command with build event specifies. I will however need PRE_BUILD, which the documentation informs me is only available for vs-studio, while I am building use cmake & gcc. So I guess I have a new question: how to duplicate the behavior of PRE_BUILD in a cmake/gcc build environment.

Related

cmake incorrectly escapes bison target option

Take this minimized example
Critical place:
bison_target(parser
numgrammar.y
${CMAKE_CURRENT_BINARY_DIR}/parser.cc
COMPILE_FLAGS "--defines=${CMAKE_CURRENT_BINARY_DIR}/numgrammar.tab.hh")
Then please create some folder with space inside name like:
> mkdir "test folder" && cd "test folder"
> cmake ${ADVGRAMMAR}
> make VERBOSE=1
You will see something like:
> /usr/bin/bison --defines=/mnt/c/research/test folder/advgrammar/build/numgrammar.tab.hh -d -o ....
You see the problem: space in "test folder" not escaped.
Ok, now lets try to escape:
bison_target(parser
numgrammar.y
${CMAKE_CURRENT_BINARY_DIR}/parser.cc
COMPILE_FLAGS "--defines=\"${CMAKE_CURRENT_BINARY_DIR}/numgrammar.tab.hh\"")
You will see:
/usr/bin/bison --defines=\"/mnt/c/research/test folder/advgrammar/build/numgrammar.tab.hh\" -d -o ....
Now cmake added wrong escape mark for quotes.
Of course no problem if cmake in some neutral-named folder without spaces, but I want any folder.
I tried to google really hard. I tried string with CONFIGURE, I tried generator expressions, I tried this and that and everything and looks like I can't get behavior I want.
I want simply this:
/usr/bin/bison --defines="/mnt/c/research/test folder/advgrammar/build/numgrammar.tab.hh" -d -o ....
Of course I can do it with my own custom target. But I want this with bison_target.
Really need cmake experts help. Any ideas appreciated.
This is because the bison_target macro calls1 separate_arguments on the value of the COMPILE_FLAGS without using the new form that respects native shell rules (it just blindly replaces spaces with semicolons).
Unfortunately, the macro doesn't give you a chance to inject flags in a more modern way, either, so the best I could come up with was to use the variable_watch command to hack at the internals of bison_target, at least until this bug is fixed.
Here's a full example:
cmake_minimum_required(VERSION 3.22)
project(test)
find_package(FLEX REQUIRED)
find_package(BISON REQUIRED)
# Hack around FindBISON's incorrect use of separate_arguments
if (CMAKE_VERSION VERSION_LESS 3.24)
function(patch_flags variable access value ip stack)
set(invalid "${CMAKE_CURRENT_BINARY_DIR}")
separate_arguments(invalid)
string(REPLACE "${invalid}" "${CMAKE_CURRENT_BINARY_DIR}" "${variable}" "${value}")
set("${variable}" "${${variable}}" PARENT_SCOPE)
endfunction()
variable_watch(BISON_TARGET_cmdopt patch_flags)
endif ()
flex_target(scanner
numgrammar.l
"${CMAKE_CURRENT_BINARY_DIR}/lexer.cc"
)
bison_target(
parser
numgrammar.y
"${CMAKE_CURRENT_BINARY_DIR}/parser.cc"
COMPILE_FLAGS "--defines=${CMAKE_CURRENT_BINARY_DIR}/numgrammar.tab.hh"
)
add_flex_bison_dependency(scanner parser)
add_executable(
numgrammar
driver.cc
${BISON_parser_OUTPUTS}
${FLEX_scanner_OUTPUTS}
)
And here's a shell interaction:
$ cmake -G Ninja -S . -B "build with space"
$ cmake --build "build with space" -- -nv # n = dry-run, v = verbose
[1/6] cd "/home/alex/test/build with space" && /usr/bin/bison "--defines=/home/alex/test/build with space/numgrammar.tab.hh" -d -o "/home/alex/test/build with space/parser.cc" /home/alex/test/numgrammar.y
[2/6] cd "/home/alex/test/build with space" && /usr/bin/flex "-o/home/alex/test/build with space/lexer.cc" /home/alex/test/numgrammar.l
[3/6] /usr/bin/c++ -MD -MT CMakeFiles/numgrammar.dir/driver.cc.o -MF CMakeFiles/numgrammar.dir/driver.cc.o.d -o CMakeFiles/numgrammar.dir/driver.cc.o -c /home/alex/test/driver.cc
[4/6] /usr/bin/c++ -MD -MT CMakeFiles/numgrammar.dir/parser.cc.o -MF CMakeFiles/numgrammar.dir/parser.cc.o.d -o CMakeFiles/numgrammar.dir/parser.cc.o -c '/home/alex/test/build with space/parser.cc'
[5/6] /usr/bin/c++ -MD -MT CMakeFiles/numgrammar.dir/lexer.cc.o -MF CMakeFiles/numgrammar.dir/lexer.cc.o.d -o CMakeFiles/numgrammar.dir/lexer.cc.o -c '/home/alex/test/build with space/lexer.cc'
[6/6] : && /usr/bin/c++ CMakeFiles/numgrammar.dir/driver.cc.o CMakeFiles/numgrammar.dir/parser.cc.o CMakeFiles/numgrammar.dir/lexer.cc.o -o numgrammar && :
As you can see the bison rule correctly quotes the spaces.
... /usr/bin/bison "--defines=/home/alex/test/build with space/numgrammar.tab.hh" ...
1. See https://github.com/Kitware/CMake/blob/6b6bdcbb64e1aa2ddac4f09a0807553f5684165a/Modules/FindBISON.cmake#L131 and https://github.com/Kitware/CMake/blob/6b6bdcbb64e1aa2ddac4f09a0807553f5684165a/Modules/FindBISON.cmake#L249
Just in case: there is an option DEFINES_FILE in the bison_target macro. As for me it looks like more portable. You can use it instead of COMPILE_FLAGS.
bison_target(parser
numgrammar.y
${CMAKE_CURRENT_BINARY_DIR}/parser.cc
## COMPILE_FLAGS "--defines=${CMAKE_CURRENT_BINARY_DIR}/numgrammar.tab.hh")
DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/numgrammar.tab.hh
)
It works fine at least on Catalina.
$ cmake -G Ninja -S . -B "build with spaces"
$ cmake --build "build with spaces" -- -nv
[1/6] cd /Users/tonnyred/td && /usr/local/opt/bison/bin/bison "--defines=/Users/tonnyred/td/build with spaces/numgrammar.tab.hh" -o "/Users/tonnyred/td/build with spaces/parser.cc" numgrammar.y
[2/6] cd /Users/tonnyred/td && /usr/local/opt/flex/bin/flex "-o/Users/tonnyred/td/build with spaces/lexer.cc" numgrammar.l
...

fatal error: gtkmm.h: No such file or directory

i couldn't figureout what's wrong in this code
whatever edit i do, it gives same error every time
all : asio - 1.12.2 src / chat_server client
asio - 1.12.2 :
tar xzf asio - 1.12.2.tar.gz
client : src / client.cpp src / mainwin.cpp src/*.h
$(CXX) $(CXXFLAGS) src/client.cpp src/mainwin.cpp $(GTKFLAGS) -o src/client
GTKLINKFLAGS=$(shell pkg-config --libs gtkmm-3.0)
GTKCOMPILEFLAGS=$(shell pkg-config --cflags gtkmm-3.0)
CXXFLAGS+= -DASIO_STANDALONE -Wall -O0 -g -std=c++11
CPPFLAGS+=-I./include -I./asio-1.12.2/include -I./src ${GTKCOMPILEFLAGS}
LDLIBS+= -lpthread ${GTKLINKFLAGS}
clean:
-rm -rf asio-1.12.2
-rm -f src/client
-rm -f src/chat_server
I can reproduce your error. In:
client : src / client.cpp src / mainwin.cpp src/*.h
$(CXX) $(CXXFLAGS) src/client.cpp src/mainwin.cpp $(GTKFLAGS) -o src/client
You are using $(GTKFLAGS) which is empty. To notice it type: make -n. In your case replace it by $(GTKCOMPILEFLAGS) $(GTKLINKFLAGS) but this is not a nice fix because you should separate your Makefile rules:
one for compiling each cpp file into object file (.o)
and one rule for linking all objects files into your binary.

How to escape $ in cmake?

I am interested in having a line like:
add_link_options(-Wl,-rpath=$ORIGIN)
However, in the executable, the $ gets expanded to $$. E.g.
15:19:52 Debug # readelf -a mybinary | egrep -i runpath
0x000000000000001d (RUNPATH) Library runpath: [$$ORIGIN]
It is possible to get this effect with patchelf after build, but would like to have it in cmake itself if possible.
I have tried things like \$, $$ and "$ORIGIN" without any luck and can't think of anything except 50ยข.
This is with cmake 3.13.1.
Edit: Verbose cmake output:
/usr/bin/c++ -g "-Wl,-rpath=\$$ORIGIN" CMakeFiles/mybinary.dir/....
Edit2: This works:
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} "-Wl,-rpath=$ORIGIN")
...
/usr/bin/c++ -g -Wl,-rpath=$ORIGIN CMakeFiles/mybinary.dir/...

makefile with debug option

I am kinda rookie in makefile field but trying to write makefile that would go in two modes: normal mode make outputing executable file called say bingo depending on some files and a mode make debug outputing executable file called bingo.debug that shall be compiled with debug option. I'm trying to use target variable with the following result:
PROGRAM = bingo
SUFIX = .debug
CC = gcc
CFLAGS = -Wall -O2
DEBUG = -g -D DEBUG
all: $(PROGRAM)
debug: CFLAGS += $(DEBUG)
debug: PROGRAM += $(SUFIX)
debug: all
file1.o: file1.c file1.h
$(CC) -c $(CFLAGS) -o $# $<
file2.o: file2.c file2.h
$(CC) -c $(CFLAGS) -o $# $<
$(PROGRAM).o: $(PROGRAM).c
$(CC) -c $(CFLAGS) -o $# $<
$(PROGRAM): file1.o file2.o ($PROGRAM).o
$(CC) -o $# $^
.PHONY: all clean
clean:
rm -rf $(PROGRAM) *.o
It looks like make debug correctly compiles the file with debug flags but it does not change the file name (i.e. both modes outputs the same bingo file). Any help much appriciated!
You cannot use target-specific variables in targets. The documentation is very clear that they are available only in recipes.
In general it's problematic to do things this way, because make has no idea which objects were built with debug and which weren't. If you forget to do a complete clean and/or run make the wrong way then you'll get a mix of different object files: some compiled with debug and some not.
Instead, you should put your debug object files in a different directory from your non-debug object files so you don't have to worry about that.

Make a DLL file from a Fortran source using a Makefile

How would I make a DLL file from a Makefile where the files are written in Fortran?
I'm working with Windows but having it compiled under a Linux system isn't a problem.
I have two modules and one file that uses those modules.
Normally, I would just write the following to to produce what I want:
gfortran module1.f90 module2.f90 main.f90 -o main.dll
If module1.f90 and module2.f90 are just modules and you want to create a dll, you may use the following command:
gfortran -o main.dll module1.f90 module2.f90 -shared -fPIC -lgfortran
It will generate a dll that can be loaded later by the main program.
The main flags here for generate a lib are -shared and -fPIC.
If main.f90 is the main program, the output should be an exe, and the command to compile maybe:
gfortran -o main.exe module1.f90 module2.f90 main.f90
It will generate an exe with the main file using the modules.
Edit:
Here, is a sample Makefile that builds the DLL (for the question):
FC=gfortran
FFLAGS=-g -shared -fPIC
LDFLAGS=-lgfortran
main.dll: main.f90 module1.o module2.o
$(FC) $(LDFLAGS) -o $# $?
module1.o: module1.f90
$(FC) $(FFLAGS) -o $# $?
module2.o: module2.f90
$(FC) $(FFLAGS) -o $# $?
clean:
rm -f *.o *.exe *.dll
After executing it:
gfortran -g -shared -fPIC -o module1.o module1.f90
gfortran -g -shared -fPIC -o module2.o module2.f90
gfortran -o main.dll main.f90 module1.o module2.o
It generates the DLL:
20/12/2016 16:23 59.091 main.dll
Maybe, this Makefile can be improved with use of macros.
HTH
tested with gfortran version 6.2.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)