What is the difference when you execute cmake in a CMakeLists.txt versus you execute cmake in a terminal, and how can we make it behave the same?

Viewed 122

OK, I'm not sure how I can explain the problem exactly, but basically, in my Project X's CMakeLists.txt, I'm building external library dependencies which are separate projects in itself (say, Project A, Project B, Project C, Project D) - then I build using catkin_make.

I use CMake's ExternalProject_Add() for each projects A, B, C, D; followed by a declaration of add_dependencies() to make projects A, B, C, D build first before the main Project X.

Now, I particularly have problems with Project D, having the need to set multiple paths to CMAKE_PREFIX_PATH (eg, CMAKE_PREFIX_PATH=/addrA;/addrB;addrC).

  • Error #1: If I use ExternalProject_Add(), it recognizes the semicolon path separator ';' as "end of the command", and thus, it cuts the cmake command through the first path (only CMAKE_PREFIX_PATH=/addrA is recognized). I tried a lot of stuff already - usage of LIST_SEPARATOR, setting CMAKE_PREFIX_PATH before that using set(), or even using $<SEMICOLON> - nothing worked, same problem.
  • Error #2: What I did next - is instead of using ExternalProject_Add() to do both download and build of Project D - I used ExternalProject_Add() to just do download (eg, BUILD_COMMAND will be just set to ""), and then do the build separately via add_custom_target(). Now the cmake command from there also didn't work - because for some reason/limitation I don't know about, it didn't properly map the CMAKE_INSTALL_* flags, and even though the CMAKE_PREFIX_PATH was correct, it didn't really recognize the bin/lib/include directories that should've been automatically recognized inside the paths inside CMAKE_PREFIX_PATH. When I try to replace the correct values of these flags in the resulting CMakeCache.txt and rebuild via catkin_make, it works fine. But if I don't do that, like if I rely only on the initial CMakeCache.txt it generated, it won't work.

Please do take note, that when I try to run cmake of Project D in the terminal, I don't encounter any issues at all.

As a workaround, I did an execute_process() that creates a bash script containing the cmake and make command for Project D, and then I just call that bash script either at the ExternalProject_Add()'s BUILD_COMMAND or the add_custom_target()'s COMMAND. Either of these works - which just shows, that the cmake executed within a COMMAND of either ExternalProject_Add() or add_custom_target() is somehow different than executing cmake in a terminal or through a bash script.

I've searched around everything here but I couldn't find anyone having the same problem as me, thus my workaround for now.

Here is the rough code that works (the one with my workaround):

### Project A 
include(ExternalProject)
ExternalProject_Add(ProjectA
    PREFIX             /tmp/ProjectA
    GIT_REPOSITORY     https://github.com/ProjectA
    GIT_TAG            v0.0.1
    CONFIGURE_COMMAND  ""
    BUILD_COMMAND      cmake -DWITH_GFLAGS=off -DCMAKE_INSTALL_PREFIX=/opt/ProjectA <SOURCE_DIR>
    COMMAND            sudo cmake --build . --target install
    INSTALL_COMMAND    ""
    TEST_COMMAND       ""
)

### Project B
include(ExternalProject)
ExternalProject_Add(ProjectB
    PREFIX             /tmp/ProjectB
    GIT_REPOSITORY     https://github.com/ProjectB
    GIT_TAG            v0.0.1
    CONFIGURE_COMMAND  ""
    BUILD_COMMAND      cmake -DLWS_STATIC_PIC=ON -DCMAKE_INSTALL_PREFIX=/opt/ProjectB <SOURCE_DIR>
    COMMAND            sudo cmake --build . --target install
    INSTALL_COMMAND    ""
    TEST_COMMAND       ""
)

### Project C
include(ExternalProject)
ExternalProject_Add(ProjectC
    PREFIX             /tmp/ProjectC
    GIT_REPOSITORY     https://github.com/ProjectC
    GIT_TAG            v0.0.1
    CONFIGURE_COMMAND  ""
    BUILD_COMMAND      cmake -Dprotobuf_BUILD_TESTS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/opt/ProjectC <SOURCE_DIR>
    COMMAND            sudo cmake --build . --target install
    INSTALL_COMMAND    ""
    TEST_COMMAND       ""
)

