FetchContent / CMAKE_MAKE_PROGRAM issue

I’ve created a GitHub repo to show a minimal reproducible example as requested by @craig.scott in the other thread:

Basically the intent is to have a way to be in control of the version of Ninja my developers on my team use.
And ideally not have them have to download/set Ninja themselves. I want the experience as seemless as possible. Currently this type of workflow works fine since we just have our cmake modules inside our project. The problem arises in separating the cmake code in it’s own GitHub and downloading it using fetchcontent.

The module in question:

# example.cmake
include(GLOBAL)

function(set_ninja_version)
    set(CMAKE_MAKE_PROGRAM ${CMAKE_CURRENT_LIST_DIR}/ninja.exe)
endfunction()

Here is how I try and grab it:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.19)

include(FetchContent)

FetchContent_Declare(ninja_issue
    GIT_REPOSITORY "https://github.com/personWhoWritesCmakeExamples/ninja_fetch_content_issue"
    GIT_TAG "main"
)

FetchContent_MakeAvailable(ninja_issue)
list(APPEND CMAKE_MODULE_PATH "${ninja_issue_SOURCE_DIR}/")
include(example)

set_ninja_version()

project(FOOBAR
    LANGUAGES "CXX"
)

However this fails with the following output.

C:\coolguy> cmake -S . -B build_ninja -G "Ninja"
CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
-- Configuring incomplete, errors occurred!
See also "C:/coolguy/build_ninja/_deps/ninja_issue-subbuild/CMakeFiles/CMakeOutput.log".

CMake Error at C:/Program Files/CMake/share/cmake-3.19/Modules/FetchContent.cmake:977 (message):
  CMake step for ninja_issue failed: 1
Call Stack (most recent call first):
  C:/Program Files/CMake/share/cmake-3.19/Modules/FetchContent.cmake:1111:EVAL:2 (__FetchContent_directPopulate)
  C:/Program Files/CMake/share/cmake-3.19/Modules/FetchContent.cmake:1111 (cmake_language)
  C:/Program Files/CMake/share/cmake-3.19/Modules/FetchContent.cmake:1154 (FetchContent_Populate)
  CMakeLists.txt:13 (FetchContent_MakeAvailable)


-- Configuring incomplete, errors occurred!
C:\coolguy>

EDIT:

Link to the github: https://github.com/personWhoWritesCmakeExamples/ninja_fetch_content_issue

So FetchContent is doing its own logic that requires ninja. I don’t know it well enough to say whether that loop can be broken, but it would seem kind of fundamental.

This comment indicates that what you want to do is probably just not possible without a bootstrap build tool:

1 Like

FetchContent needs whatever CMAKE_MAKE_PROGRAM points at to exist, since it uses that tool to do the content population. It seems like in your case you already have a version of Ninja installed, you just want to download a more recent one and use that. This is quite an unusual case and not typically an arrangement I’d recommend. On Windows, I imagine you’d run into problems if you tried to replace the existing tool that is being used to run the sub-build. Saving the updated binary somewhere else and trying to switch the main project to this new location after the configuration processing has already started seems risky.

Maybe the simpler solution is to just have some kind of separate script your devs can run to go and grab the latest Ninja. They should run that script outside of any project processing.

1 Like

I’m not trying to do that.

The workflow I’m trying to create in this example is ideally developers never have to leave CMake.

  • Developer joins team
  • cmake -S . -B build -G “Ninja”
  • They just get Ninja downloaded for them. With the exact version I want them to have.

Why I want this.

  • Knowing what version of Ninja developers are using.
  • All they need is one build command to start contributing to the project.

But based off of everything I understand FetchContent isn’t the tool I should be using for this.

I think writing a CMake script which downloads ninja an places it somewhere would be best. See how CMake does it for CI (in powershell, but CMake code is just less verbose). You can then do:

cmake_minimum_required(VERSION X.Y)
if (NOT EXISTS "${CMAKE_SOURCE_DIR}/.build-tools/ninja.exe")
  execute_process(COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_SOURCE_DIR}/.build-tools/get-ninja.cmake")
endif ()
if (NOT CMAKE_MAKE_COMMAND AND CMAKE_GENERATOR MATCHES "Ninja")
  set(CMAKE_MAKE_COMMAND "${CMAKE_SOURCE_DIR}/.build-tools/ninja.exe" CACHE FILEPATH "")
endif ()
project(myproject)

Some error checking may be warranted.

1 Like

Your are very much getting to the heart of my desires here :smiley:

The problem is this works fine now for 1 project.

The problem is how do I share that code you wrote with multiple projects at my company.

Because ideally we are trying to standardize the build process for our software.

Ideally I’d have that shared code you wrote in a cmake module repo.

And ideally I’d fetch that cmake module repo using fetchcontent.

But that’s how this whole problem kinda started.

I’d just stuff it in a submodule if you really want the hassle of a submodule for a single CMake script and 8 lines of boilerplate. If you already have modules, sounds fine to me :slight_smile: .

Basically: bootstrapping is hard; everything needs to start somewhere. You’re digging close to the bottom of this well :slight_smile: .

1 Like

Not trying to resurrect an old thread, but leaving the below comment after ending up here while following links of a recent discussion.

There was work I did for FetchContent which avoided using a sub-build, but I had to revert it after it broke some existing behavior. I still harbour a desire to bring that change back in a form that avoids the previous problems. The performance gains on Windows especially make that highly desirable. As a nice side benefit, it would remove the above-mentioned problem and allow the original pattern discussed in this post to work (download the Ninja build tool via FetchContent before the first project() call).

I don’t know if or when I’ll get a chance to revisit that work for FetchContent, but it is still very much on my radar.