Makefile

Makefile

Sovint, quan iniciem projectes grans, no pensem en tot el temps que dediquem a arrencar els projectes, instal·lar-los cada cop, reiniciar les màquines virtuals, còrrer migracions,... i no sols és temps sinó espai a memòria. La nostra.

Els Makefiles son una gran ajuda a aquestes dues tasques. La d'alliberar una mica d'espai a la nostra memòria, i a la de pujar un petit grau la velocitat a la que fem les coses. A part, es clar, de posar ordre, pau i documentació de com iniciar un projecte.

Així doncs veiem qué son, pequè serveixen i com fer els nostres propis Makefiles! 💪

Qué es el Makefile?

Abans d'explicar que es el makefile, necessitem entendre qué és make.

Make, es una eina que controla la generació d'executables o altres arxius, no font, d'un programa. Ens otorga la possibilitat de, donat un arxiu anomenat makefile, dirigir, automatitzar o executar, aquelles comandes que volguem per tal de compilar, executar o printar aquelles accions o ordres que nosaltres li indiquem.

Així doncs, el makefile, ens ajudarà a automatizar ⚙️ algunes tasques que, d'altra forma, hauríem d'executar a mà de forma ordenada.

Documentació oficial de make.

També hem de saber, que make només el trobarem a sistemes operatius basats en UNIX 🐧, es a dir que si esteu a un Windows, oblideu-vos de probar la comanda "make" al terminal. A menys que estigueu emulant alguna màquina virtual.

El meu primer Makefile

Ok, ok Oriol, ja ens queda clar de què va això dels makefile... Podem automatizar processos dels nostres projectes. Show me the money 💰🧐 Posa'ns exemples, i veurem millor el potencial.

Molt bé, doncs anem a pel primer exemple senzill per veure un cas pràctic.

Si escribim "make" al terminal, sense res mes en aquell directori en el que estiguem, i si tenim make al nostre sistema, veurem un missatge com el següent:

make: *** No targets specified and no makefile found.  Stop. 

Doncs el que podem fer es crear un arxiu anomenat "makefile" al directori on tinguem l'arrel del nostre projecte on volguem automatitzar algún procés, o a cualsevol directori on volguem probar les bondats del makefile. Un cop creat, hi copiem el contingut:

# Això és un comentari que serà totalment ignorat
run:
	@echo "Executa la primera comanda que hem definit"

Abans de @echo ha de ser un tabulador o veurem un error.

Si ara escribim make al terminal, veurem que s'ha escrit el text:

Executa la primera comanda que hem definit

Perfecte! Ja hem executat la primera ordre del makefile... Com veureu, no ha fet falta cridar explícitament l'ordre "run" que hem definit. Això es per que per defecte, i si no diem el contrari, la primera ordre que es trobi serà la primera que executi. Si escribim make run veurem el mateix resultat.

Ara bé... que passa si tenim més d'una ordre?

# Això és un comentari que serà totalment ignorat
run:
        @echo "Executa la primera comanda que hem definit"

stop:
        @echo "Executa la comanda STOP"

Si escribim només make igual que abans veurem l'execució de la primera ordre. Però si escribim make stop veurem com s'executa la segona. El que vol dir que la comanda "make" ens permetrà llançar les ordres de forma individual sempre que volguem. Fins i tot podrem canviar el comportament per defecte, per que no executi la primera ordre que trobi. Ho veurem més endavant.

Ordres en seqüència

També podrem fer que una sola ordre sigui la que executi una seqüència d'altres ordres, de froma que automatitzi un procés més complexe. Veiem un breu exemple:

# Això es un comentari que serà totalment ignorat
sec: run stop
        @echo "Fi de la seqüència"
run:
        @echo "Executa la primera comanda que hem definit"

stop:
        @echo "Executa la comanda STOP"

Si executem make sec o solament make , veurem com executa, en ordre les comandes que li hem llistat tot seguit dels dos punts, i a continuació el seu propi contingut, donant-nos un resultat com el següent:

