Makefile e DevOps, una breve guida

27 February 2021, Tags: ComputerScience

💻 Introduzione

Oltre ad Ansible, per motivi lavorativi mi sono messo a giocare con infrastrutture e architetture basate su container docker in ambito docker-compose. Lo so che chi sta leggendo penserà che attualmente esiste solo kubernates per queste cose però mi sono dovuto ricredere anche io su questo tema.

Docker-compose è un ottimo strumento, direi un passo abilitante e che consiglio a tutti per entrare nell’ottica non solo dei microservizi, intesi nello specifico come ristrutturazione di monoliti software ma anche per iniziare a spostarsi nelle competenze devops, nella parte di netowrking e infrastrutture.

Ma bando alle ciance, perchè Makefile? Uno strumento che chi come me ha sempre visto di fianco alle guide per compilarsi qualche pacchetto linux quando sulla tua distro preferita, nel mio caso Slackware… perchè?

Beh, ultimamente make è tornato in voga nell’ambito della gestione dei container per alcuni pregi:

  • è facilmente portabile
  • è uno strumento per eseguire task ripetitivi (vedi compilazione)
  • non necessita di particolari dipendenze, anzi è out-of-the-box nel caso di sistemi GNU\Linux e affini.
  • descrive le dipendenze tra i vari target

Storia

Make è uno strumento scritto nel 1976 da Stuart Feldman nei Bell Labs. Serve per compilare codice C / C++ e può essere paraganato a Maven, Ant, Gradle per Java, (i linguaggi interpretati come Javascript e Python non necessitano di questi strumenti).

Makefile

Makefile è il file che contiene le istruzioni da far eseguire a make. E’ come il pom.xml per maven. Per provare il tutto basterà quindi avere un terminale linux e un editor di testo.

Rules

Essenzialmente un Makefile descrive una serie di regole che hanno questa struttura base:

target1 target2 target3: prereq1 prereq2
 commands
 commands
 commands

dove:

  • target1 sono i file di output da produrre
  • prereq1 … prereqn sono gli n-file dai quali target dipende. Sono le dipendenze di target
  • commands sono i comandi necessari per produrre target

Spesso troverete i task più generici in testa al file, mentre man mano che si hanno task più complessi nel fondo in un orientamento top-down

Hello World

L’esempio più semplice è il classico Hello World

hello:
 @echo "Hello World"

se eseguiamo il comando make hello vedremo il risultato atteso.

Hello World - Attenzione

Se invece proviamo questo esempio:

hello:
 touch hello

e lo eseguiamo due volte vedremo che la seconda esecuzione non verrà eseguita perchè abbiamo creato un file chiamato hello che è il target della rule hello.

Rule di default

La rule di default può essere impostata aggiungendo all’inizio del file questa costante riservata

.DEFAULT_GOAL := hello

Costanti

Makefile permette di dichiarare le costanti. Solitamente a inizio del file makefile attraverso la seguente sintassi. Nell’esempio che segue dichiaro la costante ECHO_HELLO_WORLD. Le costanti sono per convenzione dichiarati tutti in maiuscolo

.DEFAULT_GOAL := echo1

ECHO_HELLO_WORLD := @echo "Hello World"

echo1:
 $(ECHO_HELLO_WORLD)

Variabili

Le variabili sono definite come nell’esempio che segue:

.DEFAULT_GOAL := echo1

x = hello
y = world

echo1:
 @echo $(x) $(y)

PHONY targets

Come abbiamo visto, make nasce per compilare dei file, ma per i nostri scopi spesso i target che scriviamo non producono file e vogliamo che siano eseguiti sempre. Per fare questo possiamo usare il target PHONY aggiungendo i nostri target come prerequisiti di questo target particolare.

.PHONY: echo1

echo1:
 @echo Hello World

Funzioni

Un’altro costrutto utile è quello della dichiarazione di funzioni. La sintassi per richiamare una funzione è la seguente:

$(function-name arg1[, argn])

$(call function-name,arg1,...,argn)

mentre la sua dichiarazione è la seguente:

define nome_funzione
 corpo della funzione
 posso usare $1 per accedere al primo parametro
 posso usare $2 per accedere al secondo parametro
 e via dicendo
endef

questo un esempio completo:

.DEFAULT_GOAL := scoping_issue

define parent
 echo "parent has two parameters: $1, $2"
 $(call child $1)
endef

define child
 echo "child has one parameter: $1"
 echo "but child can also see parent's second parameter: $2!"
endef

scoping_issue:
 @$(call parent one two)

Tips

Formattazione

L’identazione è configurata di default con i TAB, per modificare questo aspetto usare la variabile .RECIPEPREFIX

Shell di default

Si può impostare la shell di default con la variabile SHELL

SHELL=/bin/bash

cool:
    echo "Hello from bash"

Utilizzare file .env

Con docker e docker-compose ci siamo abituati a sfruttare al massimo le variabili di ambiente, per configurare a parità di file ambienti di distribuzione diversa. I Makefile sfruttano le variabili attualmente in uso nella shell. Un trucco per poter avere anche in questo contesto i nostri amati .env è l’utilizzo di questo snippet:

ifneq (,$(wildcard ./.env))
    include .env
    export
endif

Conclusione

Questo post non vuole essere una guida esaustiva anzi come il resto del blog è un appunto personale che spero possa essere utile anche per qualcun’altro. Ovviamente andrò ad integrare in futuro sullo stesso post se dovessi utilizzare altri costrutti di make.

Da questo primo utilizzo posso dire che i Makefile possono essere utilizzati per automatizzare i comandi più ricorrenti in ambito docker ma anche in fase di sviluppo può tornare utili. Sono molto comodi per aprire vscode con parametri particolari e uguali per tutto il team dev o per wrappare i comandi git per esempio in modo da garantire che prima di una commit vena eseguito un pull del repository per esempio.

Conto di ritornare su questo post perchè sono sicuro di aver solo scalfito le potenzialità di questo strumento.

Links

Non aspettatetvi dei problemi, perchè tendono a non deludere le aspettative. (Napoleon Hill)


Profile picture

Scritto da Giuseppe Caliendo con amore 💖 dall'Italia. [Twitter] [LinkedIn][Github][Tutti i tag]

© 2019 - 2024, Built with Gatsby