Make

form something by putting parts together or combining substances.

if you have some complex and esoteric shell commands that you have to run on a regular basis, it can be convenient to just package them up somewhere, so you don't have to remember them or type them out every time.

Why, I don't use Makefiles more often? Even though I know I should. One of the reason may be because, as an Emacs user, automating simple tasks is funny using Emacs Lisp. Following are simple elisp functions I use to compile a C file, nothing extraordinary:

(defun my-c-save-compile ()
  "Save, and compile c file."
  (interactive)
  (save-buffer)
  (compile
   (format "cc -Wall -Wextra -pedantic -std=c11 -g %s -o ./bin/%s"
           (buffer-file-name)
           (file-name-base (buffer-file-name)))))

(defun my-c-run ()
  "Run c code."
  (interactive)
  (compile (format "./bin/%s"
                   (file-name-base (buffer-file-name)))))


Another reason I don't use Makefiles more often could be, the Bourne Again Shell. I find it easier to quickly write shell aliases for repetitive tasks. The following is some aliases I sometimes use whenever I'm doing Django development:

#!/bin/sh

# my weird aliases
alias djo_runserver="python manage.py runserver"
alias djo_shell="python manage.py shell"
alias djo_shellp="python manage.py shell_plus"
alias djo_make="python manage.py makemigrations"
alias djo_mig="python manage.py migrate"
alias djo_csu="python manage.py createsuperuser"
alias djo_smtpd="python -m smtpd -n -c DebuggingServer localhost:1025"

Make do have a reputation for being complex. Indeed, it can be very esoteric. The full manual is a 183 pages. Fortunately, you can ignore most of this, Make can be used in a very simple and incredibly useful way.

The following, is a Makefile I sometimes use in replacement of the shell aliases above:

# Makefile for (the project)
#
# Nsukami
# November 2016

SHELL := /bin/sh
PROJECT := tas
PYTHON_BIN := $(VIRTUAL_ENV)/bin

DEV_SETTINGS = $(PROJECT).settings.dev
PROD_SETTINGS = $(PROJECT).settings.prod
TEST_SETTINGS = $(PROJECT).settings.test
BASE_DIR = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
PMP = python manage.py

# if not on prod server, use dev settings
ifneq ($(shell uname -n),phobos)
    SETTINGS = $(DEV_SETTINGS)
else
    SETTINGS = $(PROD_SETTINGS)
endif


# target: all - Default target. Does nothing.
all:
    @echo "Nothing to do by default. Try 'make help'"

# target: help - Show list of targets + description
help:
    @egrep "^# target:" [Mm]akefile

# target: init - Install that a dev needs to get up and running.
init: bootstrap.sh
    cd $(BASE_DIR) && sudo ./bootstrap.sh

# target: clean - remove all useless files
clean: clean-pyc clean-build

# target: clean-pyc - remove all auto generated files
clean-pyc:
    find . -name '*.pyc' -exec rm --force {} +
    find . -name '*.pyo' -exec rm --force {} +
    find . -name '*~' -exec rm --force  {} +

# target: clean-build - remove all auto generated folders
clean-build:
    -rm --force --recursive build/
    -rm --force --recursive dist/
    -rm --force --recursive htmlcov
    -rm --force --recursive .coverage
    -rm --force --recursive *.egg-info

# target: translate - calls the "makemessages" django command
translate:
    cd $(BASE_DIR) && $(PMP) makemessages --settings=$(SETTINGS) -a

# target: test - calls the "test" django command
test:
    $(PMP) test --settings=$(TEST_SETTINGS)

# target: run - calls the "runserver" django command
run:
    $(PMP) runserver --settings=$(SETTINGS)

# target: update - install (and update) pip requirements
update:
    pip install -U -r $(BASE_DIR)/requirements/dev.txt

# target: collect - calls the "collectstatic" django command
collect:
    $(PMP) collectstatic --settings=$(SETTINGS) --noinput

# target: rebuild - rebuild tables from models
rebuild:
    $(PMP) makemigrations --settings=$(SETTINGS)
    $(PMP) migrate --settings=$(SETTINGS)

# target: deploy - let make take care of deployment
deploy:
    # Should I call fabric here?


.ONESHELL:
.PHONY: all help translate test clean update collect rebuild run clean-pyc clean-build clean init

Finally, the other reason, and probably the main reason I don't use Makefiles more often, it's because of Fabric. Following is a fabfile more or less doing the same thing as the Makefile above:

#!/usr/bin/env python
# -*- coding: utf-8 -*- #

""" fabric commands """

from fabric.api import env, sudo, cd, local, task, run
import fabric.contrib.project as project
import os

# Local path configuration (can be absolute or relative to fabfile)
env.hosts = ['localhost']
env.use_ssh_config = True
env.ssh_config_path = '/home/nsukami/.ssh/config'
env.owner = 'nsukami'

PROJECT = "tas"

DEV_SETTINGS = PROJECT + ".settings.dev"
PROD_SETTINGS = PROJECT + ".settings.prod"
TEST_SETTINGS = PROJECT + ".settings.test"
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
PMP = "python manage.py "



def uname():
    """ Prints information about the host. """
    local("uname -n")

if uname() is not "phobos":
    SETTINGS = DEV_SETTINGS
else:
    SETTINGS = PROD_SETTINGS

@task
def init():
    "init - Install that a dev needs to get up and running."
    with cd(BASE_DIR):
        sudo("./bootstrap.sh")

@task
def clean():
    "clean - remove all useless files"
    clean_pyc()
    clean_build()

def clean_pyc():
    "# clean-pyc - remove all auto generated files"
    local("find . -name '*.pyc' -exec rm --force {} +")
    local("find . -name '*.pyo' -exec rm --force {} +")
    local("find . -name '*~' -exec rm --force  {} +")

def clean_build():
    "clean-build - remove all auto generated folders"
    local("rm --force --recursive build")
    local("rm --force --recursive dist")
    local("rm --force --recursive htmlcov")
    local("rm --force --recursive .coverage")
    local("rm --force --recursive *.egg-info")

@task
def translate():
    "translate - calls the 'makemessages' django command"
    with cd(BASE_DIR):
        cmd = PMP + "makemessages --settings={} -a".format(SETTINGS)
        local(cmd)

@task
def test():
    "test - calls the 'test' django command"
    cmd = PMP + "test --settings={}".format(SETTINGS)
    local(cmd)

@task
def runserver():
    "run - calls the 'runserver' django command"
    cmd = PMP + "runserver --settings={}".format(SETTINGS)
    local(cmd)

@task
def update():
    "update - install (and update) pip requirements"
    cmd = "pip install -U -r {}/requirements/dev.txt".format(BASE_DIR)
    local(cmd)

We see that Fabric can be used as a build tool even though it isn't one. It is easy to read and write, especially for a Python developer. The drawback may be, it does require Python to run. Seriously, who cares?

New year resolution: using Makefiles more often.

More on the topic: