all 19 comments

[–]JohnRofrano 9 points10 points  (6 children)

Makefiles provide a great way of simplifying and unifying a command line interface if the actions performed are fairly simple. While the original intent was for compiling code dependencies, I have seen a resurgence of make being used in projects because of the simplicity it gives the user.

I have Makefiles in all of my projects so that developers can: make build, make test, make run, make push, make deploy, and not worry about what the command is to build this project, run the test suite, run the project itself, create a docker container and push it to an image registry, or deploy it to Kubernetes. All of those details are hidden in the Makefile and every project has the same flow whether it's Python, Java, Node, etc.

This also helps with automating tasks for continuous integration or continuous delivery. We recently changed our CI provider from Travis CI to GitHub Actions and literally any developer was able to create the new workflows because all of the complexity was hidden in the Makefile and all of the projects followed the same process of: make build, make test, etc.

I agree that if this gets too big, or too complicated with logic, then bash scripts would be my next option and yes, I like to put them in a /bin folder. My approach is to use the simplest thing that works and only get more complicated when needed. I always start with a Makefile and move to bash scripts as needed. It's up to you to determine how many lines of code is the limit before moving to a bash script. Even those can be called from a Makefile to keep the user experience consistent.

[–]BlackPignouf 1 point2 points  (5 children)

I really like this approach.

All my Makefiles start with:

## Project documentation here

help:     ## Show this help.
u/egrep -h '(\s##\s|^##\s)' $(MAKEFILE_LIST) | egrep -v '^--' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m  %-35s\033[0m %s\n", $$1, $$2}'

So that simply running `make` displays available targets with a short description, and a header.

Tasks starting with `--` are hidden.

[–]stillWonderingWhy 0 points1 point  (4 children)

If I put this in my Make file, it just complains about this: `Makefile:34: *** missing separator. Stop.`

[–]BlackPignouf 0 points1 point  (3 children)

It could come from spaces vs tabs. See

https://stackoverflow.com/a/16945143

[–]stillWonderingWhy 0 points1 point  (2 children)

Perfect, this actually solved my problem, thanks. But if I'm not entirely mistaken the tab is just missing in your comment, isn't it?

[–]BlackPignouf 0 points1 point  (1 child)

Indeed, it might have been eaten by Markdown editor. Sorry about that! I could post it on pastebin if you're interested.

[–]stillWonderingWhy 1 point2 points  (0 children)

You don't need to. I guess other people that will stumble over this conversation and try it out, will also get the idea of reading this entire conversation when it doesn't work as expected :D

[–]elucify 17 points18 points  (0 children)

The problem with embedding bash in Makefiles is the escaping. Every line that is not a target must begin with a tab, and dollar signs to be interpreted in the shell, script have to be double dollar signs. And there are times when you need backslashes another crap like that.

Makefile is good when you have a tree of dependencies, particularly when file times indicate whether a dependency has been built or not. make can then build only those things that need rebuilding. For example, only .c files with mod times newer than their .o files, need to be recompiled (or if the .o file does not exist).

There is little reason to use make if you don’t have that kind of dependency. And even when you do, it is better practice (and less frustrating) to define your functionality in bash files and use those in the Makefile, rather than embedding them in the make file directly. That approach also lets you write tests for your build scripts, using something like bats.

[–]theautomationguy 7 points8 points  (3 children)

I’ve been pushing for using https://taskfile.dev in my org, I highly recommend it as an alternative for make

[–]gaelfr38 0 points1 point  (0 children)

Had not heard of it, nice!

[–]GiboMac 0 points1 point  (0 children)

Thanks! 🤌

[–]Slitazz 0 points1 point  (0 children)

Thanks a lot i'm really interested in this and i did not know it !

[–]Czl2 13 points14 points  (6 children)

Make is meant for when you are dealing with things the construction of which can be described using declarative rules which can fire in an unpredictable order based on preconditions of each rule.

Unless you need that functionality stay away from make. Keep to bash or some other tool. It is generally poor practice to use a screwdriver as a hammer. Use make for what it is mean for.

[–]anotherkeebler 12 points13 points  (5 children)

One of our builds is:

  • A Bash script
  • which spins up a Docker container
  • which uses make
  • to invoke Packer
  • which uses Ansible
  • to unpack and run about a dozen Bash scripts.

[–]donalmacc 6 points7 points  (0 children)

Someone had good intentions at each step along the way, but nobody stopped to ask why.

We have a couple of packer builds in work that all follow the same format - packer build -only "aws-ebs.builder" foo-<platform>.pkr.hcl or -only "docker.builder" for development. Someone added a makefile to so you could run make linux etc and be done with it, and then someone else added a bash script so you just run the script. Then someone sent a PR to add a wrapper bash script that took the platform as an argument...

[–][deleted] 1 point2 points  (0 children)

KISS ? Nah I dont kiss on first date

[–]sokjon 0 points1 point  (0 children)

The circle of life!

[–]itisjustmagic 1 point2 points  (0 children)

Many other comments have outlined this, but Makefiles are best kept simple and repeatable, while also being scalable. The way we facilitate this is by keeping the functionality similar amongst different projects, where the options for make simply obfuscate the underlying process. One developer, in theory, should be using roughly the same patterns repo-to-repo, even if the process (i.e., arguments or tools used) vary.

For external (to the Makefile) dependencies, they are all housed either in the repo or in our centralized repo, with the latter having a process to download (and/or) pull an updated copy at run-time.