I have been using Makefile for over 10 years in all of my projects, and here are some features I've always found lacking in Makefile:<p>1. There is no way to display documentation for commands and accepted parameters. Yes, you can write a special task that will display comments, but you have to copy it from project to project.<p>2. The need to pass named parameters when calling tasks. I want to write `make serve localhost 3000` instead of `make serve bind=localhost port=3000`<p>3. I've always had the need in different projects to use the same commands, so I had to copy tasks from project to project. I need a central place with commands that I can apply to any project.<p>4. The ability to write tasks in different languages. In some cases, it's easier to write in Python or TypeScript/Deno.<p>5. And most importantly, it is difficult to write commands in Makefile that can be used in different environments. For example, I need to run commands on different groups of servers: production and staging. This could look like: `make production TASK1 TASK2` or `make stage TASK1 TASK2`. In other words, the production/stage task sets up the parameters for executing tasks in a specific environment. It might be possible to call commands in this way with Makefile, but it seems too complicated.<p>As a result, I decided to write my own utility for task automation: <a href="https://github.com/devrc-hub/devrc">https://github.com/devrc-hub/devrc</a><p>It solves all of the above problems and has other interesting features and also written in Rust .
These “run something” targets should be marked as .PHONY so make realizes they will not produce a file. Otherwise a file named like the target will confuse make and turn your day into a sad one.
The main reason I still use Make over other alternatives is that everyone already has it. No need to install something else to bootstrap a project. I put in a `make initialize` that takes care of everything: setting up the virtualenv, using our internal pip mirror, downloading dependencies, etc. It's just such low friction!<p>Side note: Make is also really popular among self-hosters for ansible infrastructure setup.
Make wasn’t designed to be a task runner. It can be used as one, but it doesn’t make (pun not intended) a very good one, and it doesn’t have the best syntax, either.<p>Make is an artifact updater. Although it’s not its main focus, Peter Miller’s classical paper ‘Recursive Make Considered Harmful’ does a good job of explaining what it is.<p>One great benefit of make is that it’s present everywhere, so there’s no additional hassle of installing an extra tool. Depending on the project, this hassle-freeness may or may not outweigh make’s relative incomfort as a task runner.
It's not true that Python doesn't have anything akin to package.json scripts. Bonus points: all is executed within project's virtualenv.<p><a href="https://python-poetry.org/docs/cli/#run" rel="nofollow">https://python-poetry.org/docs/cli/#run</a>
I've been seeing a growing use of `just` for cases like this. It works similar in function to make, but it's a great deal cleaner and easier to use.
Invoke is like a Makefile but in Python: <a href="https://www.pyinvoke.org/index.html" rel="nofollow">https://www.pyinvoke.org/index.html</a>
Makefiles are great for local task automation. In college I had a friend who started every homework, even the social science and English homework, with a makefile to compile his Latex files.
Unrelated to Python, but is anyone aware of a tool like Make that can handle file path wildcards in prerequisites?<p>For example, let's say I have a rule that consumes *.json to produce some target. I want it to rebuild that target if any *.json files are added or removed (or modified).<p>As far as I'm aware, Make relies on file system timestamps, so even if it supported wildcards (I'm not sure if it does or not), it wouldn't be able to notice that foo.json has been <i>deleted</i> and a rebuild of the target is needed.<p>I thought I'd ask here before I go build such a tool (to support incremental rebuilds of a static site).<p>Edit to add that my particular use case has a non-prescriptive set of directories, nested a few levels deep. I'm actually realizing that that is probably a bigger hurdle for using something like Make (e.g. transform all *.md files, no matter how deep; but copy everything else, like images, verbatim; oh, and also then aggregate all *.md files into an Atom feed). Yes, I know this is asking a lot of something like Make!
I am surprised nobody is mentioning go-task <a href="https://github.com/go-task/task">https://github.com/go-task/task</a>.<p>It is a great project. I love the management of dotfiles, the inclusion of other Taskfiles, the namespacing, the fact it is an easily downloadable binary that runs the same on Linux, macOS AND Windows.<p>It is the superior option.
Another possibility is using the `scripts` in the `pyproject.toml`, as described here: <a href="https://python-poetry.org/docs/pyproject/#scripts" rel="nofollow">https://python-poetry.org/docs/pyproject/#scripts</a>
I don't do much python anymore but recently, I decided to try moving from Makefile files to simple command-line python scripts for this type of thing. The python standard library has pretty much everything you need to replace `make` so there's no need for virtual environments and...
1. They're way more readable and intuitive
2. Cross-platform across Linux, Windows, MacOS, even if you have to branch off to platform-specific commands inside the script
3. I'd much rather maintain commands using the python standard library over complex command-line things with `sed`, `awk`, etc
Makefile quickly gets complicated when you have to pass in flags (--an-example-flag).<p>e.g.<p>dev:
./scripts/run-dev.sh $(if $(filter build,$(MAKECMDGOALS)),--build,) $(if $(filter restart,$(MAKECMDGOALS)),--restart,) ...etc<p>Since the project is in python it would be better to write a python script and start the tasks as subprocesses. This means you can use the python argparser you're already familiar with.<p><a href="https://docs.python.org/3/library/subprocess.html" rel="nofollow">https://docs.python.org/3/library/subprocess.html</a>
I’ve been doing this for decades now. Anything I do has “make deps”, “make serve”, etc. I don’t care that Make “wasn’t made for this”, it is ubiquitous and reliable at it.
Use Makefile if you want to make your cross-platform build environment Linux only. You will have all kind of issues on Windows, Mac, *BSD, you name it.
Pipenv has script hooks as well.<p><a href="https://pipenv.pypa.io/en/latest/scripts/" rel="nofollow">https://pipenv.pypa.io/en/latest/scripts/</a><p>It also includes many other package.json features.
I would not recommend make for python projects as make does not play nicely with virtualenvs and the environment variables that power them. You can make it work, but you'll be fighting it constantly.
For my side projects I use a bash script in the style of <a href="https://github.com/adriancooney/Taskfile">https://github.com/adriancooney/Taskfile</a>