Wed 27 March 2024

☔️ Sane C++ March 24

SC

Welcome to the third monthly update for Sane C++ Libraries!

📄 Sane C++ Libraries Documentation
💻 Sane C++ Libraries Repository
📹 Sane Coding Youtube channel
💬 Sane Coding Discord

SC::Tools#

written for Sane C++ Libraries, using Sane C++ Libraries

There is no better way to improve a library than using it to create some other project or tool.
This is why effort has been spent using Sane C++ Libraries to build some useful tools needed by...Sane C++ Libraries!

So, unsurprisingly, the big addition of the month to Sane C++ Libraries are SC::Tools!

Something similar to SC::Tools already used to exist in some form: SCBuild.cpp, the bootstrap from source C++ file using SC::Build build system (generator).
It used to work thanks to a bootstrap bash / batch file that compiles SC-Build.cpp and the unity build file SC.cpp to produce the SCBuild[.exe] self-contained (statically linked) executable.
This was nice but it had a few issues, for example it was not able to detect changes in any of the source files and rebuild the executable accordingly.

SC::Tools is a generalization of the SCBuild.{sh | bat} mechanism into a more flexible SC.sh $TOOL [$ACTION]. This allows also running custom tools, leveraging Sane C++ Libraries, by just launching SC.sh myDirectory/myScript.cpp [$ACTION]!

