Creating CMake module repository: scope issues

Hello, I am new here.

I have been attempting to setup a git repository that will contain cmake modules.
I have achieved bringing this into another project using the Fetch Content pattern presented by Craig Scott in his book (and likely by others too). However, my module’s functions don’t actually work because the files that the function requires are buried deep inside the cmake build structure and I don’t know how to trigger an install to deliver the files to the local project scope. But I also don’t want to do that because it would clutter my source tree with module files. Maybe there is a way to get the path to these files once my module subdirectory is added (but how)?

I currently have one module which has a function used to generate windows dll/exe version properties. In order to achieve this, there are two template files which are processed by configure_file and which generate a version.h and a versionresource.rc file, then adds the path to those generated files into a list variable provided to the function. Then the calling scope would add these to the target include directories and my dll gets built with version meta data.

But as I said before, I cannot generate the configured files because they are located relative to the cmake module itself (and part of that module’s git repo).

What am I doing wrong? What is the right way to achieve this? I would love it if my modules were consumable as installed (packaged zip), by fetch content, and by external project, but I will accept only fetch content or only external project if it works well.

This is for my job, so I can’t really post too much detail. The git repo is for internal use.

Thanks a lot in advance. I’ve been searching for hours and rereading the book trying to figure this out.

  • David
1 Like

How are you referring to those files? In CMake 3.17, variable CMAKE_CURRENT_FUNCTION_LIST_DIR was introduced for precisely this usage (see the linked documentation).

Prior to 3.17, you can use the trick with storing CMAKE_CURRENT_LIST_DIR at module scope (which is also shown in the linked docs).

1 Like

Hi Petr,

I posted last night from my phone. I’m at my PC now so let me share a little bit of the code:
The cmake project file that contains the cmake module looks like this:

cmake_minimum_required(VERSION 3.25)
project(company-CMakeModules LANGUAGES NONE)

add_subdirectory(CMakeBinaryVersioning)

And the contents of ./CMakeBinaryVersioning/CMakeLists.txt:

cmake_minimum_required(VERSION 3.25)
project(cmake-binary-versioning LANGUAGES NONE)
enable_testing()

include(GenerateBinaryVersion.cmake)

install(
        FILES GenerateBinaryVersion.cmake
        DESTINATION share/cmake/GenerateBinaryVersion
        COMPONENT cmake-binary-versioning
        )

To answer your direct question, here is the relevant portion of GenerateBinaryVersion.cmake:

... other stuff
    set(VERSION_H_FILEPATH ${TARGET}Version.h)
    set(VERSIONRESOURCE_RC_FILEPATH ${TARGET}VersionResource.rc)

    configure_file(
        templates/Version.in
        ${VERSION_H_FILEPATH}
        @ONLY
    )
    configure_file(
        templates/VersionResource.in
        ${VERSIONRESOURCE_RC_FILEPATH}
        @ONLY
    )

    list(APPEND outfiles ${VERSION_H_FILEPATH} ${VERSIONRESOURCE_RC_FILEPATH})
    set (${VARNAME} ${outfiles} PARENT_SCOPE)
... other stuff

As you can see, the templates are being referred to in a relative path, with the expectation that they are relative to the module itself. Since this function is called from the scope of a top level project, this is invalid. I just don’t know how to refer directly to these files from within this function scope.
The files are located here:
├───CMakeBinaryVersioning/
└───templates/Version.in
└───templates/VersionResource.in

I’m going to look at the documentation you referenced and I’ll respond back if I am able to solve the problem. In the mean time, if you or anyone else sees any red flags, please let me know.

Thanks!

In the CMakeBinaryVersioning CMakeLists.txt, you should add:

cmake_minimum_required(VERSION 3.25)
project(cmake-binary-versioning LANGUAGES NONE)
enable_testing()

list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")

include(GenerateBinaryVersion.cmake)

and in your helper function, change the paths to be absolute by using CMAKE_CURRENT_FUNCTION_LIST_DIR:

function(foo)

configure_file(
        "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/templates/Version.in"
        ${VERSION_H_FILEPATH}
        @ONLY
    )

endfunction()
1 Like

Usage of “${CMAKE_CURRENT_FUNCTION_LIST_DIR}/templates/Version.in” did the trick! Thanks guys.

@benthevining why is it necessary to append to the module path in this case? My understanding is that the module will automatically be loaded since add_subdirectory() is called by the FetchContent_MakeAvailable command. Is there another purpose to appending to the module path?

1 Like

The issue is the line include(GenerateBinaryVersion.cmake) in the CMakeBinaryVersioning CMakeLists.txt. The include command can take an absolute path, a relative path (which is what you currently have), or just a module name, which will be searched in the CMAKE_MODULE_PATH.

If someone in the future removes the .cmake to make that line just include(GenerateBinaryVersion), that’s no longer a relative path so the include will fail.

The most robust way to ensure your provided cmake module is loaded would be to use an absolute path, ie: include ("${CMAKE_CURRENT_LIST_DIR}/GenerateBinaryVersion.cmake"). I usually prefer to add the directory to CMAKE_MODULE_PATH and then just have include(GenerateBinaryVersion), because it’s more idiomatic and allows users to override modules by placing them in a directory listed earlier in CMAKE_MODULE_PATH.

1 Like

Excellent explanation. Thanks! I will incorporate this, too.

1 Like

I’ve been trying to modify the CMAKE_MODULE_PATH as suggested by @benthevining but it’s not modifying the variable in the outer scope and therefore doesn’t work. I even tried including it in this module, as shown below, and it doesn’t find it there either.

What would prevent the append of CMAKE_MODULE_PATH from propagating up the chain?
And why doesn’t it even work in this scope?

CMakeBinaryVersioning/CMakeLists.txt:


cmake_minimum_required(VERSION 3.25)
project(cmake-binary-versioning LANGUAGES NONE)
enable_testing()

list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_FUNCTION_LIST_DIR}" )
include(GenerateBinaryVersion)

install(
        FILES GenerateBinaryVersion.cmake
        DESTINATION share/cmake/GenerateBinaryVersion
        COMPONENT cmake-binary-versioning
)

you could try

list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_FUNCTION_LIST_DIR}")
set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} PARENT_SCOPE)

or you could try setting it in the cache.

This is an issue I’ve run into before, though. Because of variable scoping, it can be difficult to propagate the module path upward to consumers.

Gave that a shot, but unfortunately it’s not working. I still can’t include the module even in the same scope. I am very confused why this doesn’t work. The module path is set literally right before the include. Is the include command using an outer-scope version of the variable and it’s being shadowed?

Edit: does redefining a project in my cmake module clear out these variables? I thought it was correct to define a project within each cmake module/submodules but I am somewhat new to this.

Silly question, but does ${CMAKE_CURRENT_FUNCTION_LIST_DIR} work outside of a function?

The way you’re trying to use it in your example there, unlike in Ben’s earlier example, isn’t inside a function. It’s just at the top level scope of your CMakeLists.txt. I wouldn’t be surprised if ${CMAKE_CURRENT_FUNCTION_LIST_DIR} has no definition at that scope.