[HN Gopher] The Unreasonable Effectiveness of Makefiles ___________________________________________________________________ The Unreasonable Effectiveness of Makefiles Author : srijan4 Score : 163 points Date : 2022-08-12 13:49 UTC (9 hours ago) (HTM) web link (matt-rickard.com) (TXT) w3m dump (matt-rickard.com) | LAC-Tech wrote: | I wanted to learn makefiles to generate a static website. | | Quickly ran into some massive limitations - one of which is that | it completely broke apart when filenames had spaces in them. | | "But why would you do that you're doing it wrong" - don't care | wanted spaces in filenames. | | Ended up switching Rake (a make like DSL written in ruby) and | never looked back. Not only can you do all the make declarative | stuff, but you get the full power of ruby to mess around with | strings. | rightbyte wrote: | Next thing you ask for leading tabs in filenames ... | | And the feature bloat slippery slope is sliding towards Bazel! | Mister_Snuggles wrote: | My most horrible abuse of `make` was to write a batch job runner. | | Most of the targets in the Makefile had a command to kick off the | job and wait for it to finish (this was accomplished with a | Python script since kicking off a job involved telling another | application to run the job) followed by a `touch $@` so that make | would know which jobs it had successfully run. If a process had | dependencies these were declared as you'd expect. | | The other targets in the Makefile lashed those together into | groups of processes, all the way up to individual days and times. | So "monday-9pm" might run "daily-batch", "daily-batch" would have | "daily-batch-part-1" (etc), and each "daily-batch-part-..." would | list individual jobs. | | It was awful. It still is awful because it works so well that | there's been no need to replace it. I keep having dreams of | replacing it, but like they say there's nothing more permanent | than a temporary solution. | | All of this was inspired by someone who replaced the rc scripts | in their init system with a Makefile in order to allow processes | to start in parallel while keeping the dependencies in the right | order. | jbboehr wrote: | > All of this was inspired by someone who replaced the rc | scripts in their init system with a Makefile in order to allow | processes to start in parallel while keeping the dependencies | in the right order. | | Any sufficiently complicated init system contains an ad hoc, | informally-specified, bug-ridden, slow implementation of half | of systemd. | ithkuil wrote: | My most horrible abuse of make was a distributed CI where I put | a wrapper in the MAKE env var so that recursive make executions | would invoke my wrapper which would enqueue jobs for remote | workers to pick up | [deleted] | scott_s wrote: | Congrats! You invented Apache Airflow: | https://airflow.apache.org | Mister_Snuggles wrote: | Interesting: | | > Airflow was started in October 2014 by Maxime Beauchemin at | Airbnb. It was open source from the very first commit and | officially brought under the Airbnb GitHub and announced in | June 2015. | | I believe I starting building my tool somewhere around 2010, | possibly 2011. The core mechanism has been completely | unchanged in that time. If Airflow was a thing at the time, | I'd have hopefully looked into it. I looked at a handful of | similar products and didn't find anything that was a good | fit. | | Based on a really quick skim of the Airflow docs it seems | like it checks all of the boxes. Off the top of my head: | | * LocalExecutor (with some degree of parallelism, assuming | the dependencies are all declared properly) seems to do | exactly what I want. | | * I could write an Operator to handle the interaction with | the system where the processes actually run. The existing | Python script that does this interaction can probably get me | 90% of the way there. Due to the nature of what I'm running, | any job scheduler will have to tell the target system to do a | thing then poll it to wait for the thing to be done. To do | this without any custom code, I could just use BashOperator | to call my existing script. | | * It's written in Python, so the barrier to entry (for me) is | fairly low. | | * Converting the existing Makefile to an Airflow DAG is | likely something that can be done automatically. We | deliberately keep the Makefile very consistent, so a | conversion program can take advantage of that. | | I think my dream of replacing this might have new life! | rhacker wrote: | But airflow is an abomination... that I am forced to use at | my current job. | MonkeyMalarky wrote: | With the added bonus of not having to learn or maintain | Apache Airflow! | cerved wrote: | That's beautiful, not awful | josteink wrote: | > All of this was inspired by someone who replaced the rc | scripts in their init system with a Makefile in order to allow | processes to start in parallel while keeping the dependencies | in the right order. | | Sometimes the most interesting thing is not the story itself, | but the story _behind_ the story. | | This has my interest _peaked_. Is there anywhere else I can | read about this? | pjot wrote: | Just a friendly tip that it's _piqued_ :) | shawabawa3 wrote: | You never know, maybe it is the peak of their interest and | it's all downhill from here | [deleted] | topspin wrote: | My most elaborate use of make was ten years ago to build a | bootable, reproducible embedded system image including the OS, | application, media files and cryptographic signatures. It's still | in active use and no one is complaining. | hyperupcall wrote: | Honestly, I only find Makefiles useful when I have a tiny C/C++ | project and need stuff just to compile quickly and easily without | the overhead of a real build system. | | For literally everything else, I found myself using it more as a | task runner - and Make doesn't do a great job at it. You end up | mixing Bash and Make variables, string interpolation, and it | becomes really messy, really fast. Not to mention the footguns | associated with Make. | | I found bake (https://github.com/hyperupcall/bake) to suit my | needs (disclaimer: I wrote it). It's literally just a Bash script | with all the boilerplate taken care of you - what a task runner | is meant to be imo | evilotto wrote: | The 2 biggest things I find missing in makefiles is the ability | to have the "modified date" of a target be something other than | the mtime of the target file, and for the dependencies of a | target to be generated rather than statically specified. The | latter there are workarounds for, but not the former. The | author's suggestion of "better support for make docker" is | completely the wrong thing to do - you don't need to support | docker, you need to support mtimes and dependencies in a way that | docker could use. | h4l wrote: | I've found these guidelines for Makefiles make for a pretty good | experience using make: https://tech.davis-hansson.com/p/make/ | | The advice on output sentinel files for rules creating multiple | files helps keep rebuilding dependencies reliable. Avoiding most | of the cryptic make variables also helps Makefiles to remain | easily understandable when you're not frequently working on them. | And using .ONESHELL to allow multi-line statements (e.g. loops, | conditional etc) is great. No need to contort things into one | line. or escape line breaks. | | Seems like you could even use a more serious programming language | instead of sh/bash by setting SHELL to Python or similar. That | may be a road to madness though... | carapace wrote: | > Seems like you could even use a more serious programming | language instead of sh/bash by setting SHELL to Python or | similar. That may be a road to madness though... | | TIL. SHELL=/usr/bin/python .ONESHELL: | all: @from plumbum.cmd import ls | print(ls["-a"]()) | | It totally works... _Mwoooo ha ha ha haaaa!_ | zelphirkalt wrote: | The problem with .ONESHELL is, that it is for the whole file. I | so wish it was per target. That would be really useful. But for | the whole file? Maybe I need each line to be a separate shell | anywhere in the file and that will make it impossible to use | .ONESHELL for the entire file. | h4l wrote: | It is per target, that's how it works when I've used it. For | example: # Makefile SHELL := bash | .ONESHELL: .RECIPEPREFIX = > thing1: | > FOO=bar > echo "$${FOO:?}" .PHONY: thing1 | thing2: > echo "$${FOO:?}" .PHONY: thing2 | | Results in: $ make thing1 thing2 | FOO=bar echo "${FOO:?}" bar echo "${FOO:?}" | bash: line 1: FOO: parameter null or not set | | Note how the bash error for unset FOO is line 1 for the | second target. | | Edit: Maybe I misinterpreted, do you mean you'd want to | choose whether a given target is ONESHELL or not? | philsnow wrote: | > File-watcher or live-reloading. You can create a build/deploy | loop fairly easily | | When I worked with latex more, I kept a ~/.Makefile-latex and a | shell function that would pretty much just do | inotifywait -e modify -e move --recursive --monitor . | while | read; do make --makefile ~/.Makefile-latex; done | | and I kept emacs and xpdf in side-by-side windows. Whenever I'd | save a file in emacs (or xfig or whatever), a second later xpdf | would refresh the pdf (and stay on the same page). It took away | some of the pain of working with latex. | | _edit: I used this complicated setup instead of LyX or whatever | other "(la)tex IDE" because I had ancillary files like xfig | diagrams that would get compiled to .eps files and gnuplot | scripts that would render data into graphs, and the makefile knew | how to generate everything._ | kwantam wrote: | Very nice! It turns out that latexmk has this functionality: | latexmk -pvc -pdf foo.tex | | (It can be configured to HUP your pdf reader if needed, too.) | | I usually add something like this command as the `auto` target | in my latex Makefiles, which works pretty nicely. | earthscienceman wrote: | This is funny, because I just wrote a (fish) shell script that | does this as well because all of the tex IDEs are so painful. | Mostly because the entire efficiency of latex is that you're | editing text and can do it in a _text editor_ like emacs and | move things around very quickly. I don 't want a new interface! | | But. I'm kind of proud. My shell script monitors the tex files | for character changes and then once a (configurable) threshold | of changes is met, it kicks off the compilation process. But | the real game changer is that every time it compiles, if the | compile is successful it commits the recent edits to a git | branch. Then if I want, I can go through the git branch and see | the entire history of edits for only versions of the document | that compiled. It's a game changer in a big way. When I finish | a section, I squash the minor edits into one commit and give it | a good message and then commit the full thing to the main | branch. Then there is where I can make sure my manuscripts look | the way they should and do major revisions or collaborative | edits. | | The icing on the cake is that the fish script monitors for a | special keypress and will take actions. So I hit "c" to compile | the tex, "b" to rerun the massive bibliography compilations, | "s" to squash the commits into the main branch, and "l" to | display log errors. It's a dream! Now I don't think about | compilation at all, or when I should commit something minor to | git (and fiddle with the commands). I just type away and | watch/request the pdf refresh when I need it... and _actually_ | get work done. My god. So happy. | | I just finished this today. | shepherdjerred wrote: | I'd encourage anyone thinking of using make to look at | alternatives. Make is great, but is quickly becomes a ball of | duct-tape. Make works very well when you spend the time to | express your dependency tree, but realistically that never | happens and people tend to add hacks upon hacks for Makefiles. | Not only that, but they don't scale well as your project adds | more components, such as integration testing, documentation, etc. | | I found Earthly[0] to be a great replacement. Everything runs in | Docker. Your builds are reproducible, cache-able, and | parallelizable by default. I've heard Dagger[1] is another good | tool in the space. | | [0]: https://earthly.dev/ | | [1]: https://dagger.io/ | keepquestioning wrote: | Now we need to banish CMake | Koshkin wrote: | Yeah, there is something deeply wrong about the whole thing... | But what do we replace it with? (Out of desperation, perhaps, I | even dreamt of a build system based on a C library, with the | usage being, say, tcc -run mybuild.c | | :) | mdaniel wrote: | People keep saying that, but of the systems I've encountered | that use CMake they've worked the most predictably, as opposed | to autotools, meson, scons, bazel, or heavenforbid ./build.sh | | *WRITING* CMake is the 11th circle of hell, but CLion is | gradually getting more support for it because they use(d?) it | as the first-class project definition when it launched | | I would pay good money for CMake 4.x to switch to | skylark/starlark | klodolph wrote: | Make is fantastic at what it does well. | | These days, I would absolutely not use Make to compile code | written in C, except for the smallest personal projects. It is | just too fussy to construct the Makefile correctly, in a way that | you can get correct incremental builds. Nearly any other build | system is better at building projects written in C, in the sense | that it is easy & straightforward to get your build system to do | correct incremental builds. | EddySchauHai wrote: | Absolutely! Basically every company I've worked with over the | last couple years as a contractor followed this methodology and | it's grown to be my default runner as it's language agnostic. | properparity wrote: | > incremental builds | | I've found that to not matter that much these days - unless | your projects is hundreds of thousands (or maybe millions even) | of LOC, full builds are instant on modern machines. | klodolph wrote: | That's definitely not true for: | | - Most C++ or Rust projects | | - Medium size or larger C projects | | - Anything which is built with tools written in JavaScript | (due to Node startup time overhead) | | Stuff where a full build is close enough to instant: | | - Most Java, C#, Go projects | | - Small C projects | | - Tiny/trivial C++ or Rust programs, or C++ programs written | for embedded systems | | This is just my experience. YMMV. | stjohnswarts wrote: | any sizeable rust or c++ project is gonna take a while to | compile in my experience? I tend to break things off into | libraries that are faster to compile. Some of my coworkers | don't like it but they learn to deal (c++) | cassepipe wrote: | Advertisement time (not affiliated, just want to share the joy) | : I personally use Xmake and try to advertise it every time I | get the chance : FOSS, no DSL it's just Lua, dead simple yet | featureful, and it is ninja fast, or at least claims to be I | never bothered to check that out, it's fast enough for me. | | https://xmake.io/#/ | est31 wrote: | Make is solving many complicated tasks, like keeping in mind when | to re-run some target (there is communication going on with the | compilers that provide make .d files so that they know which | source files influence a binary), or running job servers managing | parallelism that also support nested sub-makes. But it also has | many ugly warts. It's hard to design something that solves both | those tasks as well as make does, and also as _generalist_ as | make does. Often they are solving a subset, what currently itches | the main developer. But something that is both as general, and as | comprehensive as make, those tools are rare. Ninja for example | checks many of the boxes, but lacks make jobserver support. | bXVsbGVy wrote: | Ninja looks clean because it is new. Give it some decades and | it is likely inherit a few of warts make has. | aappleby wrote: | It's not that new, and in practice there aren't very many | ways for it to get warty because it's basically a dependency | graph with "run this command line if this node is dirty" | attached to the edges. | | I like it very much. | belkarx wrote: | There was a post on HN (I believe) a couple of months ago | concerning a research paper that explores "What defines a | makefile" concluding that by their definition, Excel can be used | as one. I've looked around the internet and can't find it, so if | anyone else remembers it and has it upvoted or otherwise has the | link, posting it would add to this conversation. It had | interesting discussion of graphs and requirements and was overall | a worthwhile read. | yaris wrote: | Was it "Build Systems a la Carte" paper? I could not find | mentions of it on HN after 2020 though. | belkarx wrote: | Yes, thank you! | | Here's the PDF in case anyone else is interested: | https://www.microsoft.com/en- | us/research/uploads/prod/2018/0... | | I can't find any actual conversation about it on HN so I | won't post the HN link | eichin wrote: | Aww, noone mentioned that `debian/rules` files are almost always | makefiles? (To the point of starting with `#!/usr/bin/make -f` | ...) | aappleby wrote: | Recently I've been much more pleased by the "Unreasonable | Effectiveness of Ninja Plus A Few Trivial Scripts". | | A raw Ninja file is verbose but easy to read and understand, and | there is basically no magic happening behind the scenes (well, | except for "deps = gcc", but that's minor). Any action that's too | complicated to go directly in the Ninja file goes to a "rule | command \ command = ${command}" that just runs a python or shell | script. | | The only thing I'd like to add to my setup would be "Ninja with | glob support", but again that can be handled in a few lines of | Python that spit out another chunk of .ninja rules so it's not | really a blocker. | falcolas wrote: | I love Make. It's a terrible tool for quite a few things, but | it's awesome at the thing I use it most for - abstracting away | complex series of shell commands behind one or two words. It's | like shell aliases that can follow a repo anywhere. | make test make format make clean | make docker-stack | | Fantastically useful stuff, even if all it's doing is calling | language specific build systems in the background. | JackFr wrote: | Never loved make. First used it in the early nineties and found | the syntax obscure and error messages cryptic. | | My response to this article would be, if make is so great why did | they have to invent 'configure' and 'xmkmf'? And why do people | continue to create new build tools every couple of years? | | Yeah, I mean I guess it worked, but unreasonably effective? | Hardly. | jhallenworld wrote: | Eh, they solve different problems. Make is too simple to | customize your build to deal with system differences- it just | builds your code. | falcolas wrote: | > why did they have to invent 'configure' | | Cross-architecture and linux distro compatibility, mostly. | compiler-guy wrote: | Err, pedantically, configure was not for cross Linux distro | compatibility, but for cross unix compatibility. It existed | long before Linux was a sparkle in Linus's eye. | | And even then, it handled even some non unix environments as | well. | bch wrote: | > ... why do people continue to create new build tools every | couple of years? | | Seems like a rite[0] of passage to some degree. Perhaps similar | to people talking a stab at The Next Actually Correct CMS, and | The Next Object System That Doesn't Suck, or The Next Linux | Distro For Smart People. | | [0] edit: corrected "right V. rite" per | https://news.ycombinator.com/item?id=32442473 | erik_seaberg wrote: | (btw, it's https://en.wikipedia.org/wiki/Rite_of_passage) | stjohnswarts wrote: | i've turned to cmake to do some really weird dependency | management for various script calling. It's much more | scriptable/friendly than make in its modern form but obviously | no python :) | ogogmad wrote: | Is there a Python library that can check a timestamp and update | some files according to a lambda? I don't want to learn Make | syntax again. | Jtsummers wrote: | if os.stat(object).st_mtime < os.stat(dependency).st_mtime: | ... | | Use a dict to contain each rule: rules["a.c"] = | (["b.c", "c.c", "b.h", "c.h"], action) ... | | That can be simplified as well and then run a simple parser | over it that takes a simpler representation and turns it into | that dict. Then: def make(rules, rule): | (deps, action) = rules[rule] run_rule = len(deps) == 0 | # if there are no dependencies, the rule always runs | for dep in deps: make(rules, dep) if | os.stat(rule).st_mtime < os.stat(dep).st_mtime: run_rule = True | if run_rule: action() | | Of course, this doesn't actually validate the DAG itself. | | ---------- | | An amendment: You'll probably want to pass both the rule name | and the dependencies into the action function/lambda/object so | that you can parameterize it and maybe reuse the action (like a | common compiler command): if run_rule: | action(rule, deps) | badrabbit wrote: | Autogen/autotools mess however... not so pleasant when it breaks. | | Curious for those who published source with autotools,etc... do | you really sit down and figure that out. It's just a mystery that | works or doesn't to me. I never have or would go beyond a simple | make file. It really seems tedious on top of the actual code you | write. A bit impressive tbh. | jbboehr wrote: | Yes, I used autotools[0]. It's definitely hairier than plain | make, but you get a lot of useful features on top of it. | There's thousands of examples all over the internet so it's | easy to reference them. | | I like the elegance of pure make, and do use it when | appropriate, but I wouldn't really want to reimplement the | things autotools does myself in it. | | [0]: | https://github.com/jbboehr/handlebars.c/blob/master/configur... | thinkingkong wrote: | I like make. But these days to me the best part about it is that | it's a common entry point. Most popular languages come with their | own make-esque tools that provide the same experience to | developers and systems. | | Tying together multiple projects, source from different | locations, etc Id probably use make or a script. | dijit wrote: | A company I worked for used make to execute docker, which felt | somewhat odd. | | Correct me if I'm wrong, but I always assumed `make` to be one of | those build tools that could incrementally build targets based on | dependencies. | | The arcane and esoteric language that constitutes `make` is | almost universally avoided, surely if people need a simple task | runner there could be better options? | falcolas wrote: | Better? Absolutely. | | More ubiquitous? Not really. | | I use it for executing docker as well, because the docker | command is actually about 5 commands with long lists of | parameters. But it's just `make docker-stack` for everyone now. | jwilk wrote: | It's 500 Internal Server Error for me. | | Archived copy: | | https://web.archive.org/web/20220812135641/https://matt-rick... | jcoq wrote: | This is a remarkably stupid comment but not everything is | "unreasonably effective". Mathematics was noted as unreasonably | effective for modeling the universe because most areas of | mathematics were not invented for the applications they meet... | like discovering that your coffee maker doubles as an | exceptionally good waffle maker. | | Makefiles on the other hand, are not unreasonably effective in | this sense. Makefiles are, in fact, _reasonably_ effective... | like a coffee maker that brews coffee well. | [deleted] | bjourne wrote: | The short article conflates popularity with quality. Windows 3.11 | became the most sold os in history despite being utter trash. | Make is popular because it was the first build system, not | because it is not utter trash. | BiteCode_dev wrote: | Just got out of a python training session with one of my | student running w11. Can confirm. So many problems. | | Seems like one version out of 2 of windows being trouble stills | stand. | Nexialist wrote: | Slightly tangential but I've worked for several companies now | that use `make` as a simple command runner, and I have to say | it's been a boon. | | Being able to drop into any repo at work and expect that `make | init`, `make test` and `make start` will by convention always | work no matter what the underlying language or technology is, has | saved me a lot of time. | ReadTheLicense wrote: | This is standard in Node.js ecosystem and I love it. Each | package has scripts in package.json that you can run with _npm | run [name]_ , and some of these like start, test or build (and | more) are standardized. It's really great DX. | patrickthebold wrote: | But it's npm, so when you switch to a java project, for | example, you have different commands. | r3trohack3r wrote: | Quite a few companies I've contracted with have lifted the | pattern up into Bazel or Gnu Make - for node projects `make | lint` can be a pass through. | | In the project repo, either work. | txutxu wrote: | Conventions are great, but that doesn't look like anything | specific to make, a shell wrapper could do that: | #!/bin/sh case $1 in init) | ... do whatever for each project init ;; | start) ... do whatever for each project start | ;; test) ... do whatever for each | project tests ;; *) | echo "Usage: $0 init|start|test" >&2 exit 1 | ;; esac | | In my home/personal projects I use a similar convention (clean, | deploy, update, start, stop, test...), I call those little sh | scripts in the root of the repo "runme". | | The advantage could be, maybe, no need to install make if not | present, and no need to learn make stuff if you don't know it. | | Sometimes they don't match the usual words (deploy, start, | stop, etc) but then I know that if I don't remember them, I | just type ./runme and get the help. | | For my scenario, it's perfect because of it's simplicity. | kazinator wrote: | You can make "make init" work on Windows _and_ Unix if you | work at it, out of the same Makefile. | | The above won't. | dymk wrote: | A shell wrapper could do that, but Makefiles are a DSL to do | exactly that with less boilerplate. | marcosdumay wrote: | And have a nice inbuilt graph runner if you decide one task | depends on another... | tom_ wrote: | Complete with automatic parallelization if you ask for | it! And automatic KEY=VALUE command line parsing, default | echoing of commands (easily silenced), default barf on | subprocess failure (easily bypassed). The variable system | also interacts reasonably sensibly with the environment. | | I've never rated Make for building C programs, but it's | pretty good as a convenient cross-platform shell-agnostic | task runner. There are also several minimal-dependency | builds for Windows, that mean you can just add the exe to | your repo and forget about it. | marcosdumay wrote: | To tell the truth, make sucks incredibly for building | modern C programs. There are just too many targets. It's | why all of them generate their makefile with some | abomination. | | But it is still a great task runner. | natrys wrote: | Tbf, that particularity is easily achieved in shell | scripts too: task1() { echo | hello } task2() { | task1() echo world } | "$@" | garblegarble wrote: | But now update it to not re-run tasks unnecessarily - | it's already wordier than a shell script right now. | | Meanwhile, in Make that's task1: | echo hello task2: task1 echo | world | natrys wrote: | True, that's where Make shines. Though given the | popularity of so many Make alternatives (the strictly | subset of command runner variety, like just[1]) who keep | its syntax but not this mechanism, I wonder if for | command runner unnecessarily re-running dependencies is | really a big deal. Because quite often the tasks are | simple and idempotent anyway, and then it's a bit of a | hassle to artificially back the target by a dummy file in | Make (which your example doesn't do here e.g.). | | [1] https://github.com/casey/just | whateveracct wrote: | make gives you autocomplete more easily for free. One reason | I use it always. | sanderjd wrote: | This was the nicest thing about blaze at google. I'm a big | believer that having a single standard tool for things is a | huge value add, regardless of what the tool is. I didn't really | like blaze particularly, and I don't really like make | particularly, but it's _amazing_ to just have a single standard | that everybody uses, no matter what it is. | pornel wrote: | Rust's Cargo has the same appeal. There are 90,000 libraries | that support cargo build/doc/run/test with no fuss. | shoo wrote: | I've worked on a few projects that apply this pattern of using | a Makefile to define and run imperative commands. A few people | develop the pattern independently, then it gets proliferated | through the company as part of the boilerplate into new | repositories. It's not a terrible pattern, it's just a bit | strange. | | For many junior colleagues, this pattern is the first time | they've ever encountered make -- hijacked as some kind of | imperative command runner. | | It's quite rare to run into someone who is aware that make can | be used to define rules for producing files from other files. | | I find it all a bit odd. Of course, no-one is born knowing | about middle-aged build tools. | arinlen wrote: | > _It 's quite rare to run into someone who is aware that | make can be used to define rules for producing files from | other files._ | | Is it, though? | | That's literally what Make does as part of its happy path. | | GNU Make even added support for pattern rules, as this use | case is so pervasive. | | What do you think people think make is about? | shoo wrote: | oh i agree, that's why i find the situation odd! | | i'm talking working on projects with people whose first | encounter with make is in a project where someone else has | defined a Makefile to wrap imperative actions, e.g. `make | run-unit-tests`, `make deploy`. If they think about make at | all, there's a good chance they think make is for | performing imperative actions, and has nothing specifically | to do with producing files from other files using rules and | a dependency graph, or the idea of a target being a file, | or a target being out of date. | 3836293648 wrote: | This is what I do for all non-rust projects. I knew what it | was supposed to do, but wow if it took me forever to figure | out how to do it (the connection between rule name and file | name is really poorly documented in tutorials, probably | should've just read the man page) | jbboehr wrote: | Yeah, I did this too. It's not that surprising considering | that typically end-users only interact with phony targets | (all, clean, install, etc). | pak9rabid wrote: | $ make run-dev | | That command (to run an Angular/nodejs dev instance has staved | off carpel-tunnel syndrome for me for maybe another 5 years. | shadowgovt wrote: | Does make still basically fail to handle filenames with spaces in | them? | | That was the deal-breaker for me last I checked. | davidpfarrell wrote: | As these types of post often come around to make's sub-optimal | use as general runner, I'd like to point out my project, Run: | | https://github.com/TekWizely/run | | It feels like a makefile but is optimized managing and invoking | small tasks and wrappers, and auto-generates help text from | comments. | [deleted] | PaulKeeble wrote: | Its unfortunate that other build systems haven't taken over. Make | is terrible for incremental builds and its reliance on binaries | often means issues getting it to run and being very platform | dependent. It is better than using a bat or shell file for the | same purpose but its a long way behind many of the other language | specific tools. I am surprised something better hasn't become | popular, Make is the CVS of the build tools. | olliej wrote: | I find a bunch of the decisions in Make to not be great - but | it's all just syntax stuff. | | At its core all a Makefile is is a dependency graph, which is | necessary in all the more advanced config managers, only those | others are much more heavyweight than the vast majority of | projects ever need. Most code doesn't need to vary arguments or | parameters based on the target environment, and so that | complexity is unneeded, in which case a Makefile can do it all. | BiteCode_dev wrote: | Make dsl is terrible, it's the yaml of build systems. | | If you are a python dev, give doit a try. | Koshkin wrote: | What is so "terrible" about it? I think the rule syntax is as | simple as it gets. | vitiral wrote: | Required tabs, lines are each their own "script" instead of | blocks (allowing variables), not allowing other executors | (i.e. python, TCL, etc would be better than sh). | nottorp wrote: | Interesting that everyone who recommends an alternative to Make | picks... something different. I doubt there will be two people | recommending the same thing in the whole discussion. | qbasic_forever wrote: | For a task runner I really like just and its Justfile format: | https://github.com/casey/just It is heavily inspired by make but | doesn't focus on the DAG stuff (but does support tasks and | dependencies). Crucially it has a much better user experience for | listing and documenting tasks--just comment your tasks and it | will build a nice list of them in the CLI. It also supports | passing CLI parameters to task invocations so you can build | simple CLI tools with it too (no need to clutter your repo with | little one-off CLI tools written in a myriad of different | languages). | | If most of your make usage is a bunch of .PHONY nonsense and | tricks to make it so developers can run a simple command to get | going, check out just. You will find it's not difficult to | immediately switch over to its task format. | gurgeous wrote: | Seconded - I love just & Justfile. Such an upgrade after trying | to force things into package.json scripts. Chaining commands, | optional CLI arguments, comments, simple variables, etc. Very | simple and a breath of fresh air. | davidpfarrell wrote: | For those looking for a powerful task runners that feel like a | makefile, please take a look at Run: | | https://github.com/TekWizely/run | | It's better a managing and invoking tasks and generates help | text from comments. | kbd wrote: | Just seems neat, but except for the dependencies it's just a | way to package multiple shell scripts into one file, no? | | I've thought about trying it out a few times but can never see | its value over scripts in ./bin. | qbasic_forever wrote: | Scripts in bin have no documentation, no easy way to | enumerate them, etc. There is definitely a time and a place | for bin scripts, especially as things grow in complexity. | However the beauty of just is that there's one file (the | justfile) that defines all of your project's actions. You | don't have to go spelunking into bin to figure out how to | tweak a compiler flag, etc. And since just will run anything | there's no reason why your complex bin scripts can't just be | called from a simple one liner task in a justfile. | | Could your write a bash script that does stuff like enumerate | all the bin scripts, pull out documentation comments, etc.? | Absolutely, and people have followed that pattern for a while | (see https://github.com/qrush/sub) but it's a bunch of | boilerplate to copy between projects. Just pulls out that | logic into a simpler config file. | niedzielski wrote: | Just looks soooo promising! I don't think I can use it until | conventional file target and dependencies are supported though. | Right now everything's tasks (phonies) so conventional makefile | rules like the following are impractical: tic- | tac-toe: tic.o tac.o toe.o cc -o '$@' $^ | %.o: %.c; cc -c $^ | qbasic_forever wrote: | You might find checkexec useful to pair with just, it is | basically a tool that only does the file-based dependency | part of make: https://github.com/kurtbuilds/checkexec | dahfizz wrote: | I don't understand the use case of `just`. It drops every | useful feature from `make`. It doesn't look like it has | parallelism or the ability to not needlessly re-run tasks. | | Even if `just` was installed on a standard Linux box, I don't | see the benefit of it over a bash script. | throwaway787544 wrote: | Declarative isn't important. I wish people would stop talking | about it. It's not even useful to think about most of the time | because of how many ways it can be interpreted. It's a | thereotical categorization, not a functional design principle. | | Just make the program do useful things for the user. Make the | computer work for the human rather than the other way around. | shepherdjerred wrote: | > Make the computer work for the human rather than the other | way around. | | Ironically is a very concise definition of `declarative`. | samatman wrote: | This is almost too devoid of content (opinion is not that) to | usefully reply, and I suspect you're gesturing at some more | specific point I might agree with, or at least understand. | | Declaration is absolutely a coherent and functional design | principle. A declarative system is one in which the outcome is | specified and the process is not. | | This has big payoffs in domains where it's natural. A good | example being grammars. It also has hazards, a good example | being the performance of algorithms to parse grammars. | | We can see where a declarative build system might be a mixed | bag, because the process itself is imperative: make is an early | attempt to reconcile imperative build processes with a | declaration of what circumstances require their triggering. | Basically every build system since make has improved on make, | but they all do more-or-less what make does. | | The design, in short, is proven, as well as declarative but not | purely so. | | And the ability to reason about the degree to which make is | declarative shows that 'declarative' is in fact a coherent | idea. But you can't declare software into existence, you must | compile it. | nimih wrote: | I dunno, the "declarative-ness" of `make` is a pretty important | component of its usefulness. In particular, the property of | `make`, wherein the structure/details of the computation is | implicit in the provided configuration and invoked command | rather than being explicitly written down somewhere, is central | to its utility, since the alternative of just writing a shell | script is anecdotally a much less popular option. If you want | to propose a more appropriate word to describe such a property | which is less buzzwordy, feel free, but in the context of "why | does `make` have such enduring popularity", I think the | article's author is being quite reasonable in bringing it up. | throwaway787544 wrote: | You could replace the word "declarative-ness" with | "automation" and it'd mean the same thing. And literally all | configuration of functionality implies the structure and | details of computation - that's the point of configuration, | to tell an already assembled program that already has | structure and computational details what to do with it. | There's no overt distinction between "declarative | programming" and "configure a function with value X". | | Makefiles are simply configuration files that use whitespace | and a couple characters to create the configuration, and what | Make's inbuilt functions do with that determine the extent to | which the result becomes "more intelligent". Yes they are | used to build a graph and execute it, but so is Dotfile | notation, and software package configuration files. But we | don't call those declarative programming. Many of those | configuration files create multiple levels of instructions | and require several passes to execute properly. But we just | call them "config files" because we don't feel they are | intellectually superior enough to be called a form of | programming. And on the other hand, we don't call declarative | programming "configuration", but they're often the same | thing. | | Nobody says they "imperatively configure" some software, but | they do consider themselves "declaratively configuring" it. | Because they've overloaded the word "declare" as if it means | something other than "write down a thing I want a computer to | eventually do with some automation". People bring up the | declarative thing because they want to imagine there's some | intellectual value to considering it, but there isn't. You're | basically saying "I want to configure a program rather than | write one". Which is fine. But just say that and stop | pretending that's going to immediately lead to a better | result. | Banana699 wrote: | Configuration doesn't have to be declarative, for example | see https://lukeplant.me.uk/blog/posts/less-powerful- | languages/, in particular the section about python | configuration, where an imperative configuration language | is discussed. How imperative? Very. It's a line-oriented | language, where the program reads a line and changes | something in an internal data structure accordingly then | continue reading the file. This is imperative, the person | writing the configuration file has to think about state and | time while writing the file, not just abstract goal states | and facts. | | Declarative vs. Imperative is a spectrum. For instance, | there is a declarative language hiding inside most | imperative languages : Infix Math. 1+2*3/71**7 is | declarative because it under-specifies the order of | operation, only the data flow dependencies implied by | operator precedence needs to be respected. In the | precedence hierarchy I had in mind when I wrote it, You can | do 2*3 first or 71**7 first, it's unspecified and | irrelevant. I only ask that you do both before you perform | the division of their results, and that the addition is the | last operation. Meanwhile, in Forth, math is imperative, | you have to unroll the expression tree into an exact | sequence. | | Declarative is any language that under-specifies the task | being described. Therefore, every language worth using is | declarative to some degree or the other. After all, that is | the very purpose of a high level language : to under- | specify a task by describing only the most essential of | details, all the abstracted details are taken care of by | either inference (compiler figures it out, possibly | according to rules that you need to be aware of) or | exhaustive checking (compiler generates all possible cases | and code to select among them at runtime, or very generic | code that can handle all cases uniformly). If, like Alan | Perlis says, "A low level language is that which requires | attention to the irrelevant", then every good language is | already declarative in some sense, you omit things and they | get taken care of automatically, that's what Decorative | means. | | You can say you hate buzzwords, I empathize. You can just | say that make is bad software (trivially true, or we | wouldn't have needed software to _generate_ makefiles, | effectively making them a machine code that isn 't meant to | be written by humans) and that being declarative doesn't | make it any less bad. Declarative vs. Imperative are just | names for design decisions, they guide a language designer | but don't have the power to make a language good single- | handedly. | wnoise wrote: | The builder pattern is imperative configuration. | kazinator wrote: | Anyone who finds make unreasonably effective must be working with | GNU Make. | | If I had to use some barely POSIX conforming thing from BSD thing | or wherever, I'd instead write a write a top-to-bottom linear | shell script full of conditionals. | makapuf wrote: | In that case,a good habit is to name those GNUMakefile. Works | the same, but announce gnu make (which is the one worth it) | bXVsbGVy wrote: | I'm trying to understand why so many people seems to hate make. | | I hate building system that don't use Makefile, or that use but | don't respect the variable convention. It makes really quite | annoying to do things like changing allocation library, add | compilers flags, etc. | TylerE wrote: | Because gnu autotools blows goats. Oh, and the tab thing. | gbrown_ wrote: | make != autoconf | TylerE wrote: | Yes, but 99% of the makefiles you encounter in the wild | comes from autotools. | dima55 wrote: | This isn't at all true, in my experience. If it's true | for you, please consider that your issues are about | autotools and not Make, and direct your complaints in | that direction. | stjohnswarts wrote: | the vast majority of the ones I encounter are from | cmake... | tpoacher wrote: | surely it blows gnus rather than goats? | qbasic_forever wrote: | As a build system make wasn't really designed to handle stuff | like partial rebuilds, caching, or distributed building. Modern | build systems like bazel are just orders and orders of | magnitude faster and better for complex projects. | dima55 wrote: | Yeah. As far as I can tell, most people complaining about Make, | and building its replacements haven't figured out how to use | Make, or bothered to read the manual. It's really not that | complicated... | nhooyr wrote: | 100% agreed. Half these comments make no sense and | demonstrate a real ignorance of make. Please everyone read | the manual and judge make for make, not autotools... | [deleted] | jhallenworld wrote: | To go along with Make, use this technique to automatically | generate and include dependency information: | | https://scottmcpeak.com/autodepend/autodepend.html | | And this, avoid recursive make: | | https://accu.org/journals/overload/14/71/miller_2004/ ___________________________________________________________________ (page generated 2022-08-12 23:00 UTC)