Cython, CMake and out-of-source build results in double compilation - cmake

I have a big project containing multiple projects that create library/executables and some even a python module using a library.
To keep it simple, I have an MWE included called myprogram that creates an executable and a shared library for use by Cython code to create a python package for that same code. I use CMake to compile the code but due to my setup, as detailed here, the build process of the python routine is executed twice (during build and install).
During build the output is:
Scanning dependencies of target pymyprogram
[ 80%] Generating build/timestamp
Compiling /builds/myprogram/src/myprogram/pymyprogram.pyx because it changed.
[1/1] Cythonizing /builds/myprogram/src/myprogram/pymyprogram.pyx
running build_ext
building 'pymyprogram' extension
creating build
creating build/temp.linux-x86_64-3.8
creating build/temp.linux-x86_64-3.8/builds
creating build/temp.linux-x86_64-3.8/builds/myprogram
creating build/temp.linux-x86_64-3.8/builds/myprogram/src
creating build/temp.linux-x86_64-3.8/builds/myprogram/src/myprogram
gcc -Wno-unused-result -Wsign-compare -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -I/opt/rh/rh-python38/root/usr/include -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -I/opt/rh/rh-python38/root/usr/include -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -I/builds/myprogram/src/myprogram -I/builds/myprogram/include -I/builds/myprogram/buildRelease -I/opt/rh/rh-python38/root/usr/include/python3.8 -c /builds/myprogram/src/myprogram/pymyprogram.c -o build/temp.linux-x86_64-3.8/builds/myprogram/src/myprogram/pymyprogram.o
creating build/lib.linux-x86_64-3.8
gcc -pthread -shared -L/opt/rh/rh-python38/root/usr/lib64-Wl,-z,relro -Wl,-rpath,/opt/rh/rh-python38/root/usr/lib64 -Wl,--enable-new-dtags -g -L/opt/rh/rh-python38/root/usr/lib64-Wl,-z,relro -Wl,-rpath,/opt/rh/rh-python38/root/usr/lib64 -Wl,--enable-new-dtags -g build/temp.linux-x86_64-3.8/builds/myprogram/src/myprogram/pymyprogram.o -L/builds/myprogram/buildRelease/src/myprogram -L/opt/rh/rh-python38/root/usr/lib64 -lmyprogram -o build/lib.linux-x86_64-3.8/pymyprogram.cpython-38-x86_64-linux-gnu.so -Wl,-rpath=/builds/myprogram/lib/
[ 80%] Built target pymyprogram
And during install:
-- Installing: /builds/myprogram/bin/myprogram.x
running install
running build
running build_ext
building 'pymyprogram' extension
creating build
creating build/temp.linux-x86_64-3.8
creating build/temp.linux-x86_64-3.8/builds
creating build/temp.linux-x86_64-3.8/builds/myprogram
creating build/temp.linux-x86_64-3.8/builds/myprogram/src
creating build/temp.linux-x86_64-3.8/builds/myprogram/src/myprogram
gcc -Wno-unused-result -Wsign-compare -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -I/opt/rh/rh-python38/root/usr/include -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -I/opt/rh/rh-python38/root/usr/include -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -I/builds/myprogram/src/myprogram -I/builds/myprogram/include -I/builds/myprogram/buildRelease -I/opt/rh/rh-python38/root/usr/include/python3.8 -c /builds/myprogram/src/myprogram/pymyprogram.c -o build/temp.linux-x86_64-3.8/builds/myprogram/src/myprogram/pymyprogram.o
creating build/lib.linux-x86_64-3.8
gcc -pthread -shared -L/opt/rh/rh-python38/root/usr/lib64-Wl,-z,relro -Wl,-rpath,/opt/rh/rh-python38/root/usr/lib64 -Wl,--enable-new-dtags -g -L/opt/rh/rh-python38/root/usr/lib64-Wl,-z,relro -Wl,-rpath,/opt/rh/rh-python38/root/usr/lib64 -Wl,--enable-new-dtags -g build/temp.linux-x86_64-3.8/builds/myprogram/src/myprogram/pymyprogram.o -L/builds/myprogram/buildRelease/src/myprogram -L/opt/rh/rh-python38/root/usr/lib64 -lmyprogram -o build/lib.linux-x86_64-3.8/pymyprogram.cpython-38-x86_64-linux-gnu.so -Wl,-rpath=/builds/myprogram/lib/
running install_lib
creating /builds/myprogram/lib64
creating /builds/myprogram/lib64/python3.8
creating /builds/myprogram/lib64/python3.8/site-packages
copying build/lib.linux-x86_64-3.8/pymyprogram.cpython-38-x86_64-linux-gnu.so -> /builds/program/lib/python3.8/site-packages
running install_egg_info
Writing /builds/myprogram/lib/python3.8/site-packages/pymyprogram-1.1A-py3.8.egg-info
Also when I make changes to otherprogram.c but no changes to myprogram in either the library of pyx code, at install stage the library is build anyway which increases compilation time.
I therefore searched for a solution and found the method from PJ_Finnegan but have a few issues so wondered whether others have resolved that or could help out.
My working tree for the MWE is:
.
├── README.TXT
├── README.md
├── bin
├── CMakeLists.txt
├── buildDebug
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── Makefile
│   ├── cmake_install.cmake
│   ├── install_manifest.txt
│   ├── src
│   └── utils
├── include
│   ├── version.h
│   └── version.h.txt
├── lib
│   └── libmyprogram.dylib
├─── src
│   ├── some other myprogram
│   │   ├── CMakeLists.txt
│   │   ├── othermyprogram.h
│   │   ├── othermyprogram.c
│   ├── myprogram
│   │   ├── CMakeLists.txt
│   │   ├── myprogram.h
│   │   ├── myprogram.c
│   │   ├── myprogram.pyx
│   │   ├── setup.py.in
│   └── versioning.cmake
The top level CMakeLists.txt contains add_subdirectory(src/myprogram) and the content of the CMakeLists.txt in src/myprograms is:
cmake_minimum_required(VERSION 2.8.8...3.20.5 FATAL_ERROR)
project (myprogram)
message ("-- Configuring: *** myprogram **")
if(APPLE)
set(CMAKE_MACOSX_RPATH 1)
endif()
set(CMAKE_C_FLAGS "${CFLAGS} -O0 -ggdb -fPIC")
set(CMAKE_C_FLAGS_DEBUG "${CFLAGS} -O0 -ggdb -fPIC")
set(CMAKE_C_FLAGS_RELEASE "${CFLAGS} -O3 -fPIC")
set(CMAKE_CXX_FLAGS "${CFLAGS} -O0 -ggdb")
set(CMAKE_CXX_FLAGS_DEBUG "${CFLAGS} -O0 -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "${CFLAGS} -O3")
# *** myprogram.so ***
SET(libmyprogram_SRCS
myprogram.c
)
ADD_LIBRARY(myprogram SHARED
${libmyprogram_SRCS}
)
ADD_DEPENDENCIES(myprogram versioning)
TARGET_LINK_LIBRARIES(myprogram
m
)
install(TARGETS myprogram
DESTINATION "lib")
if (APPLE)
install(CODE "execute_process(COMMAND ln -sf ${PROJECT_HOME}/lib/libmyprogram.dylib /usr/local/lib/libmyprogram.dylib)")
endif()
# *** myprogram.x ***
SET(myprogram_SRCS
myprogram.c
)
ADD_EXECUTABLE(myprogram.x
${myprogram_SRCS}
)
ADD_DEPENDENCIES(myprogram.x myprogram versioning)
TARGET_LINK_LIBRARIES(myprogram.x
m
)
set (EXECUTABLES "${EXECUTABLES}" myprogram.x)
# install executables and scripts
install (TARGETS ${EXECUTABLES}
RUNTIME DESTINATION "bin")
# *** pymyprogram.pyx ***
find_package(PythonInterp 3 REQUIRED)
if (CMAKE_BUILD_TYPE MATCHES "Debug")
message(STATUS "Python: version=${PYTHON_VERSION_STRING} interpreter=${PYTHON_EXECUTABLE}")
set(PYTHON_BUILD_FLAGS "--debug")
endif()
# Get location of user site-packages to install libs to
execute_process(COMMAND python3 -m site --user-site OUTPUT_VARIABLE PYTHON_INSTALL_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
if (PYTHONINTERP_FOUND)
set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py")
set(DEPS "${CMAKE_CURRENT_SOURCE_DIR}/pymyprogram.pyx")
set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build/timestamp")
message ("-- Building Python Extension using: " ${PYTHON_EXECUTABLE})
configure_file(${SETUP_PY_IN} ${SETUP_PY})
add_custom_command(OUTPUT ${OUTPUT}
COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} build ${PYTHON_BUILD_FLAGS} --build-lib ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}
COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT}
DEPENDS ${DEPS})
add_custom_target(pymyprogram ALL DEPENDS ${OUTPUT})
add_dependencies(pymyprogram myprogram versioning)
install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} install_lib --skip-build
--build-dir ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}
--install-dir=${PYTHON_INSTALL_DIR} --force
COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} install_egg_info
--install-dir=${PYTHON_INSTALL_DIR})" DEPENDS ${DEPS})
else()
message (WARNING "-- Could not build *** pymyprogram **, could not locate PYTHON: ${PYTHON_EXECUTABLE} or improper version")
endif()
And the setup.py.in that is used:
import os, sys
from distutils.core import setup, Extension
from Cython.Build import cythonize
link_arguments = []
if (sys.platform == 'darwin'):
link_arguments.append("-Wl,-rpath")
link_arguments.append("-Wl,#loader_path/")
os.environ["CC"] = "gcc"
else:
link_arguments.append("-Wl,-rpath=${CMAKE_SOURCE_DIR}/lib/")
myprogram_extension = Extension(
name="pymyprogram",
sources=["${CMAKE_CURRENT_SOURCE_DIR}/pymyprogram.pyx"],
libraries=["myprogram"],
extra_link_args = link_arguments,
library_dirs=["${PROJECT_BINARY_DIR}"],
include_dirs=["${CMAKE_SOURCE_DIR}/include", "${CMAKE_SOURCE_DIR}/build${CMAKE_BUILD_TYPE}"],
)
setup(name="pymyprogram",
author="My Name",
author_email="my-email",
version="1.1A",
description="Python wrapper for myprogram",
package_dir={ "": "${CMAKE_SOURCE_DIR}/lib/" },
license="MIT License",
ext_modules=cythonize([myprogram_extension], compiler_directives={'language_level' : "3"})
)
The code runs on different machines (Linux or macOS) and my module was previously build and installed using this method and either the --user or --prefix of the install command based on whether the code ran on Linux or macOS.
To avoid the double build step of the python module, I implemented a new method from PJ_Finnegan as mentioned above and shown in the code sample of my CMakeLists.txt. This method uses install_lib, to avoid the second build which allows the use of --build-dir which is great but no longer works with --user or --prefix which is unwanted as it will require me to define the path manually and that the path no longer depends on the python version automatically.
I also wonder what the difference between using install-lib vs install entails. I noticed no egg-info file being created, is this a problem? I therefore added the install_egg_info step as well.
Also, the the mypogram code is build twice, (the executable and library), and a third time for the python package. Is there some optimisation possible to reduce compile time?
Any other ways to build and install a python/Cython extension and separate standalone executable and help the install step find the build code? Perhaps using sdist and pip install?
EDIT
I've changed the INSTALL command in my CMAKE files to:
install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} install ${PYTHON_INSTALL_PREFIX} --skip-build --force WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})")
This reduces the install step to only installation and no more build as the build directory from the previous build step is now found.
Now the question remains, how to make the CMAKE install command not run every time and only upon changes in the *.pyx file. I know add_custom_target is always considered out-of-date so is there perhaps a workaround?

