A dependency uses FetchContent in a tricky way, and I can't figure out how to add CMake install code to this dependency

The project, WebGPU-Distribution (GitHub - eliemichel/WebGPU-distribution: Distributions of WebGPU for native and web development, easy to integrate and interchangeable.), is designed to be used in your project via add_subdirectory. The “main” branch conditionally selects a more specific branch of the same repository to download via FetchContent, based on whether the build is Emscripten or not and whether you want wgpu or Dawn implementation on native. That fetched content in turn may download a pre-compiled wgpu distribution. (I am using wgpu for native and Emscripten for web.)

The batteries-included / one-touch method the project author designed works well for the tutorial of which that project is a component, however I organize my development environment differently. To avoid duplication of large game engine dependency libraries and to enforce a uniform standard, I install all of the dependencies of my projects to a common prefix directory, and use find_package to get them. Whenever a 3rd party library does not support this, I fork it and add that install code myself.

I think I understand the basic steps of adding CMake install code, and this has worked for me for several dependencies so far. In my own words:

  1. Change target_include_directories to use generator expressions.

  2. List the header files in a variable and pass that to install(FILES ...).

  3. Add install(TARGETS ...) for each target with exports and install destination.

  4. Generate config files and pass that to install(FILES ...).

  5. Use install(EXPORT ...) to install exports created during step three.

However when I got to WebGPU-Distribution, I couldn’t figure out how to adapt these steps to the way that project works. (I invite you to simply read the cmake file I’m talking about that I couldn’t figure out how to add install code to; it’s not very long: https://github.com/eliemichel/WebGPU-distribution/blob/main/webgpu.cmake).

Should I write code in the main branch of (my fork of) the WebGPU-Distribution project that reaches into ${CMAKE_CURRENT_BINARY_DIR} to hoist the downloaded wgpu DLLs with install(FILES …) into my common install directory? Should I add install code to each non-main branch (but then how do I kick off the install from main?) Should I convert the project from using FetchContent to use ExternalProject instead? Should I “git clone” each branch as a separate top-level folder (in my projects folder) and manually install each for just the one target configuration each applies to?

You might want to take some inspiration from Qt here. Have an “umbrella” project where you find the components via COMPONENTS arguments. So you might say:

find_package(WebGPU-Distribution COMPONENTS Dawn)
# or
find_package(WebGPU-Distribution COMPONENTS wgpu)

This project internally does a find_package for the “real” thing and provides convenience targets (or the individual projects do this). Then each “variant” of WebGPU-Distribution can use its own package name and avoid collisions. You can also do the find_package(WebGPU-Distribution-wgpu) (or whatever) directly.

However, I would look to getting upstream to support this so that you don’t conflict if some other pattern arises.

Yeah, the craziness of storing completely different code on each branch of the same repo… well, I didn’t like it when GitHub decided that’s how gh-pages would be implemented, and I don’t much like it here. But that’s really just a distraction. Your downstream project could just as easily choose a specific branch and include that FetchContent() command instead.

(Of course, there are no guarantees the branches’ contents won’t change at any moment, or even get deleted and replaced with a completely different set of branches that have different names. Which is why branches are the worst choice for organizing something like this, vs. non-ephemeral tags or releases.)

But regardless, the issue you’re facing is that no matter what path you go down, none of those secondary branches actually compiles anything for itself, either.

  • The wgpu-static branch includes pre-built static library binaries, which it exposes as an IMPORTED target.
  • The wgpu branch includes pre-built shared libraries – again, as IMPORTED targets.
  • The dawn branch does a SECOND FetchContent of its own, in order to download and build the dawn sources from Google. They’re compiled, and then published by the outer wrapper as an INTERFACE target.

And the thing is, you can’t install() IMPORTED or INTERFACE targets, and you can’t install(EXPORT ...) their configuration.

I’m not sure there’s a path to making that setup properly installable as its own package. It’s really designed solely as a convenience for sucking pre-built components into self-contained projects.

You can install INTERFACE targets (cf. header-only libraries). But yes, IMPORTED targets aren’t going to work.

1 Like