~skeeto/public-inbox

1

cmake-init review and solutions

Details
Message ID
<dee65d6f-6ae9-481c-aa02-d41c37a2e945@waifu.club>
Sender timestamp
1737495245
DKIM signature
pass
Download raw message
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.
Details
Message ID
<20250122161233.eb7sjej74zt4yjvi@nullprogram.com>
In-Reply-To
<dee65d6f-6ae9-481c-aa02-d41c37a2e945@waifu.club> (view parent)
Sender timestamp
1737544353
DKIM signature
missing
Download raw message
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.
Reply to thread Export thread (mbox)