Self-Documented Makefile
marmelab.com
marmelab.com
To fix that, I created `make2tap`: https://www.npmjs.com/package/make2tap
This small utility takes a `make` output and generate a `TAP` one that you can pipe to any `TAP reporter`.
Our current `make build | make2tap | tap-format-spec` looks like: http://i.imgur.com/chs0Jf3.png
This is dumb:
restart-frontend: ## Restart the frontend and admin apps in dev
@make stop-frontend-dev && make run-frontend-dev
@echo "Frontend app restarted"
Write it with a shell script like this: stop-frontend-dev() {
...
}
run-frontend-dev() {
...
}
restart-front-end() {
stop-frontend-dev
run-frontend-dev
echo "Frontend app restarted"
}
build() {
make # this actually does stuff you can't do in bash.
}
"$@" # call function $1 with args $2... Can also print help here.
This is a lot cleaner. The PID stuff can be done with bash too.Use Make when you need to track dependencies based on files, such as for build systems whose output is files, and bash/other script for managing a hosts services which are/may be required for the build system.
The Makefile here almost certainly has some unexpected side-effects when used in a way that the original author didn't intend to. E.g. accidentally removing your PID files (e.g. with git clean) and then you have more than one dev server running.
Make isn't intended for starting/stopping services or doing non-build tasks. It really sucks for that kind of tasks. It might work ok for the handful of workflows the author intended it for, but then spectacularly break down when someone else tries to do something outside of that or when new features get added.
Sometimes I see people use Make for situations where some build platform (e.g. Windows or an embedded toolchain) doesn't have a proper shell at hand. But it's still not a great idea to use Makefiles as a replacement for shell scripts.
There are much better alternatives for doing non-build tasks.
My experience has been that you can totally use GNU make as a replacement for shell scripts, and it works fine. Why wouldn't it? It's just sequences of shell commands, where each sequence has a name.
And in fact for this sort of situation, where you've got one of a pre-canned set of sequences to run, it works very well - because GNU make will handle the command line for you, and most shells will give you some kind of basic (but usable) tab completion for the sequences available, without any effort on your part.
You do need to use phony targets, though. (https://www.gnu.org/software/make/manual/html_node/Phony-Tar...) This might seem like a ridiculous requirement, but I've seen plenty of bash scripts that just kept on going when a shell command failed; it's not like GNU make is unique in requiring a small amount of boilerplate to make things work nicely.
> This is a lot cleaner. The PID stuff can be done with bash too.
In any case, until there is a tool which is as ubiquitous and as declarative as make you're going to have hard time convincing those of use who use it this way to stop.
Plus, you end up doing stupid stuff like:
DoThat(); // It does that
When you use make anyway, adding those targets there is the logical thing to do. One interface instead of two. It's even less lines of code than your proposed solution (which btw does not fail hard like make does, so a recipe for desaster).
Finally: Pretty please, sh, not bash. Almost none of the bash-scripts out there use actual bash-features and those that do can usually easily be rewritten to just rely on a plain posix shell.
Make's semantics are to invoke the shell at EVERY LINE. This is slow and makes quoting a nightmare (try quoting find or sed in bash in Make correctly; you cannot directly invoke them from Make). Conversely, bash's semantics do not involve make whatsoever :)
Shell also has local variables for modularity, and better constructs for conditionals and iterations. And functions. Make has a poor man's implementation of Turing-complete constructs compared to shell (which is a little ironic since shell is already sort of a poor man's procedural language).
'set -o errexit' makes bash fail on errors. Also, as mentioned, the Makefile isn't using phony targets. Neither shell or Make is perfect, but shell is the more appropriate tool when you're not making use of Make's incremental build features.
Another pattern I find useful is to have the actions take more arguments, like:
./run.sh dev-frontend --flag-for-testing
which is implemented just like this: dev-front-end() {
./frontend.py --port 8080 --other-default-flag "$@"
}
"$@"
The arguments to Make are actually TARGETS/files and not functions, so I don't think you can do this.I assume you mean commands here? Actually, how slow is it? Ever measured it? I am sure me writing this line took more time than they could save by a rewrite over the course of a few years of using the performance optimized version.
> Shell also has local variables for modularity, and better constructs for conditionals and iterations
And variables in make that you set for a command are only visible to it. Besides - wasn't your point that the OP should not use make because s/he does not use its features?
> 'set -o errexit' makes bash fail on errors
Indeed it does, it was just missing from your better version.
> Another pattern I find useful is to have the actions take more arguments [...] The arguments to Make are actually TARGETS/files and not functions, so I don't think you can do this.
Yes, one communicates with make through variables, not through arguments.
Over the last 20 or so years I've done my fair share of shell scripting and of writing makefiles. Maybe I am just too old and boring to get excited over this, but let me recap: The OP happens to be using make, s/he used an elegant hack to have the file document itself and its usage. S/He wrote about the latter. No, I am not impressed by the post - I used a similar technique in 2003 or 2004 to bootstrap the documentation of a very complex build system - and I'm sure others have before and after me. But I like the concise article.
Debating whether make is "better" than "sh" is completely off-topic here - if there was a universal truth we'd not have seen tools like psh, scsh and the rest of language-specific shell substitutes nor would we have the myriad of language-specific build tools. Back then, when I was young and naive, I've implemented a few of those myself. But now I am not young anymore. And now I naively believe that people should use what they are most productive with. Or everybody come to their senses and just use lisp.
It also turns out that dependencies creep up pretty often, so shortly after we started abusing the Makefile, we also started using it "right" too.
Examples: * https://github.com/dergachev/screengif/blob/master/Makefile * https://github.com/dergachev/drupal-docker-marriage/blob/mas...
Not very useful, but it kept me amused for a weekend at least.
Procfil is a format that declares a named list of processes to be run that can be controlled by tools like Foreman (http://ddollar.github.io/foreman/) and Honcho (https://pypi.python.org/pypi/honcho). The advantage is being able to start and stop them concurrently as a group, useful for things that otherwise take a tmux session or multiple windows/tabs, like dev server + file watching + live reload: they become a simple `foreman start`. Processes can also be started individually. Procfiles can also be exported to other formats, like systemd, upstart, inittab, etc.
Here's an example Procfile from a web project I've been working on. Since it uses node I went with node tools like http-server and watch, but it could just as easily use any other web server or file watcher. The way it works is it starts a web server serving public/; starts a live reload server for public/; and watches the src/ directory for changes and re-runs make. The makefile has a few rules for compiling JS and CSS from src/ to public/.
web: ./node_modules/.bin/http-server
livereload: ./node_modules/.bin/livereload public
watch: ./node_modules/.bin/watch make srcI don't think anybody should use make to do that at first place. That's not what make was built for. Likewise Foreman should not be used as a build tool because it is not.
EDIT:
now i've seen the makefile in the example,I understand your comment and this is absolutely not where one wants to use make, that's just ridiculous.
Let me ask you this. Would you sit on a tree stump? How about kill a fly with a newspaper? Sometimes things are great for purposes for which they weren't originally intended.
Misuse of tools in software development is why we end up with broken software, useless solutions that solve stupid problems because the problem wasn't well understood as first place, and first and foremost unnecessary dependencies. That's why we end up with this makefile "hack".
Now explain what it's got to do with "conservatism". bad practices != innovation .
If you think this particular use of make is a "bad practice", then argue why it is! If there's no better reason than "This use isn't as intended!" then your opinion won't have much weight with people.
I also don't think the article is too keen on "standards", judging by it referring to make a "task launcher" and the suggested usage completely diverging from the expected behavior of the program.
No, Jesus Christ, please don't. Preserve default action as being to build.
Good user interface and good user experience relies on meeting expectations. This behavior breaks those expectations. What if your expectations are different, you ask? In environments where there is an established tradition I think it is rude to break with the norm unless there is a compelling reason to do so. The commandline is popular among developers, other IT professionals and power users because of how efficient it is. It is efficient because there is not all the noise, handholding and other bullsh*t. Please let us keep it that way.
Use a specific target to list other targets. I've seen some people use "make targets".
And regardless of whether they do or don't, if they are in a directory with a single character file name, they will have to escape it too...
Instead of blindly following laws like "Always meet user expectations" we should think about things in a case-by-case basis. Your law is at best a rule of thumb.
If you mean during packaging, then usually the code is configured with the actual destination path (configure --prefix=/usr), but then installed into a directory which will be packaged by overriding destination (make install DESTDIR=/tmp/some/path)
Bower is a particularly bad offender here. By default it will periodically ask you if it can send anonymous usage statistics. If you've never experienced it before, you have to angrily google the problem once piping `yes` to it fails and find a github issue where the solution is `bower install --config.interactive=false`
Always assume your build tool is running in an automated environment without user input. If the user wants to do something out of the ordinary, provide command line options and try to follow the conventions that already exist.
A big offender here is CURL, where this affects the verbosity during download. This is especially frustrating with regard to automation.
1. You type by hand, everything works.
2. You copy&paste into the command line, and everything works.
3. You run it from a script, and it behaves differently.
I really wish dd had this type of caution. It would save me a lot of misery, preventing the loss of numerous drives.
That breaks down when you work with multiple projects.
For example, try writing a script that downloads and builds a couple of projects. You would have to parse each makefile and hope you can discover the right target to build.
make all && sudo make install
"all" is also recommended default target in GNU make documentation:Phony targets can have prerequisites. When one directory contains multiple programs, it is most convenient to describe all of the programs in one makefile ./Makefile. Since the target remade by default will be the first one in the makefile, it is common to make this a phony target named ‘all’ and give it, as prerequisites, all the individual programs.
We use Makefile in same way to execute project related tasks like deployments and run development environments. This will even further help to show main targets from a Makefile easily and pretty standard way. Will be taken into use.
You can achieve similar by writing bash scripts, but it will be mostly custom and others need to learn how to use it and extend it. Makefile gives you a standard way of writing small utilities related to all your project, and almost everybody knows how Makefile works and if not, they can learn from existing documentation.
@grep -P '^[a-zA-Z0-9_-]+:.?## .$$' $(MAKEFILE_LIST) | ...
help:
@awk -F ':|##' \
'/^[^\t].+?:.*?##/ {\
printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF \
}' $(MAKEFILE_LIST)
... but I agree, breaking expectations is somewhat bad.
Also, many shells have completion for Makefiles nowadays, though, that won't get you an additional help text.Slightly OT: I like how Rake handles this, which is what gave me the idea in the first place
help:
@eval $$(sed -r -n 's/^([a-zA-Z0-9_-]+):.*?## (.*)$$/printf "\\033[36m%-30s\\033[0m %s\\n" "\1" "\2" ;/; ta; b; :a p' $(MAKEFILE_LIST) | sort)https://github.com/ehartsuyker/node-deb/blob/ede596b2c8a07f1...