Makefile

by DonOfDen


Posted on 20 Nov 2019

Tags: Makefile automation tool


Makefile

Run and compile your programs more efficiently with this handy automation tool.

  • Makefiles are a simple way to organize code compilation.

Make is Unix utility that is designed to start execution of a makefile. A makefile is a special file, containing shell commands, that you create and name makefile (or Makefile depending upon the system). While in the directory containing this makefile, you will type make and the commands in the makefile will be executed. If you create more than one makefile, be certain you are in the correct directory before typing make.

As a makefile is a list of shell commands, it must be written for the shell which will process the makefile.

The makefile contains a list of rules. These rules tell the system what commands you want to be executed. Most times, these rules are commands to compile(or recompile) a series of files. The rules, which must begin in column 1, are in two parts. The first line is called a dependency line and the subsequent line(s) are called actions or commands. The action line(s) must be indented with a tab.

Rules

A makefile consists of “rules” in the following form:

target: dependencies
    system command(s)

A target is usually the name of a file that is generated by a program; examples of targets are executable or object files. A target can also be the name of an action to carry out, such as “clean”.

A dependency (also called prerequisite) is a file that is used as input to create the target. A target often depends on several files. However, the rule that specifies a recipe for the target need not have any prerequisites. For example, the rule containing the delete command associated with the target “clean” does not have prerequisites.

The system command(s) (also called recipe) is an action that make carries out. A recipe may have more than one command, either on the same line or each on its own line. Note the use of meaningful indentation in specifying commands; also note that the indentation must consist of a single <tab> character.

Execution

A makefile is executed with the make command, e.g. make [options] [target1 target2 ...]. By default, when make looks for the makefile, if a makefile name was not included as a parameter, it tries the following names, in order: makefile and Makefile.

Basic examples

Let’s start by printing the classic “Hello World” on the terminal. Create a empty directory coolproject containing a file Makefile with this content:

say_hello:
        echo "Hello, World!"

Now run the file by typing make inside the directory coolproject. The output will be:

$ make say_hello
echo "Hello, World!"
Hello, World!

In the example above, say_hello behaves like a function name, as in any programming language. This is called the target. The prerequisites or dependencies follow the target.

Lets see a useful Makefile with more commands, following are some shell scripts to get the version of softwore installed in the system.

# To get the version of node npm yarn
NODE=$(shell which node)
NPM=$(shell which npm)
YARN=$(shell which yarn)

# To know the OS
OS := $(shell uname)

# To get Date Month Year using shell
DATE=$(shell date +%d)
MONTH=$(shell date +%m)
YEAR=$(shell date +%Y)

# This is to get a `jq` version from package.json
CURRENT_VERSION:=$(shell jq ".version" package.json)

The following will exmaple what the Makefile consist.

Targets

@cat Makefile* | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

The above ^^ will help get all the availabe Make commands with the description folloing to it. So its kind of help command, but will run when you type just make in the command.

.DEFAULT_GOAL := explain
.PHONY: explain
explain:
    ### Welcome
    #
    ### HELP
    #
    # $$ make help
    #
    # If already installed - run the following to start the application
    #
    # $$ make start
    #
    #
    ### Targets
    #
    @cat Makefile* | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

To make is more beautiful lets generate some ASCII style test to add in make explain, search in google for “Generate Text ASCII”, or use this website. Result in the following.

___  ___      _         __ _ _      
|  \/  |     | |       / _(_) |     
| .  . | __ _| | _____| |_ _| | ___ 
| |\/| |/ _` | |/ / _ \  _| | |/ _ \
| |  | | (_| |   <  __/ | | | |  __/
\_|  |_/\__,_|_|\_\___|_| |_|_|\___|
                                    

So our make explain will be.

explain:
	### Welcome
	#  "Generate Text ASCII" from online for a good looking Makefile
	#	___  ___      _         __ _ _      
	#	|  \/  |     | |       / _(_) |     
	#	| .  . | __ _| | _____| |_ _| | ___ 
	#	| |\/| |/ _` | |/ / _ \  _| | |/ _ \	
	#	| |  | | (_| |   <  __/ | | | |  __/
	#	\_|  |_/\__,_|_|\_\___|_| |_|_|\___|
	#	                                 
	#	                                                                               
                                                        
	### HELP
	#
	# $$ make help
	#
	# If already installed - run the following to start the application 
	#
	# $$ make start 
	#
	#
	### Targets
	#
	@cat Makefile* | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

An help command to provide general information on how to use the software or commands.

help: ## help to set up the project
	@echo
	@echo "Current version: $(CURRENT_VERSION)"
	@echo
	@echo "List of commands:"
	@echo
	@echo "  make info             - display node, npm and yarn versions... if not pls install the packages"
	@echo "  make install          - install npm"
	@echo "  make start            - start the serve/application."
	@echo "  make clean            - remove node modules"
	@echo "  "

The info command get you the versions information.

info: ## Show if node, npm, yarn is installed
    @echo node version: `$(NODE) --version` "($(NODE))"
    @echo npm version: `$(NPM) --version` "($(NPM))"
    @echo yarn version: `$(YARN) --version` "($(YARN))"

Make install can be used to install dependencies for our softwar. You can edit and add related command to your project. This also explains how u can run OS dependent commands.

.PHONY: install
install: ## Makefile example for `make install` which will install all the following command
    pip3 install requests
    pip3 install jsonify

# Following is an example of how we can use OS related command in Makefile
# Install jq
ifeq ($(OS),Darwin)
    # Run MacOS commands
    brew install jq
else
    @echo "Currently we only support MacOS commands - Please install JQ."
endif

So whats wait? in some situation if you want to wait for some time during instalation, this wait command can be clubed together with othe commands for a system wait.

make setup-db will wait 60 sec before execution, this uses a shell program to sleep for the given seconds. check wait.sh file for more information.

# This is one of the useful things we can do in makefile to wait for a command after execution
wait%:
    @echo
    @echo "A wait script was planted to overcome user creation errors.!"
    @echo "(\_/)"
    @echo "(o.o)"
    @echo "(___)0"
    sh wait.sh $*
# The wait can be triggered from here and a value can be sent
setup-db: wait60 ## To install and wait.
    echo "DONE! Waiting!!"

My wait.sh file

#!/bin/bash

echo "Sleeping $1 seconds"
sleep $1
echo "$1 are over!"

Yu can also club system command(s)

# If you want to run two command
two-command:
    cd python && python3 run.py

Default

We can also use .DEFAULT_GOAL to run defult command when running make alone

.DEFAULT_GOAL := explain
.PHONY: explain
explain:

make clean this can be used to delete local file systems

# Installation
clean: ## Clean the local filesystem
    git clean -fd

Another useful make command, make check-deps, Checks if our dependencies are out of date.

.PHONY: check-deps ## Checks if our dependencies are out of date
check-deps:
    composer.phar outdated

What is the purpose of .PHONY in a makefile?

By default, Makefile targets are “file targets” - they are used to build files from other files. Make assumes its target is a file, and this makes writing Makefiles relatively easy:

foo: bar
  create_one_from_the_other foo bar

However, sometimes you want your Makefile to run commands that do not represent physical files in the file system. Good examples for this are the common targets “clean” and “all”. Chances are this isn’t the case, but you may potentially have a file named clean in your main directory. In such a case Make will be confused because by default the clean target would be associated with this file and Make will only run it when the file doesn’t appear to be up-to-date with regards to its dependencies.

These special targets are called phony and you can explicitly tell Make they’re not associated with files, e.g.:

.PHONY: clean
clean:
  rm -rf *.o

Now make clean will run as expected even if you do have a file named clean.

In terms of Make, a phony target is simply a target that is always out-of-date, so whenever you ask make <phony_target>, it will run, independent from the state of the file system. Some common make targets that are often phony are: all, install, clean, distclean, TAGS, info, check.

Please refer Makefile for a complete template.

Share your thoughts via twitter @aravind_kumar_g ¯\_(ツ)_/¯