Search code examples
bashshellcmakemakefiletarget

How to compile a subset of all targets in a CMake-generated Makefile that start with a given prefix in a bash for loop


I want to write a shell script that will only compile and execute the unit test binaries in my project. All targets in the Makefile (generated by CMake) associated with unit tests begin with UnitTest, e.g. UnitTestArray. How can I write a for loop in the shell script to individually compile all unit test binaries? I know how to sequentially run the pre-compiled binaries in a loop. It's just the compilation part that I am uncertain about. See my shell script below.

WORKDIR=$PWD
BUILD=~/Dropbox/Projects/Apeiron/build
BIN=$BUILD/bin

# Compile test binaries
cd $BUILD
for target in [UnitTest* in Makefile] # I'm not sure how to write this for loop.
do
  echo ""
  echo "Compiling $target" 
  make -j 8 $target 
done

# Run tests
cd $BIN
for test in UnitTest*
do
  echo ""
  echo "Running $test" 
  ./$test 
done

cd $WORKDIR

The following are some example target rules in the generated Makefile:

#=============================================================================
# Target rules for targets named UnitTestApeiron

# Build rule for target.
UnitTestApeiron: cmake_check_build_system
    $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 UnitTestApeiron
.PHONY : UnitTestApeiron

# fast build rule for target.
UnitTestApeiron/fast:
    $(MAKE) $(MAKESILENT) -f CMakeFiles/UnitTestApeiron.dir/build.make CMakeFiles/UnitTestApeiron.dir/build
.PHONY : UnitTestApeiron/fast

#=============================================================================
# Target rules for targets named UnitTestArray

# Build rule for target.
UnitTestArray: cmake_check_build_system
    $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 UnitTestArray
.PHONY : UnitTestArray

# fast build rule for target.
UnitTestArray/fast:
    $(MAKE) $(MAKESILENT) -f CMakeFiles/UnitTestArray.dir/build.make CMakeFiles/UnitTestArray.dir/build
.PHONY : UnitTestArray/fast

