Hey Chris!
I happened to be searching cmake-init mentions and found your review here:
https://old.reddit.com/r/C_Programming/comments/1htkf7m/a_framework_for_creating_and_prototyping/m5frazo/
[1] Following the discussion I can see that you are missing the
sanitizer options as the default, however due to the lack of UBSan
support for MSVC I can't really make sanitizers the default if I want to
keep the default developer experience similar across all platforms. CI
is already testing all build configurations anyway and this is actually
something I plan to do once MSVC gains UBSan support:
https://github.com/friendlyanon/cmake-init/issues/81
The project being implemented in Python was due to Python being readily
available on nearly every platform and the support for a single file
distributable in the form of a .pyz file. Originally, this was the only
way I provided releases in fact:
https://github.com/friendlyanon/cmake-init/commit/1d74e5bdbdc30451f80d5b50ad28c26bafb225cf
The .pyz file also made it possible to automatically preserve the
directory structure that I have setup in the "templates" directory. This
saved me from having to map files to paths as you apparently have to do
with Python's builtin asset support, which seems to only support flat
hierarchies.
Curiously, someone else also wanted to run the project directly from
source similar to you despite the above limitations:
https://github.com/friendlyanon/cmake-init/issues/125
"cmake .." works just as well as any other combination of command line
options to configure the project, however its meaning depends on the
current working directory and the named directory. Not ideal, which is
why all the markdown documentations generated contain basic instructions
as well, for both users ("BUILDING.md" is intended for them) and
developers ("HACKING.md" is intended for them) of the generated project.
I'm actually not sure if there is value in fixing everything besides
[1], for which MSVC's UBSan support is a blocker anyway. I wish not to
deviate from platform defaults in more ways than necessary, so I feel I
strike a balance in that regard with what the generated projects provide.
I have considered maybe looking into rewriting things in C23 now that
finally "#embed" is a reality, but that's time I don't really have the
budget for now or in the foreseeable future. Could also be that I don't
see this endeavor that motivational.
I'm curious to hear back.
Thanks for reaching out, Pali.
To clarify, my original criticism was about CMake. By default, out of the
box it produces poor build configurations that leads many, if not most, of
its users astray. The distribution has a long tail, and in the hundreds of
CMake projects I've reviewed, the median project uses no warning flags. I
can turn on the basic warnings and discover bugs without even running the
program. Then when I do run it, sanitizers reveal more bugs. It all could
have been avoided with a decent configuration, something that I expect a
meta-build system to handle.
CMake does essentially nothing to help produce better configurations. The
official tutorial doesn't even introduce warnings until step 5, where the
solution is to manually write the toolchain detection logic and compiler
flags into CMakeLists.txt. That's more work than simply writing your own
build scripts by hand, and so CMake is ultimately negative value.
I had been told that cmake-init solves this, and indeed it seems your goal
is similar, though not necessarily the same. So I'm looking at it through
the lens of "does this tool produce a configuration that would reveal the
easy-to-find bugs that I find in so many CMake projects?" So what is that
configuration? I write about that here:
My favorite C compiler flags during development
https://nullprogram.com/blog/2023/04/29/
For GCC and Clang (GCC driver), the appropriate baseline for development
builds for most users would be:
$ cc -g3 -O0 -Wall -Wextra -fsanitize=address,undefined ...
Maximum debug information. No optimization, not even -Og, because it
interferes too much. ASan when available. If libubsan isn't available,
then put UBSan in trapping mode (-fsanitize-trap). Budging either way on
warnings, such as -Wno-unused-parameter or -Wconversion, is okay, but
anything less than this baseline is neglect, and obvious bugs would be
missed. I've seen it happen so many times. A meta-build system could
detect if ASan and libubsan is available and adjust appropriately.
Sanitizers aren't something to just enable in CI. If you're using them at
all, that's the least valuable place to use them! The later you catch bugs
the more costly they are to fix. Sanitizers are best as part of a workflow
configured to trap (abort_on_error=1, halt_on_error=1) so that bugs will
automatically pause in the debugger during testing. (Not testing through a
debugger? That's More tool neglect!)
Making this a little more complicated, until recently -fsanitize=address
and -fstack-protector-strong were incompatible in GCC (bug #110027). The
latter is enabled by default in Ubuntu. This is will continue to affect
builds for some years to come. So watch out in the "ci-sanitize" preset.
For libstdc++ I might throw in -D_GLIBCXX_DEBUG, except that it changes
the C++ ABI, and a meta-build system couldn't reliably detect if using it
would be appropriate. If wrong, the result is subtly broken programs, too
risky for a flaky test.
For MSVC, though again perhaps budging on some warnings:
$ cl /Z7 /W4 /fsanitize=address ...
The /RTC flags would be like UBSan, but they're too specialized, plus
mutually exclusive with ASan. With clang-cl you can get UBSan, too. In any
case, a meta-build system can detect what's available and use it.
Going back to cmake-init, it's solid on warnings, but relegates sanitizers
to CI. No preset produces a configuration, like my baselines above, which
would be effective for discovering and fixing many common bugs. Someone
like me can waltz into a typical project like this and find defects in a
matter of minutes just by manually using a better configuration. I don't
do this through CMake. I whip up my own build from scratch. It's usually
just a few minutes of work, and that's how I know from experience that
CMake creates negative value.
In other words, the CMake ecosystem sets its users up for failure. (A
title I've actually been considering for a future article.)
> I want to keep the default developer experience similar across all
> platforms.
For me the priority is finding defects as quickly as possible even if some
toolchains and platforms are better than others at doing so. Uniformity is
secondary, or even lower.
> "cmake .." works just as well
Sorry, I see that it does indeed work. I revisited to figure out what I
had done wrong. After I ran cmake-init, I tried the usual:
$ cmake .
Most cases I don't care about out-of-source builds, and I'd prefer not to
deal with it. However, I got "In-source builds are not supported" (which I
know from now on is a cmake-init-ism). Annoying I don't have a choice, but
I continue with the usual dance (roughly):
$ mkdir ../build
$ cd ../build/
$ cmake ../source/
Same message again even though I'm not in the source tree. It says I may
need to delete CMakeCache.txt (one of *the* most irritating things about
CMake generally), but there is no such file in my current directory. I
follow it up with the newer invocation:
$ cd ../source/
$ cmake -B ../build/
That worked, so I figured the older "cmake .." style was broken. I realize
now that there was a stale CMakeCache.txt in the source directory from my
first "cmake ." attempt. It affects "cmake .." but not "cmake -B" (CMake
bug?).
> with Python's builtin asset support, which seems to only support flat
> hierarchies
Presumably you mean "importlib.resources" which is what I had expected to
see but didn't. The Python developers have done a poor job supporting this
module, routinely breaking the API, making it needlessly difficult to use
effectively, but it *is* there.
The first thing to do is rename cmake-init/ to cmake_init/ so that it's
importable. Move the contents of cmake_init.py, which really only exists
as a workaround for the unimportable name, into __main__.py. Add an empty
__init__.py in each template subdirectory. Then change all the zipfile
stuff to importlib. That would be enough that I could run this from the
repository:
$ python -m cmake_init ../path/to/project
Or if I put the repository root in $PYTHONPATH I can run it anywhere:
$ export PYTHONPATH=/path/to/cmake-init
$ python -m cmake_init .
After I saw there were no dependencies, I was *hoping* to run it just like
that in place. No fuss. And this all still works if I zip cmake_init/ into
cmake_init.zip and use it instead of the module directory, which I expect
meets your .pyz goals.
If there were third-party dependencies, I wouldn't have even tried. So I
commend you for that. I frequently pass over interesting-looking projects
when I see they involve downloading and executing literally hundreds (or
even thousands!) of internet rando dependencies. Entire software
ecosystems are rotten like this.
I still stand by my principle: Python is an inappropriate implementation
technology for cmake-init. It inherits Python's deployment problems, that
the user needs a Python installation. As a meta-meta-build system, at
least only the project "lead" runs it. Imagine if Python was required just
to compile a C or C++ program! (That would be crazy and nobody would ever
do that, right? …right?) CPython is a mediocre language implementation,
which reflects on applications written in Python. There's the obvious
impedance mismatch with CMake's purpose. It would be most natural as a C
or C++ program, and to dogfood cmake-init. Users by definition already
have an appropriate compiler and build tool on hand, so they could easily
build such a cmake-init from source.
I appreciate what you're trying to do, improving CMake usage generally. So
much of it can stand to be improved. But for me it still falls short of
solving the problems I see in practice.