Related

Make error with CppUTest - libCppUTest.a error adding symbols

I am trying to create a project folder structure based on CMake, using the gcc-arm-none-eabi toolchain, and that can use QEMU (arm) to run CppUTest.
Right now, my folder structure look like this:
project/
├── app/
│ ├── src/
│ │ └── main.cpp
│ ├── tests/
│ │ ├── CMakeLists.txt
│ │ ├── FooTest.cpp
│ │ └── main.cpp
│ ├── CMakeLists.txt
│ └── runCMake.sh
├── libraries/
│ ├── errortype/
│ │ ├── include/
│ │ ├── src/
│ │ └── CMakeLists.txt
│ ├── math/
│ │ ├── include/
│ │ ├── src/
│ │ └── CMakeLists.txt
│ └── prediction/
│ ├── include/
│ ├── src/
│ └── CMakeLists.txt
└── scripts/
└── cmake/
├── PreTargetDef.cmake
├── PostTargetDef.cmake
└── toolchain.cmake
The app/CMakeLists.txt:
### PROJECT INITIALIZATION
cmake_minimum_required(VERSION 3.19)
project(hello-arm)
### SET TOOLCHAIN
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/../scripts/cmake/toolchain.cmake)
### CUSTOM CMAKE SCRIPTS
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../scripts/cmake")
include(PreTargetDef)
### APPLICATION SOURCES
file(GLOB ${PROJECT_NAME}_SRC
"src/main.cpp"
)
### CREATE EXECUTABLE
add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_SRC})
### LIBRARIES TARGET INCLUDE DIRECTORIES
target_include_directories(${PROJECT_NAME} PUBLIC ${LIBERRORTYPE_INCLUDE_DIRS})
### CUSTOM CMAKE SCRIPT
include(PostTargetDef)
### OPTIONALLY COMPILE TESTS
option(COMPILE_TESTS "Compile the tests" OFF)
if(COMPILE_TESTS)
add_subdirectory(tests)
endif(COMPILE_TESTS)
Toolchain.cmake:
cmake_minimum_required (VERSION 3.6)
#set(CMAKE_CROSSCOMPILING 1)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(TRIPLE "arm-none-eabi")
unset(TOOLCHAIN_GCC_PROGRAM CACHE)
find_program(TOOLCHAIN_GCC_PROGRAM "${TRIPLE}-gcc" NO_DEFAULT_PATH HINTS ${ARM_CORTEX_COMPILER_PATH} $ENV{ARM_CORTEX_COMPILER_PATH})
get_filename_component(TOOLCHAIN_ROOT ${TOOLCHAIN_GCC_PROGRAM} DIRECTORY)
get_filename_component(EXE_EXT ${TOOLCHAIN_GCC_PROGRAM} EXT)
# To create static libraries suitable for link-time optimization (LTO),
# use gcc-ar and gcc-ranlib instead of ar and ranlib. See gcc -flto option
set(CMAKE_C_COMPILER "${TOOLCHAIN_ROOT}/${TRIPLE}-gcc${EXE_EXT}" CACHE FILEPATH "gcc" FORCE)
set(CMAKE_CXX_COMPILER "${TOOLCHAIN_ROOT}/${TRIPLE}-g++${EXE_EXT}" CACHE FILEPATH "g++" FORCE)
set(CMAKE_OBJCOPY "${TOOLCHAIN_ROOT}/${TRIPLE}-objcopy${EXE_EXT}" CACHE FILEPATH "objcopy" FORCE)
set(CMAKE_OBJDUMP "${TOOLCHAIN_ROOT}/${TRIPLE}-objdump${EXE_EXT}" CACHE FILEPATH "objdump" FORCE)
set(CMAKE_RANLIB "${TOOLCHAIN_ROOT}/${TRIPLE}-gcc-ranlib${EXE_EXT}" CACHE FILEPATH "ranlib" FORCE)
set(CMAKE_STRIP "${TOOLCHAIN_ROOT}/${TRIPLE}-strip${EXE_EXT}" CACHE FILEPATH "strip" FORCE)
set(CMAKE_AR "${TOOLCHAIN_ROOT}/${TRIPLE}-gcc-ar${EXE_EXT}" CACHE FILEPATH "ar" FORCE)
set(CMAKE_AS "${TOOLCHAIN_ROOT}/${TRIPLE}-as${EXE_EXT}" CACHE FILEPATH "as" FORCE)
#SET(CMAKE_EXE_LINKER_FLAGS_INIT "--specs=nosys.specs -Wl,-gc-sections -nostdlib -static-libgcc -static-libstdc++ -nostartfiles")
set(CMAKE_EXE_LINKER_FLAGS_INIT " --specs=nosys.specs ")
string(APPEND CMAKE_C_FLAGS_INIT " ")
string(APPEND CMAKE_C_FLAGS_INIT_DEBUG " ")
string(APPEND CMAKE_CXX_FLAGS_INIT " ")
string(APPEND CMAKE_CXX_FLAGS_INIT_DEBUG " ")
set(SHARED_LIBS OFF)
set(STATIC_LIBS ON)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH )
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH )
PreTargetDef.cmake:
### ENABLE LANGUAGES
enable_language(C)
enable_language(CXX)
enable_language(ASM)
### COMPILE TARGET TYPE
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
### CREATE BUILD DIRECTORY IF NOT ALREADY DONE
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../build)
### OPTIONS
set(APP_ROOT ${CMAKE_SOURCE_DIR}/src)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 99)
set(CMAKE_EXE_LINKER_FLAGS --specs=rdimon.specs)
### COMPILE OPTIONS
list(APPEND COMPILE_OPTIONS
-mthumb
-mcpu=cortex-a9
-mfloat-abi=softfp
-mfpu=fpv4-sp-d16
-flto
-lm
-lc
-lrdimon
-Wall
$<$<CONFIG:RELEASE>:-g3>
$<$<CONFIG:RELEASE>:-Os>
)
add_compile_options(
${COMPILE_OPTIONS}
)
### ADD SUBDIRECTORIES FOR LIBRARIES
get_filename_component(COMMON_LIBRARIES_ROOT "${CMAKE_SOURCE_DIR}/../libraries/" ABSOLUTE)
get_filename_component(COMMON_LIBRARIES_BUILD_ROOT "${CMAKE_BINARY_DIR}/common_libraries/" ABSOLUTE)
add_subdirectory(${COMMON_LIBRARIES_ROOT}/errortype/src ${COMMON_LIBRARIES_BUILD_ROOT}/errortype)
add_subdirectory(${COMMON_LIBRARIES_ROOT}/math/src ${COMMON_LIBRARIES_BUILD_ROOT}/math)
add_subdirectory(${COMMON_LIBRARIES_ROOT}/prediction/src ${COMMON_LIBRARIES_BUILD_ROOT}/prediction)
set( LIBS
prediction
errortype
)
PostTargetDef.cmake:
### TARGET LINK LIBRARY
target_link_libraries(
${PROJECT_NAME}
${COMPILE_OPTIONS}
-Wl,-static
-Wl,--start-group
${LIBS}
-Wl,--end-group
)
And, tests/ CMakeLists.txt:
# (1) Look for installed version of CppUTest
if(DEFINED ENV{CPPUTEST_HOME})
message(STATUS "Using CppUTest home: $ENV{CPPUTEST_HOME}")
set(CPPUTEST_INCLUDE_DIRS $ENV{CPPUTEST_HOME}/include)
set(CPPUTEST_LIBRARIES $ENV{CPPUTEST_HOME}/lib)
set(CPPUTEST_LDFLAGS CppUTest CppUTestExt)
else()
find_package(PkgConfig REQUIRED)
pkg_search_module(CPPUTEST REQUIRED cpputest>=3.8)
message(STATUS "Found CppUTest version ${CPPUTEST_VERSION}")
endif()
# (2) Our unit tests sources
set(TEST_APP_NAME ${PROJECT_NAME}_tests)
set(TEST_SOURCES
mocks/IFooMock.cpp
FooTest.cpp
main.cpp
)
# (3) Take care of include directories
include_directories(${CPPUTEST_INCLUDE_DIRS} ../src/)
link_directories(${CPPUTEST_LIBRARIES})
# (4) Build the unit tests objects and link then with the app library
add_executable(${TEST_APP_NAME} ${TEST_SOURCES})
target_link_libraries(${TEST_APP_NAME}
${CPPUTEST_LDFLAGS}
${COMPILE_OPTIONS}
-Wl,-static
-Wl,--start-group
${LIBS}
-Wl,--end-group
)
Doing cmake -DARM_CORTEX_COMPILER_PATH="/path/to/gcc-arm-bin/" -DCOMPILE_TESTS=ON $PROJECT_PATH creates a Makefile in the build folder for the main application and a second one under the build/tests folder.
Running make produces a hello-arm file for the main app that runs under qemu-arm hello-arm just fine. But come time to make the tests/ part, I get the following error (make VERBOSE=1):
[ 91%] Building CXX object tests/CMakeFiles/hello-arm_tests.dir/main.cpp.obj
cd /home/tester/Documents/hello-qemu-arm/build/release/tests && /home/tester/miniconda3/envs/hello-qemu/bin/arm-none-eabi-g++ -I/home/tester/miniconda3/envs/hello-qemu/include -I/home/tester/Documents/hello-qemu-arm/application/tests/../src -I/home/tester/Documents/hello-qemu-arm/libraries/prediction/src/include -I/home/tester/Documents/hello-qemu-arm/build/release/common_libraries/prediction/src-config/include -I/home/tester/Documents/hello-qemu-arm/libraries/types/src/include -I/home/tester/Documents/hello-qemu-arm/build/release/common_libraries/types/src-config/include -I/home/tester/Documents/hello-qemu-arm/libraries/math/src/include -I/home/tester/Documents/hello-qemu-arm/build/release/common_libraries/math/src-config/include -I/home/tester/Documents/hello-qemu-arm/libraries/errortype/src/include -I/home/tester/Documents/hello-qemu-arm/build/release/common_libraries/errortype/src-config/include -I/home/tester/Documents/hello-qemu-arm/build/include -I/home/tester/Documents/hello-qemu-arm/libraries/etl/src/include -I/home/tester/Documents/hello-qemu-arm/libraries/etl/src/../config -O3 -DNDEBUG -mthumb -mcpu=cortex-a9 -mfloat-abi=softfp -mfpu=fpv4-sp-d16 -flto -lm -lc -lrdimon -Wall -g3 -Os -std=gnu++17 -MD -MT tests/CMakeFiles/hello-arm_tests.dir/main.cpp.obj -MF CMakeFiles/hello-arm_tests.dir/main.cpp.obj.d -o CMakeFiles/hello-arm_tests.dir/main.cpp.obj -c /home/tester/Documents/hello-qemu-arm/application/tests/main.cpp
[100%] Linking CXX executable hello-arm_tests
cd /home/tester/Documents/hello-qemu-arm/build/release/tests && /home/tester/miniconda3/envs/hello-qemu/bin/cmake -E cmake_link_script CMakeFiles/hello-arm_tests.dir/link.txt --verbose=1
/home/tester/miniconda3/envs/hello-qemu/bin/arm-none-eabi-g++ -O3 -DNDEBUG --specs=rdimon.specs "CMakeFiles/hello-arm_tests.dir/mocks/IFooMock.cpp.obj" "CMakeFiles/hello-arm_tests.dir/FooTest.cpp.obj" "CMakeFiles/hello-arm_tests.dir/main.cpp.obj" -o hello-arm_tests -L/home/tester/miniconda3/envs/hello-qemu/lib -lCppUTest -lCppUTestExt -mthumb -mcpu=cortex-a9 -mfloat-abi=softfp -mfpu=fpv4-sp-d16 -flto -lm -lc -lrdimon -Wall -g3 -Os -Wl,-static -Wl,--start-group ../common_libraries/prediction/libprediction.a ../common_libraries/errortype/liberrortype.a -Wl,--end-group
/home/tester/miniconda3/envs/hello-qemu/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: /home/tester/miniconda3/envs/hello-qemu/lib/libCppUTest.a: error adding symbols: file format not recognized
collect2: error: ld returned 1 exit status
make[2]: *** [tests/CMakeFiles/hello-arm_tests.dir/build.make:131: tests/hello-arm_tests] Error 1
make[2]: Leaving directory '/home/tester/Documents/hello-qemu-arm/build/release'
make[1]: *** [CMakeFiles/Makefile2:296: tests/CMakeFiles/hello-arm_tests.dir/all] Error 2
make[1]: Leaving directory '/home/tester/Documents/hello-qemu-arm/build/release'
make: *** [Makefile:91: all] Error 2