Solution

  • I resorted to the solutions suggested by users @MadScientist and @Saboteur in the above comments. The following is the modification of my shell script using @Saboteur's solution:

    WORKDIR=$PWD
    BUILD=~/Dropbox/Projects/Apeiron/build
    BIN=$BUILD/bin
    
    # Compile test binaries
    cd $BUILD
    declare -a TargetList=$(grep "cmake_check_build_system" $BUILD/Makefile|cut -d":" -f1)
    for target in ${TargetList[@]}; do
      # Check if target keyword contains the substring "UnitTest"
      if grep -q "UnitTest" <<< "$target"; then
         printf "\nCompiling $target\n" 
         make -j 16 $target
      fi
    done
    
    # Run tests
    cd $BIN
    for test in UnitTest*
    do
      printf "\nRunning $test\n" 
      ./$test 
    done
    
    cd $WORKDIR
    

    This yields the following output:

    $ ./unit_test.sh 
    Compiling UnitTestArray
    [ 16%] Building CXX object _deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o
    [ 33%] Linking CXX static library ../../../lib/libgtest.a
    [ 33%] Built target gtest
    [ 50%] Building CXX object _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/src/gtest_main.cc.o
    [ 66%] Linking CXX static library ../../../lib/libgtest_main.a
    [ 66%] Built target gtest_main
    [ 83%] Building CXX object CMakeFiles/UnitTestArray.dir/libs/DataContainers/test/UnitTestArray.cpp.o
    [100%] Linking CXX executable bin/UnitTestArray
    [100%] Built target UnitTestArray
    
    Compiling UnitTestApeiron
    Consolidate compiler generated dependencies of target gtest
    [ 33%] Built target gtest
    Consolidate compiler generated dependencies of target gtest_main
    [ 66%] Built target gtest_main
    [ 83%] Building CXX object CMakeFiles/UnitTestApeiron.dir/test/UnitTestApeiron.cpp.o
    [100%] Linking CXX executable bin/UnitTestApeiron
    [100%] Built target UnitTestApeiron
    
    Running UnitTestApeiron
    Running main() from /home/niran90/Dropbox/Projects/Apeiron/build/_deps/googletest-src/googletest/src/gtest_main.cc
    [==========] Running 15 tests from 1 test suite.
    [----------] Global test environment set-up.
    [----------] 15 tests from ApeironTest
    [ RUN      ] ApeironTest.Min
    [       OK ] ApeironTest.Min (0 ms)
    [ RUN      ] ApeironTest.Max
    [       OK ] ApeironTest.Max (0 ms)
    [ RUN      ] ApeironTest.MinMax
    [       OK ] ApeironTest.MinMax (0 ms)
    [ RUN      ] ApeironTest.Bound
    [       OK ] ApeironTest.Bound (0 ms)
    [ RUN      ] ApeironTest.Sgn
    [       OK ] ApeironTest.Sgn (0 ms)
    [ RUN      ] ApeironTest.Abs
    [       OK ] ApeironTest.Abs (0 ms)
    [ RUN      ] ApeironTest.Floor
    [       OK ] ApeironTest.Floor (0 ms)
    [ RUN      ] ApeironTest.Ceil
    [       OK ] ApeironTest.Ceil (0 ms)
    [ RUN      ] ApeironTest.Round
    [       OK ] ApeironTest.Round (0 ms)
    [ RUN      ] ApeironTest.isEqual
    [       OK ] ApeironTest.isEqual (0 ms)
    [ RUN      ] ApeironTest.isLess
    [       OK ] ApeironTest.isLess (0 ms)
    [ RUN      ] ApeironTest.isLessEqual
    [       OK ] ApeironTest.isLessEqual (0 ms)
    [ RUN      ] ApeironTest.isLarger
    [       OK ] ApeironTest.isLarger (0 ms)
    [ RUN      ] ApeironTest.isLargerEqual
    [       OK ] ApeironTest.isLargerEqual (1 ms)
    [ RUN      ] ApeironTest.isBounded
    [       OK ] ApeironTest.isBounded (0 ms)
    [----------] 15 tests from ApeironTest (1 ms total)
    
    [----------] Global test environment tear-down
    [==========] 15 tests from 1 test suite ran. (1 ms total)
    [  PASSED  ] 15 tests.
    
    Running UnitTestArray
    Running main() from /home/niran90/Dropbox/Projects/Apeiron/build/_deps/googletest-src/googletest/src/gtest_main.cc
    [==========] Running 2 tests from 1 test suite.
    [----------] Global test environment set-up.
    [----------] 2 tests from ArrayTest
    [ RUN      ] ArrayTest.Initialisation
    [       OK ] ArrayTest.Initialisation (0 ms)
    [ RUN      ] ArrayTest.AssignmentOperator
    [       OK ] ArrayTest.AssignmentOperator (0 ms)
    [----------] 2 tests from ArrayTest (0 ms total)
    
    [----------] Global test environment tear-down
    [==========] 2 tests from 1 test suite ran. (0 ms total)
    [  PASSED  ] 2 tests.
    

    I can see @MadScientist's suggestion to use CTest being very useful when the number of gtest unit tests is quite large. This is because CTest provides a concise summary of the passed/failed tests. I had to configure my project CMakeLists.txt as follows:

    enable_testing()
    
    # Fetch GoogleTest directories
    include(FetchContent)
    FetchContent_Declare(GoogleTest URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip)
    FetchContent_MakeAvailable(GoogleTest)
    
    # Add unit test executables
    add_executable(UnitTestApeiron ${PROJECT_SOURCE_DIR}/test/UnitTestApeiron.cpp)
    add_executable(UnitTestArray ${PROJECT_SOURCE_DIR}/libs/DataContainers/test/UnitTestArray.cpp)
    
    # Link with gtest, gtest_main, and associated libraries.
    target_link_libraries(UnitTestApeiron gtest gtest_main)
    target_link_libraries(UnitTestArray gtest gtest_main)
    
    add_test(UnitTestApeiron ${BIN_DIRECTORY}/UnitTestApeiron)
    add_test(UnitTestArray ${BIN_DIRECTORY}/UnitTestArray)
    

    Then, simply running ctest in the project build directory gives:

    $ ctest 
    Test project /home/niran90/Dropbox/Projects/Apeiron/build
        Start 1: UnitTestApeiron
    1/2 Test #1: UnitTestApeiron ..................   Passed    0.01 sec
        Start 2: UnitTestArray
    2/2 Test #2: UnitTestArray ....................   Passed    0.00 sec
    
    100% tests passed, 0 tests failed out of 2
    
    Total Test time (real) =   0.01 sec