Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

venerdì 22 giugno 2018

Prendi il makefile e scappa
come scrivere un makefile universale - pt.2

Louise: Sai una cosa? Presto avremo un bambino.
Virgil: Scherzi...
Louise: No! Avremo proprio un bambino: me l'ha detto il dottore, è sicuro. Sarà il mio regalo per Natale.
Virgil: Ma a me bastava una cravatta!
A Virgil Starkwell bastava una cravatta... a noi invece basta un bel makefile universale. Ricordate quel vecchio post in cui ve ne ho proposto uno? Non ve lo ricordate? Subito a rileggerlo e poi tornate qua.
...presto avremo un makefile...
Rieccoci (e, piccola premessa: alcuni punti di questo post sono parzialmente copiati dal mio vecchio post... posso copiare me stesso, no?). Se avete riletto il post sapete già che questo sarà un post veloce, e non propriamente sul C: ho pensato che era ora di scrivere e proporvi una versione estesa e perfezionata del vecchio makefile universale: rispetto all'originale questo usa maggiormente le variabili, così il codice è più pulito e leggibile (lo stile, prima di tutto!) e aggiunge una funzionalità molto interessante: la creazione di una shared library (una .so, per gli amici). Vediamo schematicamente quali sono i passi da eseguire (su un Linux della famiglia Debian) per creare e usare una shared-lib:
1. creare una directory per condividere la shared-lib, ad esempio: 
       /usr/share/pluto/lib
2. modificare (come vedremo tra poco) il makefile per generare la libreria 
   (che chiameremo "libmyutils.so") e copiarla in "/usr/share/pluto/lib".
3. aggiungere in "/etc/ld.so.conf.d" un nuovo file "libmyutils.conf" che 
   contiene le seguenti due linee (la prima è solo un commento): 
       # libmyutils.so default configuration
       /usr/share/pluto/lib
4. rendere disponibile la nuova shared-lib eseguendo: 
       sudo ldconfig
Proseguiamo: supponiamo di usare lo stesso progetto dell'altra volta (si chiamava pluto). I nostri file sono organizzati in una maniera canonica, questa volta in quattro directory (l'altra volta erano tre): pluto, lib, libmyutils e include. La nuova directory è libmyutils, e contiene i sorgenti della shared-lib che vogliamo creare. Nella directory pluto troviamo il main() e il makefile, nella directory lib troviamo gli altri sorgenti dell'applicazione pluto e, infine, nella directory include troviamo gli header-files comuni a main(), lib e libmyutils. Vediamo il nuovo makefile:
# variabili
CC = gcc
CPPFLAGS = -I../include -g -Wall -Wshadow -pedantic -std=c11
CPPFLAGS_UTI = -fpic -I../include -g -Wall -Wshadow -pedantic -std=c11
LDFLAGS = -lmyutils -std=c11
LDFLAGS_UTI = -shared -fpic -lcurl -std=c11