add extern cmake directory without building it

I have a cmake project somewhere that I want to use in several other projects. Let's call it projA located at path /projA. I have built it in /projA/build. In this build folder there is some library /projA/build/lib.a.
Now if I want to create a new project B using project A in the folder /projB I know two options for the CMakeLists:
Solution A
cmake_minimum_required(VERSION 3.0)
project(projB)
add_executable(${PROJECT_NAME} projB.cpp)
add_subdirectory(/projA /projA/build)
target_link_libraries(${PROJECT_NAME} projA)
The problem is that this solution will create new make files in /projA/build and the project A will be built again. Furthermore each time I will switch to a new project using projA, projA will be built again. So that's not a good solution. I would like to not overwrite all the build folder each time I switch between two project using projA.
Solution B
cmake_minimum_required(VERSION 3.0)
project(projB)
add_executable(${PROJECT_NAME} projB.cpp)
link_libraries(${PROJECT_NAME} /projA/build/lib.a)
target_include_directories(${PROJECT_NAME} PRIVATE /projA/include)
# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE /projA/deps/depA/include)
target_include_directories(${PROJECT_NAME} PRIVATE /projA/deps/depB/include)
...
This solution works, but it's not very beautiful. I have to add a line for each include directory of all dependencies of project A.
So my question is: Is there a way to do it properly?
If you want to build them entirely separately, you will need to go through the find_package infrastructure. Here's a complete example:
Project A
lib.h
#ifndef LIB_H
#define LIB_H
int lib();
#endif
lib.c
#include "lib.h"
int lib() { return 42; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(projA)
add_library(lib lib.c)
target_include_directories(lib PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>")
# Export the target named lib for use in other projects
# from the build tree of this project.
export(TARGETS lib FILE projA-config.cmake)
Project B
main.c
#include <stdio.h>
#include <lib.h>
int main () {
printf("%d\n", lib());
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(projB)
# Import the targets defined by projA
find_package(projA REQUIRED)
add_executable(projB main.c)
target_link_libraries(projB PRIVATE lib)
Compilation steps:
I'm imagining the two directories are next to each other, like so:
$ tree
.
├── projA
│   ├── CMakeLists.txt
│   ├── lib.c
│   └── lib.h
└── projB
├── CMakeLists.txt
└── main.c
Now we can build project A:
$ cmake -S projA -B projA/build -DCMAKE_BUILD_TYPE=Release
...
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/projA/build
$ cmake --build projA/build/ -- -v
[1/2] /usr/bin/cc -I/path/to/projA -O3 -DNDEBUG -MD -MT CMakeFiles/lib.dir/lib.c.o -MF CMakeFiles/lib.dir/lib.c.o.d -o CMakeFiles/lib.dir/lib.c.o -c /path/to/projA/lib.c
[2/2] : && /usr/bin/cmake -E rm -f liblib.a && /usr/bin/ar qc liblib.a CMakeFiles/lib.dir/lib.c.o && /usr/bin/ranlib liblib.a && :
And now we'll build project B, and set the projA_ROOT variable to /path/to/projA/build so that find_package(projA) will succeed.
$ cmake -S projB -B projB/build -DCMAKE_BUILD_TYPE=Release -DprojA_ROOT=$PWD/projA/build
...
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/projB/build
$ cmake --build projB/build
$ cmake --build projB/build/ -- -v
[1/2] /usr/bin/cc -isystem /path/to/projA -O3 -DNDEBUG -MD -MT CMakeFiles/projB.dir/main.c.o -MF CMakeFiles/projB.dir/main.c.o.d -o CMakeFiles/projB.dir/main.c.o -c /path/to/projB/main.c
[2/2] : && /usr/bin/cc -O3 -DNDEBUG CMakeFiles/projB.dir/main.c.o -o projB /path/to/projA/build/liblib.a && :
Clearly, this is non-obvious, and it's also sort of minimal. To build your own packages correctly, you should carefully read these CMake documentation pages:
https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html
https://cmake.org/cmake/help/latest/command/install.html
https://cmake.org/cmake/help/latest/command/export.html
I would also suggest watching Craig Scott's talk "Deep CMake for Library Authors", here: https://youtu.be/m0DwB4OvDXk

CMake: How to handle multiple versions of same libraries?

in my project I am using the header only library rapidjson v1.1.0.
└── my_project
├── CMakeLists.txt
├── src
│
├── 3rdParty/tiny_dnn (header only)
│ ├── CMakeLists.txt
│ ├── src
│ └── rapidjson_v0.2
│
└── rapidjson_v1.1.0
The problem is now that tiny-dnn has also included rapidjson (but an older version), so while i try to include tiny_dnn in the main CMakeLists.txt like include_directories(${PROJECT_SOURCE_DIR}/3rdParty/tiny_dnn) some conflicts arise from either tiny-dnn searches mine rapidjson or my project searches in tiny-dnn's rapidjson.
my_project CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(my_project)
# check the build type and set compiler and linker flags
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE)
IF(CMAKE_BUILD_TYPE MATCHES DEBUG)
message("Debug build")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas -g -O0 -std=c++17 -Ddeveloper_build")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIC -g -O0")
ELSEIF(CMAKE_BUILD_TYPE MATCHES RELEASE)
message("Release build")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas -O3 -std=c++17")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O3")
ELSE()
message(FATAL_ERROR "No build type specified")
ENDIF()
find_package(Boost COMPONENTS system filesystem REQUIRED)
include_directories(${Boost_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}/3rdParty/tiny-dnn)
file(GLOB_RECURSE SOURCE_FILES src/*.cpp src/*.c)
set(SOURCE_FILES
${SOURCE_FILES})
add_executable(my_project ${SOURCE_FILES})
target_link_libraries(my_project ${Boost_LIBRARIES} pthread)
set_target_properties(my_project PROPERTIES SUFFIX ${CMAKE_BUILD_TYPE})
my_project.cpp
#include <tiny_dnn/tiny_dnn.h>
#include <rapidjson/rapidjson.h> // <- usr/local/include/rapidjson
int main(int argc, char **argv)
{
rapidjson::Document d; // <- uses rapidjson (v0.2) of tiny_dnn/cereal/external/rapidjson but in my project i would use /usr/local/include/rapidjson (v1.0.1)
return 0;
}
In general - don't. Force one client to use higher / better. Even if you walk around those problems, you are likely to violate one definition rule and get segfault later.

CMake imported target found when configuring but generated build.make says target-NOTFOUND

I have a simple shared library libfool2.so with installed header fool2.h which are not from a CMake project. My project my_temp1 depends on fool2 so I write a FindFool2.cmake to make an imported target:
find_path(Fool2_INCLUDE_DIR fool2.h PATH_SUFFIXES fool2)
find_library(Fool2_LIB fool2)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Fool2
REQUIRED_VARS Fool2_INCLUDE_DIR Fool2_LIB
)
if(Fool2_FOUND AND NOT TARGET Fool2::Fool2)
add_library(Fool2::Fool2 SHARED IMPORTED)
set_target_properties(Fool2::Fool2 PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${Fool2_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${Fool2_LIB}"
)
endif()
The CMakeLists.txt for my_temp1 project is:
cmake_minimum_required(VERSION 3.3)
project(my_temp1)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/cmake_modules)
# FindFool2.cmake is in ${CMAKE_CURRENT_LIST_DIR}/cmake/cmake_modules
find_package(Fool2 REQUIRED)
if (TARGET Fool2::Fool2)
message(STATUS "target found")
endif()
add_executable(my_temp1 main.cpp)
target_link_libraries(my_temp1 Fool2::Fool2)
Now
$ tree ../__install
../__install/
├── include
│   └── fool2
│   ├── fool2.h
│   └── version.h
└── lib
└── libfool2.so
$ tree .
.
├── cmake
│   └── cmake_modules
│   └── FindFool2.cmake
├── CMakeLists.txt
└── main.cpp
$ cmake -H. -B_builds -DCMAKE_INSTALL_PREFIX=../__install
# some output omitted
-- target found
-- Configuring done
-- Generating done
-- Build files have been written to: /OMITTED/my_temp1/_builds
$ cmake --build _builds
CMakeFiles/my_temp1.dir/build.make:82: *** target pattern contains no '%'. Stop.
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/my_temp1.dir/all' failed
make[1]: *** [CMakeFiles/my_temp1.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
$ head -n 85 _builds/CMakeFiles/my_temp1.dir/build.make | tail -n 10
# External object files for target my_temp1
my_temp1_EXTERNAL_OBJECTS =
my_temp1: CMakeFiles/my_temp1.dir/main.cpp.o
my_temp1: CMakeFiles/my_temp1.dir/build.make
my_temp1: Fool2::Fool2-NOTFOUND
my_temp1: CMakeFiles/my_temp1.dir/link.txt
#$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/OMITTED/my_temp1/_builds/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking CXX executable my_temp1"
$(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/my_temp1.dir/link.txt --verbose=$(VERBOSE)
The command $ cmake -H. -B_builds -DCMAKE_INSTALL_PREFIX=../__install finds fool2 because find_* commands searches in the CMAKE_INSTALL_PREFIX as well.
But why is there weird output my_temp1: Fool2::Fool2-NOTFOUND in build.make?
CMake version is 3.11.3
For IMPORTED library target value -NOTFOUND corresponds to absent IMPORTED_LOCATION property, corresponded to the library's path. You need to set that property for correctly work with IMPORTED target.
If you want CMake target to be a placeholder just for link with other libraries, use INTERFACE library target instead: such library target doesn't have library location.

Changed include path behaviour from CMake 2.8.x to 3.9.x

I have a fairly large cmake project that exhibits a compiler error when I use Makefiles generated by CMake 3.9.x:
Scanning dependencies of target client
[ 21%] Building C object
src/lib/client/CMakeFiles/client.dir/client.c.o
In file included from <command-line>:0:0:
/usr/include/stdc-predef.h:40:1: fatal error: compiler.h: No such file or directory
#endif
^
This works properly when using Makefiles generated by CMake 2.8.x. Digging in a bit, I can see that a change was made to the flags.make file generated by CMake between these two versions. The older version used to put -I (include) options in the C_FLAGS variable defined in that file:
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 2.8
# compile C with /usr/lib64/ccache/cc
C_FLAGS = -Wall -Wextra -Werror -Wformat=2 -Wundef -mcx16 -Werror-implicit-function-declaration -Wno-unused-parameter -D_GNU_SOURCE -include compiler.h -D__BUG_OUT_AUX=pd_trc_ftl -Wstrict-prototypes -Wdeclaration-after-statement -Wno-tautological-compare -g -I/.../src/lib/client ... -fPIC
C_DEFINES = -DBUILD_NUMBER=\"whatever\" -DBUILD_VERSION=\"1.66.0\" -DCOMMIT_HASH=\"f9bf1c93682f\" -DPDDEBUG -D_FILE_OFFSET_BITS=64
In later versions of CMake the -I options are broken out into their own C_INCLUDES variable:
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.9
# compile C with /usr/lib64/ccache/cc
C_FLAGS = -Wall -Wextra -Werror -Wformat=2 -Wundef -mcx16 -Werror-implicit-function-declaration -Wno-unused-parameter -D_GNU_SOURCE -include compiler.h -D__BUG_OUT_AUX=pd_trc_ftl -Wstrict-prototypes -Wdeclaration-after-statement -Wno-tautological-compare -g -fPIC
C_DEFINES = -DBUILD_NUMBER=\"whatever\" -DBUILD_VERSION=\"1.66.0\" -DCOMMIT_HASH=\"f9bf1c93682f\" -DPDDEBUG -D_FILE_OFFSET_BITS=64
C_INCLUDES = -I/.../src/lib/client ...
However, in both cases, the including file - build.make - uses only the $(C_DEFINES) $(C_FLAGS), omitting the $(C_INCLUDES) in the newer model:
...
# Include the compile flags for this target's objects.
include src/lib/client/CMakeFiles/client.dir/flags.make
src/lib/client/CMakeFiles/client.dir/client.c.o: src/lib/client/CMakeFiles/client.dir/flags.make
src/lib/client/CMakeFiles/client.dir/client.c.o: ../src/lib/client/client.c
#$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/.../cmake-build-debug/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building C object src/lib/client/CMakeFiles/client.dir/client.c.o"
cd /.../cmake-build-debug/src/lib/client && /.../contrib/cc/cgcc.sh /.../cmake-build-debug /usr/lib64/ccache/cc sparse ON /.../client-project CMakeFiles/client.dir/client.c.o $(C_DEFINES) $(C_FLAGS) -o CMakeFiles/client.dir/client.c.o -c /.../src/lib/client/client.c
...
Is this a bug in CMake 3.9.x? Has anyone else experienced anything like this when upgrading CMake?
I believe it's possible that we've always done something wrong in our CMakeLists.txt files that just happened to work in the older versions, but that when we upgraded to CMake 3.9.x, suddenly the problem is manifest. Hoping someone has had this issue and figured out what they did wrong.
Thanks in advance!