Show HN: Minimal build system using just /bin/sh
notabug.org
notabug.org
No, this code uses functions, which didn't exist in the original Bourne shell. They were introduced only in 1984.
People like to bash the autotools, but they exist for exactly this reason - they already spent decades working through all these gritty details to ensure the scripts generated are portable. To paraphrase Spencer "Those who do not understand autotools are doomed to re-invent them, poorly."
Debian dash deems 3 POSIX extensions basic enough to be in /bin/sh -- including `local`. https://www.debian.org/doc/debian-policy/ch-files.html#s-scr...
I don't care about some spherical-cow ideal of portability. It has its own costs -- like autotools. This is why OP makes no mention of POSIX or portability.
(This exchange regurgitates https://www.reddit.com/r/tinycode/comments/6md2l8/make_gmake..., albeit with more mutual sneering.)
$ /bin/sh -c "fail() { local f; }; fail"
/bin/sh[1]: local: not found [No such file or directory]
$ /bin/sh --version
version sh (AT&T Research) 93t+ 2010-03-05
$ uname -rsv
SunOS 5.11 joyent_20160721T174127Z
It wouldn't work on Solaris 9 and older either as the original /bin/sh also doesn't support "local", but as those releases are EOL it's fine to discount them.Whether you regard these platforms as extant depends on your point of view. Most people wouldn't, and that's fine. We'd naturally disagree ;)
$/bin/sh -c "fail() { local f; }; fail"
$
OpenBSD's /bin/sh defines several aliases as standard. $alias local
local=typeset
$
M. Agaram has stated elsewhere that xe was using OpenBSD sh as a poor man's POSIX conformance test.Who's that?
So if you've never tested your program on a platform where /bin/sh doesn't understand local, feel free to use local in the build script. Bashisms like local are safer anyway. Variable scoping is good. (Though I guess the author's claim of it working in a 70s era Bourne shell is still wrong.)
That's true, but in my case it turned out to be insignificant. While changing all the autoconf junk in > 100 plugins could move the total build time from 50 minutes down to 40, I'd need an order of magnitude decrease in build time to warrant the work. I say order of magnitude because its only at a 5 minute build time that the entire dev process would see a paradigm shift-- at that point I could, with a straight face, stop shipping Linux binaries and just suggest that users compile the software themselves. (Of course that's not great usability for a whole class of Ubuntu users, but at build time == 5 minutes some other dev could easily come along and package things up with ease.)
Also, the extra wasted time under Windows isn't the fault of autoconf. Try doing `git any_subcommand` in msys2 and you'll get latency so high you'd think it were a practical joke.
Edit: added parenthetical
if it doesn't just magically work, you're largely sol.
sometimes you can put in a basic build in its place, or find some environment variables to fiddle with. but usually..
He may have used functions to keep this to a single file and I doubt his 1977 comment (now changed to "30 years") was to be taken literally.
Anyone aware of the overzealous use of more complex build systems in situations where they are not necessary would understand his point. It is possible to build portable software without autoconf and makefiles.
Example: http://nacl.cr.yp.to
No, you have to realize you're working on one side of the graph (source code), but your build produces something on the other side (your artifact). Without automatic dependency resolution of you DAG, you're constantly traversing from one side to the other, making the build 'work'.
- Parallel downloading / uploading of artifacts
- Multi-step transformation (old-style: lexer to C to Object file to linker to deployment). You can turn this around, but generally you want each output of your lexer to be input to the C compiler and so on.
- Testing (especially brute-force testing might take time)
And in modern situations you have the same, with Typescript => Javascript => Minimisation => Combination => Deployment (roughly).
It really helps to think of this as a dependency chain and use a partial-order planner to find a possible linearization and execute.
Alternatives are not replacements.
A build-script without dependency resolution clearly does not scale. It was the reason to design and implement Make, CMake, Maven, SBT and many others.
Some systems don't need to scale. Make and SBT etc. have an impedance mismatch with some projects due to their learning curve and the current sophistication of the development team.
The most important feature of any development tool is not whether it is best practice, it is whether it works. For example, Tarn Adams doesn't use version control for Dwarf Fortress. [1] That doesn't mean Microsoft should have switched the Windows codebase to zip files instead of Git any more than a single developer should start boiling the small lake of SBT just because it is better.
Sometimes, it is easier and faster and better to just build the tool that is needed. And of course sometimes it isn't.
[1]: https://www.reddit.com/r/IAmA/comments/1avszc/im_tarn_adams_...
To my knowledge, today every Unix system has Python, usually at least 2.7, if not Python 3. Some even replaced most of their system shell scripts with Python scripts. (Formerly, Perl had this status, but never really caught on in that area.)
Python is a much cleaner language than any shell language, especially with regard to error handling. (yes, I'm aware of "set -eu") Also, if you occationally need some more elaborated data structures or algorithms, you can express these in a straight forward way, rather than a series of complicated (and usually very fragile) text processing steps.
Array is the only one that I have found lacking, in my experience.
You can find a nice explanation in the "tup" build tool, although there are of course many others: http://gittup.org/tup/
And yes, you can express DAGs with text and/or filesystem operations (plus symlinks), but not efficiently and writing code that operates on that level is cumbersome.
Is there any similar build system? I know of https://github.com/droundy/fac, but it's not cross-platform
1) reasonably easy to use 2) very fast 3) very correct
Most build systems fail 3), usually 2) and often also 1).
$ cat > f
a c
a d
b c
^D
$ tsort f
a
b
d
cLook at how a modern build system (like qbs) organizes the build. It builds a declarative object graph describing the project and maps that to a dependency graph where each node can be a relatively complex object ("compile C++ file X using flags Y" or "link LargeSetOfFiles") annotated with modification data (like input file hashes). (Then it compares the dependency graph to what is found on the system and creates a list of commands to execute in order to bring whatever is on the system to the desired state, i.e. create an up to date build).
https://news.ycombinator.com/item?id=15043633
debug/release, internationalized builds, coverage builds, profile-directed feedback, running parameterized test suites, etc.
$ docker run --rm -ti alpine sh
/ # bash
sh: bash: not found
/ # python
sh: python: not found
/ # perl
sh: perl: not found
/ # ruby
sh: ruby: not found
Sure, you can install all of these with a single `apk add`, but it makes your container much fatter, i.e. every CI job that has to pull the image will be taking a longer time. Same for when you push the image into a far-away datacenter.Just use make. It's not hard and it works even for fairly large projects.
Outside of parallel builds, however, I actually prefer an explicit sequence of steps to so-called "declarative syntax". The frequency with which I find bugs in the dependency graph of `Makefile`s [1][2][3] suggests that people are starting from a sequence of commands anyway. And it's better for the reader as well to see the commands laid out in order, when they need to debug breakage on their setup. People should stop pretending to be Vulcans and just write a script for building their project, and then sprinkle in `older_than` directives as an optional optimization. Building from scratch should be the case to optimize for readability, rather than some exceptional situation.
[1] https://github.com/zetavm/zetavm/commit/4e5e2e9d8b
[2] https://github.com/tekknolagi/stackx/commit/5999202423
[3] Fun fact: when you try to build OpenBSD's userland from source, the top-level Makefile always unconditionally calls `make clean`. I've seen this pattern a few times: large projects find themselves up shit creek in the build system, and it's difficult enough to test and debug the dependency graph that they give up all performance and retreat to just recompiling everything, just to be safe.
This is why the more elaborate build systems recognize dependencies automatically. For Makefiles, usually some sub-Makefile is generated automatically and then included. For language-specific build systems like OPAM (OCaml) or Cargo (Rust), this is one of their core features: As a programmer, don't care about maintaining dependency definitions! Whenever a source file is changed, the build system recalculates the relevant part of the dependency graph as well.
Anyway, how does this compare, features-wise, with GNU Make? For example with make you have `./configure` files, do I also have them here?
So there's that.
So yes, eliminating tabs (or rather the execrable requirement of using tabs) was absolutely a prime goal.
[Makefile]
indent_style = tab
And install the plugin, which is available for most editors and IDEs.> If you prefer to prefix your recipes with a character other than tab, you can set the .RECIPEPREFIX variable to an alternate character
That said, how hard is it really to disable tab expansion in your editor? I have a block of configuration dedicated to the different indentation requirements of different programming and configuration languages. In more modern editors, I don't even have to do anything at all to edit Makefiles with tabs - they come pre-configured to do the right thing.
`configure` is not in itself a feature. What purpose does it fulfill for you? Then we can discuss how we may serve that purpose in some more lightweight manner.
Why? Try removing a configure script for even a simple autotools project, and ensure that it still builds fine on all previously supported platforms by writing a cross platform Makefile. Good luck with that.
You're certainly right about that:
http://akkartik.name/post/libraries2
https://lobste.rs/s/to8wpr/configuration_files_are_canary_wa...
:)
"This works in some cases, but certainly not all of them."
This too I'm totally willing to accept. I believe most abstractions are prematurely frozen and so poorly designed. But this makes me liable to err too far in the other direction.
"configure is one of them. Why? Try removing a configure script for even a simple autotools project, and ensure that it still builds fine on all previously supported platforms by writing a cross platform Makefile. Good luck with that."
Here we part ways. I think `configure` is one of the easiest cases for me to be sure of, and it's because I don't care about "all previously supported platforms". Many if not all of them are obsolete. Also, as rossy pointed out elsewhere in this thread (https://news.ycombinator.com/item?id=15045076), it's impossible to know today if there's a bug in autotools for some rare platform.
We live in a world very different from the one autotools were built for, and it's almost a monoculture at this point. All that crap is utterly unnecessary today. I don't care about building software on all possible platforms, all I care about is building it on this one platform in front of me right now.
The most frustrating thing about autotools is that it's unclear which platform each individual check is concerned with. I have a whole mess of rules from 20 years ago -- and zero rationale for any of the rules. That makes even the few rules that are worth having a net liability because I can't separate them from all the useless ones.
The concept of `./configure` is 100% orthogonal to Makefiles. You can have Makefiles without `./configure`, and you can have `./configure` that outputs something other than a Makefile. For example, I could easily imagine a `./configure` that prints a shell script like the one in the submission.
No, it's not. This isn't under a free or open source license. The lack of license means that only an idiot would use it for anything.