4

I have a CMake project that depends on Google Test library. I've added Google Test using ExternalProject_Add and add_subdirectory as described here (https://github.com/google/googletest/tree/master/googletest section Incorporating Into An Existing CMake Project).

Now, after command add_subdirectory I want to check version of included project gtest and gmock, however gtest_VERSION and gmock_VERSION variables is undefined. Is it possible to get project version added by add_subdirectory?

Kamil Zaripov
  • 904
  • 11
  • 33

2 Answers2

6

Since you don't have direct access to sub-directories local variables, I wanted to add my variants of making the gtest_VERSION variable CACHED and therefore globally accessible:

  1. CMAKE_PROJECT_<PROJECT-NAME>_INCLUDE

    A CMake language file or module to be included by the project() command. This is intended for injecting custom code into project builds without modifying their source.

    file(
        WRITE "${CMAKE_BINARY_DIR}/SetProjectVersion.cmake"
        [=[
            set(${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION}" CACHE INTERNAL "")
        ]=]
    )
    set(
        CMAKE_PROJECT_gtest_INCLUDE
        "${CMAKE_BINARY_DIR}/SetProjectVersion.cmake"
    )
    
  2. Overwrite the project() command

    cmake_policy(SET CMP0048 NEW)    
    
    macro(project)
        _project(${ARGN})
        set(${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION}" CACHE INTERNAL "")
    endmacro()
    
Florian
  • 39,996
  • 9
  • 133
  • 149
3

Variable <PROJECT-NAME>_VERSION has a local scope. Thus a version variable, defined by a subproject, is not visible by a main project.

Assuming a subproject's CMakeLists.txt performs project() call like

project(<project-name> VERSION <version-string> ...)

version string can be easily extracted with regular expressions:

# subproject_version(<subproject-name> <result-variable>)
#
# Extract version of a sub-project, which was previously included with add_subdirectory().
function(subproject_version subproject_name VERSION_VAR)
    # Read CMakeLists.txt for subproject and extract project() call(s) from it.
    file(STRINGS "${${subproject_name}_SOURCE_DIR}/CMakeLists.txt" project_calls REGEX "[ \t]*project\\(")
    # For every project() call try to extract its VERSION option
    foreach(project_call ${project_calls})
        string(REGEX MATCH "VERSION[ ]+([^ )]+)" version_param "${project_call}")
        if(version_param)
            set(version_value "${CMAKE_MATCH_1}")
        endif()
    endforeach()
    if(version_value)
        set(${VERSION_VAR} "${version_value}" PARENT_SCOPE)
    else()
        message("WARNING: Cannot extract version for subproject '${subproject_name}'")
    endif()

endfunction(subproject_version)

# The function's usage:    
subproject_version(gtest gtest_version)
message("VERSION for gtest: ${gtest_version}")

Implementation above uses variable <PROJECT-NAME>_SOURCE_DIR, which contains source directory of the subproject. Unlike to <PROJECT-NAME>_VERSION variable, variable with source directory has global visibility (it is CACHED, actually), thus it can be used outside of the subproject.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • It's worth noting that this won't work if the sub-project splits its call to `project()` among multiple lines. While this is certainly possible to handle with more common regex engines, I wasn't able to come up with an expression that worked with CMake's more limited one. Notably, due to its lack of lazy quantifiers and single-line mode. – oblivioncth Feb 27 '23 at 17:09