Executa la primera comanda que hem definit
Executa la comanda STOP
Fi de la secuencia

Makefile a fons

Ara que entenem i hem vist el més bàsic dels makefiles, veiem els detalls i quines coses chules podem arribar a fer.

@echo, per que l'arroba abans del echo? Això ens evita que la línia sencera es printi al terminal. Si en canvi volem veure l'ordre que s'ha executat, no li posem l'arroba i llestos.

.DEFAULT_GOAL es una de les variables especials que podem fer servir als makefile. Aquesta en concret, com podreu deduïr, serà la que ens ajudarà a definir quina ordre es la que volem que s'executi per defecte. Amb això, podem aconseguir coses com:

.DEFAULT_GOAL:=help

# Això es un comentari que serà totalment ignorat
help:
        @echo "Aquest makefile conté les següents ordres: sec, run, stop"

sec: run stop
        @echo "Fi de la secuencia"
run:
        @echo "Executa la primera comanda que hem definit"

stop:
        @echo "Executa la comanda STOP"

Variables es una de les altres característiques útils que podem definir. Per exemple, per definir versions de software que volguem instal·lar a diferents llocs, i canviar-ho a un de sol:

mysqlv := "5.7"

all: install-master install-slave

install-master:
    @echo "Procés d'instal·lació 1..."
    @echo "Instal·lant MySQL " $(mysqlv)
    
install-slave:
    @echo "Procés d'instal·lació 2..."
    @echo "Instal·lant MySQL " $(mysqlv)

Conclusions

Per acabar, us deixaré un exemple d'un dels meus makefiles, agafeu-lo, distribuiu-lo, feu-lo servir, editeu-lo,... allò que volgueu. Per a mi els makefile m'ajuden MOLT a l'hora de organitzar els meus projectes. Espero que a vosaltres també!

Si voleu més informació útil, us deixo aquest enllaç.

Fins aviat! 🙋‍♂️

.DEFAULT_GOAL := help
.PHONY: all

help:  ## Display this help
	@awk 'BEGIN {FS = ":.*##"; printf "\n\033[31m NOM DEL PROJECTE MakeFile\033[0m\n\nUsage:\n  make \033[36m<target>\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf "  \033[36m%-19s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

##@ [Application] Install / Utils
install: build migrations seeders    ## This command should install all the project into your machine
	@echo "Installing process..." ## Copiar el .env.example a .env

build: ## Build the image, install composer and generate a key
	@docker-compose up --build -d
	@docker-compose exec php composer install
	@docker-compose exec php php artisan key:generate

shell:  ## Open a terminal of PHP running machine
	@docker-compose exec php ash

db:  ## Open MySQL pod entering to MySQL instance
	@docker-compose exec mysql bash -c "mysql -uroot -proot"

migrations:  ## Run all migrations
	@docker-compose exec php php artisan migrate

seeders:  ## Run seeders to fill database for first time
	 @docker-compose exec php php artisan db:seed

db-refresh:  ## Delete all database and re-do migrations and seeders
	@docker-compose exec php php artisan migrate:refresh
	@docker-compose exec php php artisan db:seed

##@ [Docker] Docker container commands
up: ## Initialize the project with all the necessary things to work in local environment
	@echo "Initializing project"
	@docker-compose up -d
	@make migrations
	@make seeders
	@echo "Ready to fight!"

down:   ## Shut down all the project
	@echo "Stopping project"
	@docker-compose down
	@echo "See you tomorrow!"

down-images: ## Stop and/or destroy the containers and their volumes (including named volumes)
	@echo "Stopping project"
	@docker-compose down -v
	@echo "See you tomorrow!"

down-purge: ## Delete everything, including images and orphan containers
	@echo "Stopping and removing project images and volumes"
	@docker-compose down -v --rmi all --remove-orphans
	@echo "See you tomorrow!"

##@ [Testing] Test proposal commands
test-unit: ## Run only unit tests
	@docker-compose exec php ./vendor/bin/phpunit --colors=always --testsuite=Unit