SCons is not slow
github.com
github.com
When we used it at MongoDB, somebody (thanks Mathias!) put together apparatus to make it generate a ninja config instead of building by itself. That was fast! Anyway, after you had the ninja config.
But the edit-build-test cycle did not include a slow SCons run anymore, and that was huge.
The MongoDB internal wiki had a page titled "SCons Isn't That Slow", with advice for how to live with how slow it really was, later including ("unofficial") ninja and ccache setup. (Which I added.)
But at least it wasn't WAF.
Things I used SCons on were different from the things I used Waf on. SCons might need as much custom Python for those other uses as Waf. But that does not make Waf any better.
To ne the overriding criterion for a build system is that it must not demand attention. Waf did. SCons seemed not to.
And, at this point, its time has passed.
The big projects that were using scons all moved on to other systems for various reasons.
Better to move on to something that actually might generate a critical mass and community.
To be clear: SCons is not notably worse at describing what must be done to build a big project than other systems of its time. Lots of code has been compiled under it. Its deep failure has been in trying to direct the activities of compilations in real time, at which it is bad, gmake is good, and ninja positively excels.
Still, slowness in resolving dependencies is hard to defend. It is a problem in operations long well-understood, with familiar algorithms. A data structure well tuned to the algorithm would make even Python able to solve it quickly enough.
Nowadays the difficult part of a build system is the complexities of managing foreign dependencies, at which CMake is only just adequate.
20% is a pretty huge margin for large builds, which I wouldn't consider using make for in the first place. CMake + Ninja has shaved enormous amounts of time off my builds from make, so why should I use something that's even slower?
(Disclaimer: I'm not involved in the cmake project but I often see people struggling with it because of misuse, possibly because of expectations that it will work exactly like autotools)
Now that because work forces me to develop on a locked down windows box, make is the last thing I want.
A very, very fast interpreter, with truly admirable process-management skills.
https://github.com/apple/swift-llbuild
llbuild is a set of libraries for building build systems. Unlike most build system projects which focus on the syntax for describing the build, llbuild is designed around a reusable, flexible, and scalable general purpose build engine capable of solving many "build system"-like problems. The project also includes additional libraries on top of that engine which provide support for constructing bespoke build systems (like swift build) or for building from Ninja manifests.
I've been using CMake for over a decade and the only time editing CMake files was not straight-forward was when whoever wrote them had no idea of what they were doing and were trying to crowbar a round peg into a square hole.
With "modern cmake", which is modern in name only, everything is declarative, modularized, and straight-forward. You just specify targets, target properties, and that's it. I have absolutely no idea of where all the drama comes from.
I guess these are problems with all builds systems, but cmake feels like it’s hiding everything behind macros and it’s really difficult to understand what they are actually doing under the hood sometimes.
IIUC, the 20% slowdown happens with a build that does nothing, it's just the scons overhead. (Not sure if they replaced the compiler invocations with echo, or if they touched the result files right from Python, for this number. They mentioned both, but I don't remember which one of them resulted in the 20% figure.)
The 20% figure should go down for real world builds.
As much as I don't like programming golang, they did get compilation right. And Scons/ninja/automake/cmake issues just went away.
In C/C++ that means you decided to change 400 translation units.
That's not a problem with the build system at all. That's a problem with how the developers working on the project failed to follow basic best-practices in software development. No build system saves you from the mess you create yourself.
That's the whole point of performance measurement for build systems. The overhead is precisely what counts here.
If you have many source files to recompile, the fraction of time spent in the build system itself is going to quickly drop to almost zero : build-system stuff is generally several orders of magnitude faster than compiler stuff.
So it would make little sense to compare build-system performance on full builds.
But it's more than that, a good build system does not need to be Turing complete, and having Turing completeness imposes certain requirements on it that may make it inherently slower.
When setting up a build system for multiple people to use, it's important to agree on a general approach and some rules to follow in order to keep the system from turning into a mess. It's code like anything else, and benefits from training, practice and discipline. Using a language that most folk already know is almost always better than using an obscure one out of a sense that the obscurity makes it safer.
When building up a build system, I suggest focusing on designing abstractions to make 90% of use cases trivial for other developers to use. Spitting out libraries and linking them into binaries, existing autocoding scripts, etc. The other 10% of cases that other folk run into, loudly volunteer to make yourself available to help them work through it. Most of the time, there's a way to do what they need that isn't horrendous, or you can take the opportunity to improve your abstractions.
If your problem is that other developers you work with just go in with a hatchet and make a mess without informing anyone, perhaps it's time to adobt some processes to address that, such as better version control hygiene and code reviews.
And that's the likely reason why scons projects become cryptic to write, impossible to debug, and unmanageable in general.
The page in question is actually quite old at this point.
Since the content was written Ninja, Cmake, Bazel, and other build systems have launched and become mature.
We are slowly working through the easiest of the list of known performance issues.
In addition MongoDB has contributed their Ninja file output logic. We expect to integrate that in the next major release.
Projects like MongoDB, Godot, VMware, PlatformIO, and many others use SCons. One of it's strengths is the ability to correctly build complex builds.
Please engage with the community our users mailing list, IRC Channel, or discord server if you're a user.
One frustration for the project is that users run into issues using SCons and never reach out and ask for help. Many times a small change in usage will resolve whatever has a user stuck.
That said, I hope waf and scons die off soon (only a handful of packages even use them). CMake and Meson are much better choices.
I haven’t tried Meson, but CMake cant really do much besides compile the programming languages it explicitly supports. Automation tasks are pretty much impossible.
I'm mainly coming at it from the perspective of a distro maintainer. Few pieces of software use waf, and digging into the wscripts can be a pain for large projects. Only having to know a half dozen build systems is way easier than having to know waf, Scons, meson, autotools, make, setuptools, cargo, qmake, cmake is a pain.
Only 25 packages in the distro I contribute to use waf. Most are audio recording tools that do not get much maintenance. 20 packages use scons. Meanwhile almost 400 use meson, ~500 use qmake, ~1000 use cmake, and over 2000 use autotools.
Not currently, but I do have a half-written article I plan to publish eventually once I complete this project I'm working on, to showcase my use of waf and how it's helped me. That's how much I like it!
But to give you a quick example: my project (a cross-platform game) has a bunch of assets that I am embedding in the executable. To do this, I wrote a custom waf task that automatically generates a char array in a .cpp/.h bundle, and adds it to my build and include paths.
Using that same tool, I am able to embed other assets that need to be transformed in various ways before being embedded. One of those is a bundle of binary files and meta-data combined together inside a single flatbuffer data blob. This involves compiling the schema, generating a small command-line utility to write the blob using the sources generated from the schema compiler, using that utility to generate the blob, and finally embedding the blob using the asset embedding tool. (Plus some of the binary files themselves need to be processed before all of this happens)
I could do that with shell scripts, but waf's design makes it easy to do, in a small amount of reusable code (everything I just described I implemented in <400 lines of python), and in a general-purpose and easy to read programming language. Plus the tasks automatically execute in parallel wherever possible.
> I'm mainly coming at it from the perspective of a distro maintainer. Few pieces of software use waf, and digging into the wscripts can be a pain for large projects.
I've heard that complaint before, and I can see why it would be annoying for outside maintainers. Waf's customizability means two Waf projects might look nothing alike. So even if one becomes an expert at Waf (which is not very easy considering how bad the docs are), they might be completely lost in someone else's project. I failed a game jam once because I was using Amazon Lumberyard (which uses waf), and even though I completed the game, I couldn't figure out how to compile the damn thing before the deadline.
But if it's any consolation, I don't plan on distributing my project through any linux distros :P
Why not? In the old days, it was "./configure; make". Today, it's much less certain what build system(s) you'll have to deal with when building a FOSS tool. That's not a problem when everything works, but as experience shows, more often than not you'll run into issues and then I would say it's nice if you are familiar with the system.
And just like with other tools it's nice if everybody uses the same thing. Github wouldn't exist if everybody rolled their own version control system, for example.
It also wouldn't exist if no one did. It's all about compromises. Having too many tools for the same job isn't good, but monoculture isn't either.
Sadly when it comes to C++ I don't think we'll get a do-over. Too many build systems exist already, and important libraries from a decade or two ago will use those systems until the end of time, and so will the projects that depend on them indirectly.
Core primitives, like 'a file is out of date iff its modification time or hash has changed', should be expressible 'in userspace', as it were—the core of the build system shouldn't know about them. (Though it should have some concept of a target being up-to-date and not being rebuilt, it shouldn't need to know about files.) This, combined with the ability to create proper abstractions, means you can emulate the primitives of all existing build systems and give them a common substrate.
(Bonus: assuming the core primitives are done right, you can still get performance as good as ninja. Which is good, because you won't be able to compile to a build system like ninja.)
Shake is written in Haskell as a library (so your build rules have to be written in Haskell too, as programs) but you could either wire up a language via the FFI to make some classes of build rules easily expressible in JavaScript/Lua, or in theory rewrite the core of Shake in another language too. The theory is all there, though.
My personal rule of thumb is: use a "specific" build system if possible, but use Shake the second any build system requires even a tiny bit of complexity (multi language builds, requires parsing or other "advanced" features, etc). As a bonus it's also very fast, within a few % of Ninja when parsing Ninja files, and sometimes faster than that. I was surprised when it came in as 2x as fast as Ninja on a large-ish C codebase at work, due to having picked a different "build schedule" that distributed compilations across cores better.
Systems evolve. You don't want to rewrite your build scripts the moment you cross the complexity line.
Your mention of Shake is nice though. However, I'm not sure if it could gain widespread adoption, since it's based on Haskell which is not very popular unfortunately (and has a bad name when it comes to learning curve).
> A target is a file we want the build system to produce
This is somewhat arbitrary, and shouldn't be mandated by the build system. You say 'the secrets are that you need a more abstract concept of "targets" or "dependencies" that aren't just files', but that doesn't seem to be corroborated?
They do show examples of 'phony' targets, but why not simply eliminate the conflation of build targets and files entirely? Phony targets are just a holdover from make.
______________________________________________________
The other reason I would prefer lisp (over haskell) is the lack of 'compile time'. It looks like, with shake, you have to recompile the build file every time you change it? With lisp, loading a file will be pretty much instantaneous.There's very few build systems that actually support this in a reproducible and hermetic way.
Thankfully we switched to ninja.
For context, I don't like autotools.
I like python a lot better than cmake's syntax. It's nice to be able to define functions and importable modules to abstract away the ugly details of the build so that other developers can safely use them without being build system experts.
Bazel is cool, but it's relatively immature. Last time I looked into it, you couldn't just install it easily. It's easy enough to make scons look like bazel. I suspect bazel handles very large codebases more efficiently.
SCons is bad, in that it is slow, slow, slow, slow. If your time is worth nothing, it can in fact be made to do things. But other alternatives can, too, and faster.
I can't say anything against tup other than it is not widely used. In terms of performance, I did not notice any difference between tup and ninja -- but these were small projects.
If you’re into Tup, you might also like https://github.com/droundy/fac
Fun fact: ninja was inspired by Tup, but is a bit more geared toward pragmatic usages.
https://github.com/gittup/tup/issues/394#issuecomment-636378...