3

Suppose I'm writing an app, and managing its build with CMake; and I also want to use a library, mylib, via the FetchContent mechanism.

Now, my own CMakeLists.txt defines a bunch of targets, and so does mylib's CMakeLists.txt. If I were to install mylib, then find_package(mylib), I would only get its exported targets, and even those would be prefixed with mylib:: (customarily, at least). But with FetchContent, both my app's and mylib's (internal and export-intended) targets are in the "global namespace", and may clash.

So, what can I do to separate those targets - other than meticulously name all of my own app's targets defensively?

I would really like it if it were possible to somehow "shove" all the mylib targets into a namespace of my choice.


Note: Relates to: How to avoid namespace collision when using CMake FetchContent?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    You cannot. There's an open issue with upstream about adding namespacing to solve this problem, but it hasn't been implemented yet. I really shy away from FetchContent because of issues like this. – Alex Reinking Jul 11 '22 at 03:54
  • @AlexReinking: I don't like it myself, but users of my libraries want it... – einpoklum Jul 11 '22 at 09:04

2 Answers2

3

In the current CMake (<=3.24) world, there are no features for adjusting the names of the targets in other black-box projects, whether included via find_package, add_subdirectory, or FetchContent. Thus, for now, it is incumbent on you to avoid name-clashes in targets, install components, test names, and anywhere else this could be a problem.

Craig Scott says as much in his (very good) talk at CppCon 2019, see here: https://youtu.be/m0DwB4OvDXk?t=2186

The convention he proposes is to use names that are prefixed with SomeProj_. He doesn't suggest to literally use ${PROJECT_NAME}_, and I wouldn't either, because doing so makes the code harder to read and grep (which is extremely useful for understanding a 3rd-party build).

To be a good add_subdirectory or FetchContent citizen, however, it is not enough to simply namespace your targets as SomeProj_Target; you must also provide an ALIAS target SomeProj::Target. There are a few reasons for this:

  1. Your imported targets from find_package will almost certainly be named SomeProj::Target. It should be possible for consumers of your library to switch between FetchContent and find_package easily, without changing other parts of their code. The ALIAS target lets you expose the same interface in both cases. This will become especially pressing when CMake 3.24 lands with its new find_package-to-FetchContent redirection features.
  2. CMake's target_link_libraries function treats names that contain :: as target names always and will throw configure-time error if the target does not exist. Without the ::, it will be treated as a target preferentially, but will turn into a linker flag if the target doesn't exist. Thus, it is preferable to link to targets with :: in their names.
  3. Yet, only IMPORTED and ALIAS targets may have :: in their names.

Points (2) and (3) are good enough for me to define aliases.

Unfortunately, many (most?) CMake builds are not good FetchContent citizens and will flaunt this convention. Following this convention yourself reduces the chance of integration issues between your project and any other, but obviously does nothing to prevent issues between two third party projects that might define conflicting targets. In these cases, you're just out of luck.


An example of defining a library called Target that will play nice with FetchContent:

add_library(SomeProj_Target ${sources})
add_library(SomeProj::Target ALIAS SomeProj_Target)
set_target_properties(
  SomeProj_Target
  PROPERTIES
  EXPORT_NAME Target
  OUTPUT_NAME Target  # optional: makes the file libTarget.so on disk
)

install(TARGETS SomeProj_Target EXPORT SomeProj_Targets)
install(EXPORT SomeProj_Targets NAMESPACE SomeProj::)

For a more complete example that plays nice with install components, include paths, and dual shared/static import, see my blog post.


See these upstream issues to track the progress/discussion of these problems.

  • #22687 Project-level namespaces
  • #16414 Namespace support for target names in nested projects
Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
1

As @AlexReinking , and, in fact, Craig Scott, suggest - there's no decent current solution.

You can follow the following CMake issues through which the solution will likely be achieved:

  • #22687 Project-level namespaces (more current)
  • #16414 Namespace support for target names in nested projects (longer discussion which influenced the above, recommended reading)
Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    Craig says in a talk that manually namespacing your targets beneath your project's name (e.g. `Halide`, `Halide_Runtime`, `Halide_ImageIO`, etc.), plus adding aliases (e.g. `Halide::Halide`, `Halide::Runtime`, `Halide::ImageIO`, etc.) is the best practice in the current CMake era (until the above issues are resolved). – Alex Reinking Jul 11 '22 at 13:06
  • He makes this point about [install component names in this part of the talk](https://youtu.be/m0DwB4OvDXk?t=2186). – Alex Reinking Jul 11 '22 at 13:07
  • @AlexReinking: 1. Why do both, as opposed to using the "namespaced" targets internally to begin with? 2. You often can't control this, since subdirectories/submodules etc. and maybe even included files may create their own targets which don't follow this convention. Or am I wrong? – einpoklum Jul 11 '22 at 13:27
  • this is too long for a comment, sorry. I ran out of characters. I can add a longer answer to this question, if you prefer. – Alex Reinking Jul 11 '22 at 13:29
  • @AlexReinking: Yes, please do. – einpoklum Jul 11 '22 at 14:32