### Project D
execute_process (
    COMMAND bash -c "echo '#!/bin/bash\ncmake -DCMAKE_PREFIX_PATH=\"/opt/ProjectA;/opt/ProjectB;/opt/ProjectC\" /tmp/ProjectD/src/ProjectD\nmake' > /tmp/projectD_build.sh"
    COMMAND bash -c "chmod 777 /tmp/projectD_build.sh"
)
include(ExternalProject)
ExternalProject_Add(ProjectD
    PREFIX             /tmp/ProjectD
    GIT_REPOSITORY     https://github.com/ProjectD
    GIT_TAG            master
    CONFIGURE_COMMAND  ""
    BUILD_COMMAND      /tmp/projectD_build.sh
    INSTALL_COMMAND    ""
    TEST_COMMAND       ""
)

### Set dependencies to force build Project A, B, C, D before Project X is build
add_library (ProjectX SHARED IMPORTED)
set_target_properties (ProjectX PROPERTIES IMPORTED_LOCATION ${ProjectX_LIBS})
add_dependencies(ProjectX ProjectA)
add_dependencies(ProjectX ProjectB)
add_dependencies(ProjectX ProjectC)
add_dependencies(ProjectX ProjectD)

### Project X build commands here (snipped)

These are the cmake + make commands inside /tmp/ProjecD_build.txt:

cmake -DCMAKE_PREFIX_PATH="/opt/ProjectA;/opt/ProjectB;/opt/ProjectC" /tmp/ProjectD/src/ProjectD
make

And if I replace the whole "### Project D" section with this, I encounter the Error #1 that error I've discussed above (problem with multiple path CMAKE_PREFIX_PATH):

### Project D
include(ExternalProject)
ExternalProject_Add(ProjectD
    PREFIX             /tmp/ProjectD
    GIT_REPOSITORY     https://github.com/ProjectD
    GIT_TAG            master
    CONFIGURE_COMMAND  ""
    BUILD_COMMAND      cmake -DCMAKE_PREFIX_PATH="/opt/ProjectA;/opt/ProjectB;/opt/ProjectC" /tmp/ProjectD/src/ProjectD
    INSTALL_COMMAND    make
    TEST_COMMAND       ""
)

... or add a post-ProjectD like this, it still doesn't work and causes Error #2 I discussed above (CMAKE flags not properly set as seen in CMakeCache.txt). Note that with this solution, I the Project D ExternalProject_Add()'s BUILD_COMMAND will be just set to "" since the build will be done by the added add_custom_target() instead:

ExternalProject_Get_Property(ProjectD install_dir)
ExternalProject_Get_Property(ProjectD source_dir)
ExternalProject_Get_Property(ProjectD binary_dir)
message(STATUS "install_dir is: " ${install_dir})
message(STATUS "source_dir is: " ${source_dir})
message(STATUS "binary_dir is: " ${binary_dir})

add_custom_target(ProjectD_part2 ALL
    cmake -DCMAKE_PREFIX_PATH="/opt/ProjectA;/opt/ProjectB;/opt/ProjectC" ${source_dir}
    COMMAND make
    WORKING_DIRECTORY ${binary_dir}
)

add_dependencies(ProjectD_part2 ProjectA)
add_dependencies(ProjectD_part2 ProjectB)
add_dependencies(ProjectD_part2 ProjectC)
add_dependencies(ProjectD_part2 ProjectD)

The workaround works. But is there a better way of doing this? Thank you.

1 Answers

Looks complicated! Can you please try https://github.com/floooh/fips which make lots of the cmake things easier. Fips is a Python command line tool which provides an ‘Integrated Build Environment’ for C/C++ projects by wiring together existing build tools. It enables a workflow similar to Rust’s Cargo or Javascript’s NPM, but for C/C++ projects.

Related