Louise: Sai una cosa? Presto avremo un bambino.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.
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!
...presto avremo un makefile... |
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 ldconfigProseguiamo: 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!