The source code dependency tracking is taken care of by a couple of parametric makefiles.
The intermediate files directory is shared by all tools, and the unity build file SC.cpp (#include-d by Tools.cpp ) makes the entire set of Sane C++ Libraries available to every tool.
It's compiled just once (in a couple of seconds), making any successive compile / linking of the tools extremely fast (less than 1 sec on a recent laptop).

Tools in theTools subdirectory are special, as they can be invoked by just using their name (without the SC- prefix).
./SC.sh Tools/SC-build.cpp can be invoked as just ./SC.sh build.

SC-build.cpp#

building C++ programs in C++, why not?

The first tool is just called SC-build.cpp.
It comes with a companion SC-build.h file holding code shared between multiple projects.
Its default action (configure) is equivalent to the (now old) SCBuild.sh, that generates build/project files.

The new action called compile (guess what?) compiles the generated projects 🤯!

./SC.sh Tools/SC-build.cpp configure
or
./SC.sh build configure

and on windows:

SC.bat Tools\SC-build.cpp compile
or
SC.bat build compile

SC-package.cpp#

wheel reinvention, reinvented

The second tool is SC-package.cpp, once again a manifestation of the wheel reinvention attitude already pervading the entire project 😊.

This poor-man's imperative package manager downloads binary files taking care of checking MD5, extracting and testing what's contained.
The guiding reason for this tool to exist was to automatically be able downloading and extracting the officialclang distribution (at a specific version, matching the Gitlab CI) in order to use clang-format on the entire repo.
This is needed for a potential contributor to properly format files before sending a Pull Request, keeping the CI format validation jobs happy.

The end result is a folder with _PackagesCache and _Packages under _Build.

This is still very primitive for now, but who knows, maybe in the future it could be evolved into something more advanced.

Additional reasons to exist#

other than wheel re-invention

Too often repository automation tools are written inbash or batch where best thing you can do is putting print (echo) statements to debug them.
Slightly better are the cases when an actual programming language like python or javascript is used, but sometimes this comes with an entire set of problems regarding their requirements and in general the fact that such tools expect to be installed globally on developer's system, with probability of incurring into a it works on my machine situation.
Things like Python .venv can sometimes help but not always.
In some cases such scripts do not even exist, but you're getting a bunch of install this or install that Readmes always missing some step and where you end up wasting a lot of time.
And let's not discuss docker about please, I am trying to stay nice 😄.

So my obvious solution has been to write such a tool in C++ using Sane C++ Libraries of course, so that I can debug it properly just like any other program.
I am really happy the result, the small program is easy to follow and does its job.
For now all data is local to the repository _Build directory but it would be easy sharing the _PackagesCache into some custom user directory to save some space on multiple checked-out copies of the repository.
For any issue, I can just debug this C++ shell script, that is just a regular program, like all other programs that a C++ programmer debugs every day.

Technical Details#

if you really want to know...

On Posix to getclang+llvm-x.y.z-$ARCH-$PLATFORM you just download a file from github, check it MD5, and extract the .tar.gz file.

On Windows this is even more difficult because the official LLVM binary distribution is some sort of self-extracting setup (requiring admin privileges too!) and to extract it you need a tool like 7zip. 7zip is as well a self extracting installer and requires another tool (called 7zr.exe) in order to be extracted!. So on Windows SC-package downloads 7zr.exe -> extracts the 7zip installer -> uses the extracted 7z.exe to extract the LLVM-X.Y.Z-$ARCH into some directory. If someone knows an easier way of doing this (without installing other tools or requiring a package manger) please let me know.

On all platforms / architectures (macOS/Windows/Linux and ARM64/Intel64) MD5 of the downloaded files it's being checked to make sure they're all legit. This also helps creating a _PackagesCache to hold these setup files and avoid re-downloading them every time.

Also a small clang-format test is run to make sure it's properly installed and available. The extracted directory is symlinked into a _Packages sub-folder containing package name / platform.

Sane C++ Libraries doesn't have (yet) a properly working HTTPS client, so an alternative solution was necessary. It looks like nowadayscurl is pre-installed on any recent macOS,linux andwindows install and so SC-package uses it.

SC::Hashing library has been used to compute all MD5 easily getting the job done.
File operations and cross-platform path handling have been implemented using the SC::FileSystem library.

SC-format.cpp#

code is poetry, prove me wrong

SC-format.cpp runs clang-format on all files of the repository, with some exclusions, to beautify source code.

It launches many parallel processes but always dynamically limiting them to the number of available processors using SC::Process. Processes are monitored using the SC::Async Library, and as soon as one "slot" is made available, it's being filled with a new clang-format process.

Most code has upper bounds and fixed buffers.
Even if it's totally unnecessary for such a small script, it's cool to see how the libraries work together to allow minimal (or no) heap allocation!
In this example the number of maximum processes is fixed and the recursive file system iteration happens with a lambda, avoiding storing a vector of all file paths found in the target directory.

The tool looks for clang-format in known places, depending on platform, and if a working clang-format at the correct version is found, it will be used. This is for example what happens on CI as clang-format is pre-installed on Github CI OS Images. If the no suitable clang-format has been found, then SC-format.cpp relies on SC-package.cpp to download a brand new set of LLVM binaries from github, extracting and symlinking them.

Improvements to the CI and .vscode files#

All these tools are now making the CI scripts and the .VSCode files a lot nicer.

These are the new simplified CI and VSCode build scripts:

Improvements to the Library#

As expected, the act of building this simple set of tools, has uncovered issues, bugs and limitations in Sane C++ Libraries.
Of course they've been promptly fixed, but this shows, once again, that developing libraries in a vacuum doesn't generate strong enough code 🙂.
Hopefully more developers will start using the libraries, reporting issues or providing bug-reports and maybe even Pull Requests!

SC::Process

The SC::Process class has been pretty crucial in implementing the tools.

The original interface was not so easy to use so it got a nice facelift:

It also got some more features:

Improved Tests and Documentation:

and bugfixes for very tricky OS-specific issues!

SC::Build#

As already said the SC::Build library got the capability of compiling specific configuration / architecture combos.

This has been also the occasion to cleanup the intermediates / output folder naming, so that all folders can coexist in the same working directory. This is useful to map that very same working directory on all Operating Systems (with a couple of Virtual Machines), so that code can be quickly tested on all platforms while it's being developed, before even hitting the CI.

Features:

SC::Async#

The SC::Async library got some fixes in the child process exit handling, that was randomly failing on theSC-format command!

Features:

Bugfixes:

SC::FileSystem#

SC::FileSystem got some additions needed mainly by SC-package and a bugfix found when developing the tool.

Features:

Bugfixes:

SC::Strings#

SC::Strings got a series of improvements identified when implementing all the tools.

Features:

Bugfixes: