To maybe give you another example, you could have a look at my library Thor. It's an extension to SFML and thus depends on SFML (and transitively SFML's own dependencies), however Thor itself is written in pure C++.
https://github.com/Bromeon/ThorAs such, the CMake files are considerably simpler than for SFML, but there's still a few of them (for different build targets):
There are a few occurrences of explicit case differentiations:
# Variable for install directory
if(UNIX)
set(THOR_EXAMPLE_INSTALL_DIR share/Thor)
set(THOR_DOC_INSTALL_DIR share/doc/Thor)
else()
set(THOR_EXAMPLE_INSTALL_DIR .)
set(THOR_DOC_INSTALL_DIR .)
endif()
# Install (use "bin" folder for DLL, "lib" for SO files)
if(WIN32)
install(TARGETS ${THOR_LIB}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
else()
install(TARGETS ${THOR_LIB}
RUNTIME DESTINATION lib
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
endif()
if (APPLE)
set_target_properties(${THOR_LIB} PROPERTIES
BUILD_WITH_INSTALL_RPATH 1
INSTALL_NAME_DIR "@rpath")
endif()
Plus a few ones for static/dynamic linking and choosing the right SFML versions.
I agree with your statement, that CMake cannot keep its promise of abstracting all its build steps. It has annoyed myself a lot in the past, and made me think why there is no better solution even after decades. In my opinion, there are several reasons for this, but at the core all lies the lack of standardization.
1. Linking C++ binaries is a hard problemThe lack of standardization is the root of a huge variety of problems related to building C++ code. It starts with no standardized ABI, meaning that even different versions of the same compiler, or slightly different compiler flags, lead to inherently incompatible binaries. This is a source of link, load and runtime errors on one hand, and a tedious exercise on the other. In many cases, the safest approach in 2019 is still "build the whole chain yourself".
The sheer existence of "header-only libraries" is a good proof of the status quo -- it's often such a pain to link libraries that library authors deliberately accept the disadvantage of repeatedly recompiling the same code just because it's much easier. No other language has this phenomenon, not even C. Ironically, linking C is comparably easy, because you have a standard ABI. If it weren't, then not every single programming language on this planet would have its own C binding.
2. All tooling is externalC++ has a slow standardization process and a tendency to not include features that are not considered "core enough" from the standard. The language specification does not make any statements about tooling, apart from the compiler itself. Compare this to languages like Go, which include a package manager, a build system, a documentation system (Godoc),
even a formatter to enforce one code style out of the box.
Since C++ does not ship any of those, external providers emerge. And because different people have different preferences, there's not just one build system, one package manager and one documenting tool, but dozens. Dozens of each. Eventually a few ones become popular, but libraries are still partitioned, and if I have a huge project based on many libraries, I either have to fight with multiple toolchains or rewrite a lot myself.
3. There are 3000 conventions, for everythingThe standard library went with a controversial snake_case naming and very limited feature set, effectively putting no effort in encouraging anyone except Boost to follow. 20 years later, every other library uses different naming, different way of namespacing, different string/network/filesystem/thread implementations. The C++ community is truly exceptional in the number of fundamental features that have been re-invented. While this alone is not making the
linking per se difficult, it's making interoperability between libraries hard.
The same phenomenon is spreading over to library building: some use -d suffix for debug, some -debug, some nothing at all; some provide x86 and x64 builds with the same name, others have a proprietary naming scheme; some use semver, some not, other things like DLLs have no proper versioning at all. This is not exactly helping the situation.
It is near-impossible to cover all those conventions in a build system and make everyone happy. If there were standard ways on how to name, version and depend on third-party libraries, a lot of it could be automated. But there are not, so you see people write the same
if(debug) link(this) until the end of time.
So, is CMake the right tool for the job?
On one hand,
CMake is the epitome of Not-Invented-Here. It's already hard to grasp why CMake had to reinvent an entire programming language just for a meta-build system, but it's mindblowing that they managed to come up with a syntax far from any common language, instead competing with bash in WTF moments.
But, in all this, they did a decent job at keeping the syntax very simple and consistent (everything is basically a function call with space-separated arguments). And once you get the basic principles, you can limit the time wasted on looking up how to do string operations.
Also, CMake is arguably the most powerful and prominent meta-build system in the C++ universe, being around for quite some time but still actively improving. Even if it has its problems, it's usually better for the community when the majority of projects are using one system and not 22. Fighting with CMake-specific problems (which are mostly solvable, also given its huge community) is a lot easier and more scalable than maintaining separate toolchains.
Keep in mind that CMake is a
meta-build system and as such nowhere near an end-to-end solution. It solves the major task of linking C++ libraries and producing the build system input of your choice (Makefiles, VS solutions, etc.). However, package management, dependency management, documentation and easy distribution are -- to this day -- not solved in a satisfactory way. This is the same in other build systems.
Alas, this is one of the major drawbacks of C++ nowadays, and everyone who has worked with pip/npm/Composer/Cargo before -- where pulling a dependency from the Internet is one command -- will be left with many unanswered questions.