Mk: A Successor to Make (1987) [pdf]
doc.cat-v.org
doc.cat-v.org
I'm not criticizing anyone or any tool, just that we can't seem to find some good way to tool the compilation of both trivial and very complex projects, without taking an hour or two off.
I also understand that I do not want to work on such an endeavor, that seems boring AF and kudos for people that like that. You are not enough it seem :)
Unfortunately, mk inherits from rc the principle of having the list of strings as the fundamental datatype (also used by Jam). That works, but it’s noticeably more limiting than Tcl’s route of having everything be strings but with robust quoting and unquoting procedures for putting lists inside them—at which point Tcl starts to look like a Lisp-2 with a slight propensity for stringiness and a mildly unusual syntax.
I always saw Tcl as a bastard child of Lisp and sh. It's beautiful in its own quirky way.
Pehaps this is misleading.
So rc was made for Plan 9. The examples in that paper are definitely on tenth edition Unix, I think this would be because
A fairly complete version of Plan 9 was built in 1987 and 1988, but development was abandoned. In May of 1989 work was begun on a completely new system
from https://doc.cat-v.org/plan_9/1st_edition/designing_plan_9 (1990).
The mk paper (and Unix man page) doesn't mention rc.
Plan 9 existed since the mid-80s.
This sentence in the Unix rc(1) sure makes it seem like rc was Plan 9-first:
Environment entries for variables are kludgy for UNIX compatibility.
For an excellent synthesis of what Makes a build system, I can't recommend the 2018 paper "Build Systems A La Carte" enough:
https://www.microsoft.com/en-us/research/uploads/prod/2018/0...
By Andrey Mokhov, Neil Mitchell (now working at Meta on the Buck2 build system) and Simon Peyton Jones (one of the founders of Haskell)
True in the broadest sense, but there are choices to be done regarding the possibility of discovering what the graph is or has become on the fly and the propagation directions. See “Build systems à la carte”[1,2] for a systematic exploration.
(See also a neighbouring comment[3] re how the discourse structure[4] of the build script might be important in a way orthogonal to these execution-engine issues. The boundary between the build system and the build tool proper can be drawn in very different places here.)
[1] https://dx.doi.org/10.1145/3236774
[2] https://youtu.be/BQVT6wiwCxM
[3] https://news.ycombinator.com/item?id=36749885
[4] https://brenocon.com/blog/2009/09/dont-mawk-awk-the-fastest-...
Note that Nix doesn't use a shell to execute things, it uses raw `exec` calls (each .drv file specifies the absolute path to an executable, a list of argument strings, and a set of environment variable strings). Though in practice, most .drv files specify a bash executable ;)
Yeah, well, it's a little bit more than that.
Two things come to mind that don't neatly fit the DAG mental framework:
- dynamically generated dependencies (e.g. when you compile a C++ file only to discover that it #includes something and therefore has a dependency on that thing, and therefore the DAG has to be updated on the fly). Creating them by hand is horribly tedious, and/or borderline impossible (#includes that #include other #include ad infinitum)
- reproducible builds, where a build system is capable of rebuilding a binary from scratch down to having not a single different bit in the final output assuming the leaves of the DAG haven't changed. A desirable feature that is darn near impossible to do unless you pair the DAG with something else.- defining different build types (debug/release)
- optionally building and running tests
- incremental builds (detecting what has changed)
That doesn't necessarily run counter to the concept of a DAG, but the organizational structures to manage this is what makes the build system. Topologically sorting the dependencies isn't the hard part. That's why make isn't a build system. It is the generic DAG runner, but that's not sufficient.
There is: tsort. It's a POSIX utility, even, not just a GNU or BSD utility.
0001 GOTO 0002
0002 GOTO 0001
How would that be a DAG?V: (0001, 0002) E: ([0001, 0002], [0002, 0001])
But really I was just being sarcastic. Builds are "just" DAGs with syntactic sugar just like programs are "just" DAGs with syntactic sugar. That's one possible abstraction and one that is inherently lossy, because what "sugar" is available is the actual meat that makes one build system useful and another terrible. And one actually awful limitation is acylicity, since any build system will eventually deal with it (and outright forbidding of it makes your build system incapable of representing certain builds, just like acyclicity fundamentally limits a program's execution)
You can represent pretty much any data model as a graph, that doesn't really address the problem.
A build system modelling itself as a DAG, on the other hand, is very consciously taking on acyclicity as a feature.
I am curious what builds would need to be cyclic. Artifact A is built from B and C, where C is built from D and A? Does that happen in any well-designed build?
> I am curious what builds would need to be cyclic.
Compilers, interpreters, kernels, and the things that depend on them. So lots.
> Does that happen in any well-designed build?
Whether or not it's 'well designed' is irrelevant, the question is 'does it need to be possible' because even if it's rare it's still present in a lot of foundational software, notably GCC.
(begin
(0001 GOTO 0002)
(0002 GOTO 0001))
The DAG has two branches, each with three leaves. There are no cycles in the syntax, even if there is in the execution.I used this tutorial:
https://www.usenix.org/conference/atc22/presentation/curtsin...
(full disclosure: I am one of the authors)
> Make sure your source files are backed up, like in source control, or something. Tup is able to delete old files automatically, though it tries to prevent you from doing silly things like overwrite your hand-written C files. Still, it would suck if you got boned because tup has a bug or something. Hey, your hard disk can go at anytime, too.
This is unacceptable, no thanks, I build first (test) and then commit, not the other way around.
> just has a ton of useful features, and many improvements over make:
> • just is a command runner, not a build system, so it avoids much of make's complexity and idiosyncrasies. No need for .PHONY recipes!
It makes it easier to come back to a project after a few weeks, because you don't have to remember the N commands you were using to iterate/test, you only have to remember the 'just' invocation.
For some reason, it feels like a more natural fit for this than 'make'
https://github.com/9fans/plan9port/blob/cc4571fec67407652b03...
With sub-second build times for individual targets, this causes mk to needlessly recompile files because the target may have the same mtime as the prerequisites.
There's a solid, stand-alone implementation of mk in golang. No plan9 environment needed.
https://github.com/adamgordonbell/job-runner/blob/main/tests...
Which meant they cared less about "winning" in some non-valuable game of "whose code is winning" and had less irritating conversations, "no thats not how it works, no I don't have to explain this idea called the plumber to you, yes rc is very different to other shells no thats not how it works"... ad infinitum.