Project-level namespaces
Discussion of this topic has been scattered among a few other issues, with #22619 (closed) being the most recent, but also #16414 and #20208 - please read each of these, they have important discussions relevant to here. This issue here is intended to bring together those discussions and focus on a way forward.
A common problem when pulling in subprojects using add_subdirectory()
is that target names must be unique, but different subprojects might try to use the same target name, which results in a name clash. This issue proposes to introduce the concept of a project namespace to address this and related name uniqueness problems (this is the primary goal of this issue).
We already have a close analogy with the export namespace used during install. When installed, packages will usually have imported targets of the form ExportNamespace::ExportName
. Typically, the ExportNamespace
has a close relationship to the project name or package name, while the ExportName
will usually be a target name, or the target name with a project-specific part stripped off it. For example:
add_library(MyProj_something ...) # or often just "something"
# Enable installed and add_subdirectory() scenarios to use the same target: MyProj::something
add_library(MyProj::something ALIAS MyProj_something)
set_target_properties(MyProj_something PROPERTIES EXPORT_NAME something)
install(TARGET MyProj_something EXPORT MyProjTargets ...)
install(EXPORT MyProjTargets NAMESPACE MyProj:: ...)
export(EXPORT MyProjTargets NAMESPACE MyProj:: ...) # Non-install support, but also relevant
The above example shows how namespaces are currently used to make installed packages avoid name clash problems in consuming projects. This discussion here proposes to extend that same idea to the build itself. The key points of this proposal are as follows.
The project()
command sets a namespace. By default, this will be ${PROJECT_NAME}::
, but can be overridden with a new NAMESPACE
keyword (the trailing ::
should not be included in the value that follows NAMESPACE
, it will be automatically appended).
Calls to add_library()
, add_executable()
and add_custom_target()
will define a target basically the same way they do now. It will also automatically create an alias target with the current effective namespace (i.e. the most recent project()
call in the current or parent(s) directory scope) -- note that an alias for a custom target would be new functionality (see #18970). It would also no longer be an error to create a target with the same unnamespaced name, unless it also has the same namespace as an existing target. Internally, the namespace alias can be used to disambiguate different targets with the same (unnamespaced) name when storing them in CMake's C++ data structures (conceptually at least, the implementation specifics have not been explored, but it is acknowledged that this would almost certainly be a non-trivial change).
Within the same project, a target can be referred to with or without a namespace. When the target name needs to be resolved, CMake first looks in the current project, prepending the project's namespace if the name doesn't have one already. If that resolves to an existing target, that will be the one used. Otherwise, the search continues outside the current project with the name as originally provided (i.e. without any automatically prepended namespace). The semantics of that search would be the same as now, where the search can only result in finding one (unnamespaced) target by that name or it will be an error.
To facilitate using this with existing projects, calls to add_library()
or add_executable()
which try to explicitly create the same alias target as the automatically created one should silently succeed instead of causing an error. For example:
project(MyProj) # Could also use a "NAMESPACE OtherNS" here to change the namespace from the default
add_library(something ...)
# The following call is no longer needed, but would not be an error since it matches
# the automatically created alias
add_library(MyProj::something ALIAS something)
install(TARGET MyProj_something EXPORT MyProjTargets ...)
install(EXPORT MyProjTargets ...) # No longer needs to specify NAMESPACE here...
export(EXPORT MyProjTargets ...) # ... or here, but could still do so if you wanted to
We could also consider automatically stripping off part of the target name if it matched the namespace and didn't result in an empty string. That matching would need to consider underscores or hyphens in the target name as optional separators too. The advantage of such stripping would be to support existing projects that have already prepended their project name to all target names to avoid name clashes with other projects.
add_library(MyProjBlah ...) # Exported name: Blah
add_library(MyProj_something ...) # Exported name: something
add_library(MyProj-joe ...) # Exported name: joe
add_library(MyProj) # Exported name: MyProj
It is unclear whether a policy would be needed for the new behavior proposed in this issue. Ideally, all existing projects would continue to work with the above logic. We would need to consider whether there are any potential corner cases where behavior could change as a result. Having to use a policy for this would greatly reduce its value, as it would mean essentially all dependencies brought in via add_subdirectory()
would have to be updated to take advantage of the namespace feature. One of the main motivations for the feature though is to handle third party projects which either can't easily be updated or where it isn't practical to do so (e.g. many dependencies to update, some of which may be quite old and others which might have little interest in updating to support newer CMake features). We may want to consider introducing a policy just for the parts that need it (e.g. the namespace stripping from target names).
A secondary but nonetheless useful advantage to this proposal is that it will hopefully encourage more standardised target naming. Consumers should increasingly expect to be able to work with namespaced names that align with a package's project()
command and we should see fewer uses of implementation specific names like PkgConfig::someTarget
, PackageManagerName::someTarget
, etc.
A logical analogy with C++ namespaces exists, and that will naturally lead to the question of whether we should allow the contents of one namespace to be imported into another (a similar capability is also present in other languages that import packages, modules, etc.). I would suggest that this issue is already huge without considering that aspect, and while it might ultimately be worth looking at eventually, there is more value in focusing discussions on the core aspects outlined above for now.
Explicitly Out Of Scope
Discussion of how to simplify the install process of packages is out of scope for this issue (EDIT: install components are relevant here though, see comments). It is definitely related, but that's a whole separate discussion of its own (see #20208 which already contains a lot of ideas and discussion about that). If you feel compelled to comment on that topic, please find or open a separate issue and refer to this one so as not to derail the focus of what this issue aims to address. Please constrain your comments here to be consistent with the goals of this issue.