# sorgenti, oggetti e dipendenze
SRCS = $(wildcard *.c)
SRCS_LIB = $(wildcard ../lib/*.c)
SRCS_LIB_UTI = $(wildcard ../libmyutils/*.c)
OBJS = $(SRCS:.c=.o)
OBJS_LIB = $(SRCS_LIB:.c=.o)
OBJS_LIB_UTI = $(SRCS_LIB_UTI:.c=.ou)
DEPS = $(SRCS:.c=.d)
DEPS_LIB = $(SRCS_LIB:.c=.d)
DEPS_LIB_UTI = $(SRCS_LIB_UTI:.c=.d)

# tutti i target
all: libmyutils pluto

# creazione del target file eseguibile
pluto: $(OBJS) $(OBJS_LIB)
 $(CC) $^ -o $@ $(LDFLAGS) -L/usr/share/pluto/lib

# creazione della shared-lib libmyutils.so
libmyutils: $(OBJS_LIB_UTI)
 $(CC) $^ -o libmyutils.so $(LDFLAGS_UTI)
 mv libmyutils.so /usr/share/pluto/lib

# creazione degli object file (per la applicazione e la shared-lib)
%.o: %.c
 $(CC) -MMD -MP $(CPPFLAGS) -c $< -o $@

%.ou: %.c
 $(CC) -MMD -MP $(CPPFLAGS_UTI) -c $< -o $@

# direttive phony
.PHONY: clean

# pulizia progetto ($(RM) è di default "rm -f")
clean:
 $(RM) $(OBJS) $(OBJS_LIB) $(OBJS_LIB_UTI) $(DEPS) $(DEPS_LIB) $(DEPS_LIB_UTI)

# creazione dipendenze
-include $(DEPS) $(DEPS_LIB) $(DEPS_LIB_UTI)
Come vedete il nuovo makefile presentato è uno stretto parente di quello vecchio e continua ad essere veramente semplice e universale: fa tutto quello che serve, compresa la generazione dei file di dipendenza dagli header, e possiamo usarlo per qualsiasi progetto, indipendentemente dal numero di file (le directory lib e include potrebbero essere vuote oppure contenere centinaia di file). Possiamo aggiungere e togliere sorgenti e header e ricompilare senza modificare una sola linea del makefile, perché lui si adatta automaticamente a quello che trova nelle directory del progetto: cosa vogliamo di più?

Aggiungo qualche piccolo dettaglio sui blocchi (commentati) che compongono il makefile universale:

# variabili
Qua si mettono le variabili che vengono usate nel resto del makefile. Notare che in CPPFLAGS e LDFLAGS sono contenute tutte le opzioni di compilazione e link necessarie nelle fasi di creazione della applicazione, mentre che in CPPFLAGS_UTI e LDFLAGS_UTI sono contenute quelle relative alla shared-lib). Non mi dilungherò sul significato delle singole opzioni: magari potrebbero essere l'argomento di un prossimo post... comunque le opzioni che ho usato sono decisamente "universali". Se si usa qualche libreria esterna si può aggiungere qui: nell'esempio (ricordarsi: è solo un esempio!) ho scritto che pluto usa libmyutils (con il comando -lmyutils in LDFLAGS) e ho scritto che libmyutils usa la libreria open-source libcurl (con il comando -lcurl in LDFLAGS_UTI). Notare che per compilare e linkare la shared-lib si usano due direttive fondamentali: -fpic (in compilazione e link) e  -shared (solo in link). E ribadisco: -fpic si usa sia in compilazione che in link: questo è un dettaglio che molte delle guide che si trovano in rete omettono e può essere una possibile causa di strani malfunzionamenti di una shared-lib.

# sorgenti, oggetti e dipendenze
Qua ci sono le direttive che usa internamente il programma make per decidere come e dove cercare sorgenti, oggetti e dipendenze.

# tutti i target
Qua ci sono gli obiettivi di creazione: nel nostro caso la libreria libmyutils e l'applicazione pluto: il comando make "da solo" esegue entrambi gli obiettivi (la parola chiave è "all"), ma si può bypassare questo eseguendo, ad esempio, "make libmyutils" che crea solo la shared-lib.

# creazione del target file eseguibile
Qua si mette il comando per linkare i file oggetto creati e produrre il file eseguibile finale. Notare che con la direttiva -L/usr/share/pluto/lib indichiamo al linker dove si trova la libreria libmyutils.so. Notare anche che questa direttiva serve solo a livello linker, mentre, a livello esecuzione delle applicazioni che usano la nostra shared-lib, servono i passi della lista descritta all'inizio del post (in particolare i passi 3 e 4).

# creazione della shared-lib libmyutils.so
Qua ci sono le istruzioni per la creazione della shared-lib libmyutils.so e per spostarla (col comando "mv") nella directory destinazione.

# creazione degli object file (per la applicazione e la shared-lib)
Qua si mette il comando per compilare ogni sorgente e creare il file oggetto corrispondente, attivando (attraverso le variabili definite all'inizio) tutte le opzioni del compilatore che ci servono.

# direttive phony
Qua si mettono tutte le direttive phony (è un po' lungo da spiegare: guardate il link, che è chiarissimo).

# pulizia progetto ($(RM) è di default "rm -f")
Qua si mette il comando di cancellazione degli oggetti per, eventualmente, forzare una successiva ricompilazione completa.

# creazione dipendenze
Qua si mette il comando per generare i file di dipendenza che ci permettono di ricompilare solo quello che serve quando modifichiamo un header-file.

Credo che per oggi possa bastare... fatevi un piccolo progetto di prova (ad esempio usando funzioni semivuote che scrivono solo "Ciao, sono la funzione xyz") e provate il nuovo makefile universale: scoprirete che è veramente facilissimo da usare!

Ciao e al prossimo post!

Nessun commento:

Posta un commento