Hacker News new | past | comments | ask | show | jobs | submit login

A shell wrapper could do that, but Makefiles are a DSL to do exactly that with less boilerplate.



And have a nice inbuilt graph runner if you decide one task depends on another...


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.


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.


Tbf, that particularity is easily achieved in shell scripts too:

    task1() {
        echo hello
    }

    task2() {
        task1()
        echo world
    }

    "$@"


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


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


> I wonder if for command runner unnecessarily re-running dependencies is really a big deal.

I've used in in the past with python/django roughly like so (untested from memory, there may be a "last modified" bug in here that still makes something run unnecessarily):

  .PHONY: runserver

  environ:
    python -m venv $@

  environ/lib: requirements | environ
    . environ/bin/activate && pip install -r $<
    touch $@

  runserver: | environ/lib
    . environ/bin/activate && python manage.py runserver [foo]
Setting up these prerequisites takes a while and doing every time you start the dev server would be a pain, but not doing it and forgetting when requirements were updated is also a pain. This would handle both.


What is the | doing in the dependencies?


Specifies order without a hard more-recent-than dependency on everything after the |. So if the timestamp on "environ" is updated, that won't cause the "environ/lib" recipe to run, but if they both have to run then it ensures "environ" happens before "environ/lib".

It might not be necessary for this example, but I've found being more liberal with this feature and manually using "touch" has been more reliable in stopping unnecessary re-runs when the recipe target and dependency are directories instead of just files.


But now put .PHONY everywhere.

I don't think Makefiles are a bad way to go but a bash script is likely more accessible and easily reasoned about in most places.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: