Laboratorio di Sistemi Operativi Tool per la programmazione
Alberto Montresor Renzo Davoli
Copyright © 2004-2005 Alberto Montresor, Renzo Davoli
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be found at:
http://www.gnu.org/licenses/fdl.html#TOC1
Sommario
Come gestire progetti di programmazione molto grandi
• Come organizzarli
• Come compilarli
• Come lavorare in cooperazione
Nota:
• Con un'enfasi sul C, ma i concetti valgono anche per altri linguaggi di
programmazione
Come organizzare un progetto
Ovviamente:
• Programmi di grandi dimensioni non possono essere contenuti in un file singolo
Nel C
• vi sono tecniche per semplificare gestione di file multipli
• non sono "enforced": sono lasciate al programmatore
Un programma grande è diviso in moduli:
• i file .h contengono le funzioni prototipo del modulo ed eventuali costanti
• i file .c contengono le definizioni di funzioni del modulo
• i moduli sono compilati separatamente
• viene generato un eseguibile tramite linking dei moduli
Esempio: sample.c
#include <stdio.h>
#include "my_math.h"
int main() {
int a, b, c;
puts("Input three numbers:");
scanf("%d %d %d", &a, &b, &c);
printf("The average of %d %d %d is %f.\n", a,b,c,average(a,b,c));
return 0;
}
Esempio - Modulo my_math
/* my_math.h */
#define PI 3.1415926 float average(int x,
int y, int z);
float sum(int x, int y, int z);
/* my_math.c */
#include "my_math.h"
float average(int x, int y, int z)
{
return sum(x,y,z)/3;
}
float sum(int x, int y, int z) {
return x+y+z;
}
Compilare questo semplice programma
Per generare my_math.o
• Abbiamo bisogno di my_math.c e my_math.h
• gcc –c my_math.c
Per generare sample.o
• Abbiamo bisogno di sample.c e my_math.h
• gcc –c sample.c
Per generare l'eseguibile
• Abbiamo bisogno di my_math.o e sample.o
• gcc –o sample sample.o my_math.o
Come utilizzare make
I programmi che consistono di molti moduli sono impossibili da mantenere manualmente
Si crea un Makefile
# Makefile for the sample sample: sample.o my_math.o
cc –o sample sample.o my_math.o sample.o: sample.c my_math.h
cc –c sample.c
my_math.o: my_math.c my_math.h cc –c my_math.c
clean:
rm sample *.o core Target
Target Dipendenze
Comandi Indentazioni
con tab, non spazi
Makefile
Un makefile consiste in un insieme di regole del tipo:
target ... : prerequisites ...
command ...
...
Target:
• il nome di un file da generare
• un'azione da svolgere
Prerequisiti:
• uno o più file utilizzati come input per creare il file target
Regole
• una o più azioni da eseguire
Nota:
• la prima regola è quella di
"default", o radice
Come utilizzare make
Come?
• Si salva il file con il nome Makefile o makefile nella stessa directory contenente i file
• Si lancia l'utility make
Make
• trova il Makefile
• verifica le regole e le dipendenze e rigenera i file per cui è necessario un update
Esempio:
• Se solo sample.c è stato modificato:
• cc –c sample.c
• cc –o sample sample.o my_math.o
Come utilizzare make
Per rimuovere tutti i file generati
• make clean
Per ricompilare, è possibile:
• Rimuovere tutti i file generati e ricompilare
• make clean ; make
• Oppure è possibile cambiare la data di ultima modifica:
• touch my_math.h ; make
• La data di ultima modifica di my_math.h prende l'ora corrente
• Nota: questo perchè tutti i file dipendono da my_math.h
Utilizzare make con directory multiple
Con l'aumentare del numero di file .c
• diventa difficile controllare tutto con un unico makefile
• conviene utilizzare più makefile, uno per modulo
Un programma viene organizzato in questo modo:
• una directory per modulo
• file header:
• una directory per tutti i file .h
• oppure: file .h di un modulo nel modulo stesso
• Makefile nella directory radice per la creazione dell'eseguibile/libreria
• Makefile in ogni modulo dirigono la creazione dei file .o
Un esempio di Makefile
Si consideri un programma C composto da
• una struttura dati a coda queue
• queue_types.h, queue_interface.h, queue.c
• una struttura dati a stack stack
• stack_types.h, stack_interface.h, stack.c
• un modulo principale
• main.c
Organizzazione
• Tre sottodirectory:
stack, queue, include
• Tutti i file .h in include
stack queue include
main.c
Makefile Makefile
s_t.h stack.c Makefile
stack.c
Un esempio di Makefile
stack contiene stack.c e il seguente Makefile :
all: stack.o
stack.o: stack.c \
../include/stack_types.h \ ../include/stack_interface.h gcc -I../include -c stack.c
clean:
rm -f *.o
Note: opzione -I di gcc
• Specifica uno o più path in cui cercare per trovare file .h
• Path multipli separati da virgole ","
Un esempio di Makefile
queue contiene queue.c e il seguente Makefile :
all: queue.o
queue.o: queue.c \
../include/queue_types.h \ ../include/queue_interface.h gcc -I../include -c queue.c
clean:
rm -f *.o
Un esempio di Makefile
main contiene main.c e il seguente Makefile :
all: main.o stackdir queuedir
gcc -o main main.o ../stack/stack.o \ ../queue/queue.o
main.o: main.c include/*.h gcc -Iinclude -c main.c
stackdir:
cd stack ; make all
queuedir:
cd queue ; make all
Un esempio di Makefile
clean:
rm -f *.o main core cleanall:
rm -f *.o main core cd stack ; make clean cd queue ; make clean
Nota:
• Ogni riga di comando viene eseguita tramite una subshell
• Come viene gestita la directory corrente
• Cosa succede se una directory manca?
Utilizzazione delle macro nei makefile
Macro nei Makefile
• Utilizzate per semplificare la modifica dei Makefile
• Utilizzate per ridurre la dimensione dei file semplificando espressioni ripetute
Sintassi
• Definizione: name = value
• Riferimenti: $(name) o ${name}
Un esempio di Makefile, rivisitato
Makefile in stack:
CC = gcc
HDIR = ../include INCPATH = -I$(HDIR)
DEPH = $(HDIR)/stack_types.h \ $(HDIR)/stack_interface.h SOURCE = stack.c
OBJECTS = stack.o all: $(OBJECTS)
stack.o: $(SOURCE) $(DEPH)
$(CC) $(INCPATH) -c $(SOURCE) clean:
rm -f *.o
Un esempio di Makefile, rivisitato
Makefile in queue
CC = gcc
HDIR = ../include INCPATH = -I$(HDIR)
DEPH = $(HDIR)/queuetypes.h \ $(HDIR)/queueinterface.h SOURCE = queue.c
OBJECTS = queue.o all: $(OBJECTS)
queue.o: $(SOURCE) $(DEPH)
$(CC) $(INCPATH) -c $(SOURCE) clean:
rm -f *.o
Un esempio di Makefile, rivisitato
Makefile per main
CC = gcc
HDIR = include INCPATH = -I$(HDIR)
DEPH = $(HDIR)/queue_types.h \
$(HDIR)/queue_interface.h \ $(HDIR)/stack_types.h \
$(HDIR)/stack_interface.h OBJECTS = stack/stack.o queue/queue.o all: main
main: main.o stackdir queuedir
$(CC) -o main main.o $(OBJECTS)
Un esempio di makefile, rivisitato
main.o: main.c $(HDIR)/*.h
$(CC) $(INCPATH) -c main.c stackdir:
cd stack && make all queuedir:
cd queue && make all clean:
rm -f *.o main core cleanall:
-rm -f *.o main core cd stack && make clean cd queue && make clean
Ignora eventuali errori
Solo in caso di successo con cd
Makefile "avanzati"
Le funzionalità di make non si esauriscono qui
• Strutture di controllo come statement condizionali e loop
• Regole implicite che agiscono come default quando non sono presenti regole esplicite
• Semplici funzioni di supporto per trasformare testo
• Variabili automatiche che si riferiscono a vari elementi di un Makefile, come target e dipendenze.
Per ulteriori dettagli:
• http://www.gnu.org/manual/make/html_mono/make.html
Esempi
Dipendenze automatiche
• make è in grado di inferire alcune dipendenze e regole
OBJECTS = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(OBJECTS)
cc -o edit $(objects) main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h display.o : defs.h buffer.h insert.o : defs.h buffer.h search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h utils.o : defs.h
main.o : defs.h main.c gcc -c main.c
Esempi
Uno stile alternativo
• è possibile "raggruppare" le dipendenze
OBJECTS = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(OBJECTS)
cc -o edit $(OBJECTS)
$(OBJECTS) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
Esempi
Regole "statiche"
• Specificano il comportamento per una classe di file
• gnuplot: input
set{eps|gpt}.gpt *.plt, output
output.{eps|gpt}.PHONY: all eps png all: eps png
eps: quota.eps transfer.eps single.eps png: quota.png transfer.png single.png
%.eps : %.plt seteps.gpt
gnuplot seteps.gpt $< ; mv output.eps $@
%.png : %.plt seteps.gpt
gnuplot setpng.gpt $< ; mv output.png $@
clean:
rm -f *.eps *.png Identifica il target
Identifica il prerequisito Afferma che sono solo azioni
Portabilità
E' sempre un problema!
• Diversi tipi di: processori / compilatori / librerie / s.o.
• Diverse versioni di: compilatori / librerie / s.o.
Makefile per la portabilità:
• scrivere un makefile è facile
• come abbiamo visto...
• scrivere un buon makefile è difficile
• maintanance target (install, dist, distclean, ...)
• opzioni utente
• gestione delle dipendenze !!!!!
• scrivere un makefile portatile è molto difficile
Auto-tools
GNU project "auto-tools"
• automake - generazione automatica makefile
• autoheader - generazione config headers; per portabilità
• autoconf - genera configure script
• libtool - gestione librerie statiche/dinamiche
Makefile.am Configure.in
autoconf
automake Makefile.in
configure config.h Makefile
Esempio di autoconf
programmer@c:~/project> ls -1 my_math.c
my_math.h sample.c
programmer@c:~/project>:~/tmp> autoscan programmer@c:~/project>:~/tmp> ls -1
autoscan.log configure.scan my_math.c
my_math.h sample.c
Autoscan crea il file configure.scan, che ci servirà come template per creare il file configure.in
Project autoscan configure.scan
Esempio di autoconf
programmer@c:~/project> cat configure.scan
# -*- Autoconf -*-
# Process this file with autoconf for a configure script.
AC_PREREQ(2.57)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS) AC_CONFIG_SRCDIR([my_math.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT
Esempio di autoconf
programmer@c:~/project> cat configure.in
# -*- Autoconf -*-
# Process this file with autoconf for a configure script.
AC_PREREQ(2.57)
AC_INIT(sample, [1.00], [programmer@c.com]) AC_CONFIG_SRCDIR([my_math.h])
#AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
Questa riga è commentata, perchè non abbiamo bisogno di un file
config.h (per gestire portabilità)
Esempio di autoconf
programmer@c:~/project> automake -a
configure.in: `AM_INIT_AUTOMAKE' must be used
automake: no implementation of AM_INIT_AUTOMAKE was found, automake: probably because aclocal.m4 is missing...
automake: You should run aclocal to create this file, then automake: run automake again.
automake: no `Makefile.am' found or specified -a aggiunge alcuni file standard al
progetto; altrimenti li richiede
automake autoscan.in
Makefile.am Makefile.in
Esempio di autoconf
programmer@c:~/project> cat configure.in
# -*- Autoconf -*-
# Process this file with autoconf for a configure script.
AC_PREREQ(2.57)
AC_INIT(sample, [1.00], [programmer@c.com]) AC_CONFIG_SRCDIR([my_math.h])
AM_INIT_AUTOMAKE
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
Esempio di autoconf
programmer@c:~/project> cat Makefile.am noinst_PROGRAMS=sample
sample_SOURCES=sample.c my_math.c
#Ulteriori esempi, commentati:
#bin_PROGRAMS=sample
#include_HEADERS=my_math.h
#lib_LIBRARIES=mymath.a
#mymath_a_SOURCE=my_math.c
Struttura generale: where_WHAT where - dove mettere i file generati WHAT - quale file considerare
Esempio di autoconf
programmer@c:~/project> aclocal
programmer@c:~/project> automake -a automake
Makefile.am: required file `./NEWS' not found Makefile.am: required file `./README' not found Makefile.am: required file `./AUTHORS' not found Makefile.am: required file `./ChangeLog' not found programmer@c:~/project> touch \
README NEWS AUTHORS ChangeLog programmer@c:~/project> automake -a programmer@c:~/project> autoconf
autoconf autoscan.in
configure
Esempio di autoconf
programmer@c:~/project> ./configure
checking for a BSD-compatible install.. /usr/bin/install -c checking whether build environment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes checking for gcc... gcc
checking for C compiler default output... a.out checking whether the C compiler works... yes
checking whether we are cross compiling... no checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes checking whether gcc accepts -g... yes
checking for gcc option to accept ANSI C... none needed checking for style of include used by make... GNU
checking dependency style of gcc... gcc configure: creating ./config.status
config.status: creating Makefile
config.status: executing depfiles commands
Autoconf
Note finali
• Con questo esempio abbiamo solo "scalfito" le potenzialità di auto*
• Maggiori informazioni presso:
• google://automake
• google://autoheader
• google://autoconf
• google://libtool
CVS - Concurrent Versioning System
Perchè utilizzare CVS?
• tenere traccia delle differenti versioni dei vostri file
• mantenere le vecchie versioni
• lavorare sugli stessi file da postazioni diverse
• lavorare sugli stessi file in cooperazione con altre persone
Per chi è indicato?
• Programmatori, scrittori (per chi usa latex)
Open-source
• http://www.cvshome.org/.
CVS
Repository /home/cvs
/home/cvs/proj1
/home/cvs/proj1/a.c,v /home/cvs/proj1/b.c,v
/home/cvs/proj2
/home/cvs/proj2/a.tex,v
Working dir 1
Working dir 2
CVS
Repository /home/cvs
/home/cvs/proj1
/home/cvs/proj1/a.c,v /home/cvs/proj1/b.c,v
/home/cvs/proj2
/home/cvs/proj2/a.tex,v
Working dir 2 /home/pippo/proj1
/home/pippo/proj1/a.c /home/pippo/proj1/b.c
Working dir 2 checkout
CVS
Repository /home/cvs
/home/cvs/proj1
/home/cvs/proj1/a.c,v /home/cvs/proj1/b.c,v
/home/cvs/proj2
/home/cvs/proj2/a.tex,v
Working dir 1 /home/pippo/proj1
/home/pippo/proj1/a.c /home/pippo/proj1/b.c
/home/pippo/prog2
/home/pippo/prog2/a.tex
Working dir 2 checkout
CVS
Repository /home/cvs
/home/cvs/proj1
/home/cvs/proj1/a.c,v /home/cvs/proj1/b.c,v
/home/cvs/proj2
/home/cvs/proj2/a.tex,v
Working dir 1 /home/pippo/proj1
/home/pippo/proj1/a.c /home/pippo/proj1/b.c
/home/pippo/prog2
/home/pippo/prog2/a.tex
Working dir 2 /home/pluto/proj1
/home/pluto/proj1/a.c /home/pluto/proj1/b.c checkout
CVS
Repository /home/cvs
/home/cvs/proj1
/home/cvs/proj1/a.c,v /home/cvs/proj1/b.c,v
/home/cvs/proj2
/home/cvs/proj2/a.tex,v
Working dir 1 /home/pippo/proj1
/home/pippo/proj1/a.c /home/pippo/proj1/b.c /home/pippo/prog2
/home/pippo/prog2/a.tex
Working dir 2 /home/pluto/proj1
/home/pluto/proj1/a.c Modificato
CVS
Repository /home/cvs
/home/cvs/proj1
/home/cvs/proj1/a.c,v /home/cvs/proj1/b.c,v
/home/cvs/proj2
/home/cvs/proj2/a.tex,v
Working dir 1 /home/pippo/proj1
/home/pippo/proj1/a.c /home/pippo/proj1/b.c
/home/pippo/prog2
/home/pippo/prog2/a.tex
Working dir 2 /home/pluto/proj1
/home/pluto/proj1/a.c /home/pluto/proj1/b.c commit
CVS
Repository /home/cvs
/home/cvs/proj1
/home/cvs/proj1/a.c,v /home/cvs/proj1/b.c,v
/home/cvs/proj2
/home/cvs/proj2/a.tex,v
Working dir 1 /home/pippo/proj1
/home/pippo/proj1/a.c /home/pippo/proj1/b.c
/home/pippo/prog2
/home/pippo/prog2/a.tex
Working dir 2 /home/pluto/proj1
/home/pluto/proj1/a.c update
CVS
Come funziona CVS?
• Orientato ai file / orientato alla linea
• Nessun vero e proprio concetto di "progetto"
• Lavoro soprattutto con file testuali; può gestire file binari
CVS
Come indicare dove si trova un repository?
• Local path
• External :ext:user@host:path
• Password server :pserver:user@host:path
Da linea di comando:
• cvs -d /home/cvs checkout proj
• cvs -d :pserver:pippo@cvs.c.com:/home/cvs login Password: xxxxxxx
cvs checkout
Tramite configurazione, una volta:
• export CVSROOT=cvs.c.com:/home/cvs
CVS - Importare un progetto
pippo@c:~> cd temp pippo@c:~/temp> ls
my_math.c my_math.h sample.c
pippo@c:~/project> cvs -d /home/cvs import \ -m "First release" sample pippo start
N sample/sample.c N sample/my_math.c N sample/my_math.h
No conflicts generated by this import pippo@c:~/project>
CVS - Importare un progetto
pippo@c:~> cvs -d /home/cvs checkout sample cvs checkout: Updating sample
U sample/my_math.c U sample/my_math.h U sample/sample.c
pippo@c:~/> ls sample
CVS/ my_math.c my_math.h sample.c pippo@c:~/> ls sample/CVS/
Entries Repository Root
pippo@c:~/> cat sample/CVS/Root /home/cvs
Da qui in poi, non è più
CVS - Modificare un progetto
pippo@c:~/sample> cp sample.c sample2.c pippo@c:~/sample> vi sample.c
pippo@c:~/sample> cvs add sample2.c cvs add: scheduling file
`sample2.c' for addition
cvs add: use 'cvs commit' to add this file permanently
#include <stdio.h>
#include "my_math.h"
int main() {
int a, b, c;
puts("Inserisci tre numeri:");
scanf("%d %d %d", &a, &b, &c);
printf("The average of %d %d %d is %f\n", a,b,c,average(a,b,c));
}
/* Commento finale */
CVS - Modificare un progetto
pippo@c:~/sample> cvs commit -m "Added file"
cvs commit: Examining . Checking in sample.c;
/home/montreso/cvs/sample/sample.c,v <-- sample.c new revision: 1.2; previous revision: 1.1
done
RCS file: /home/montreso/cvs/sample/sample2.c,v done
Checking in sample2.c;
/home/montreso/cvs/sample/sample2.c,v <-- sample2.c initial revision: 1.1
done
CVS - Varie ed eventuali
pippo@c:~/sample> cvs update cvs update: Updating .
Vedere le differenze:
pippo@c:~/sample> cvs diff -r1.1 sample.c Index: sample.c
RCS file: /home/montreso/cvs/sample/sample.c,v retrieving revision 1.1
retrieving revision 1.2 diff -r1.1 -r1.2
6c6
< puts("Input three numbers:");
---
> puts("Inserisci tre numeri:");
10d9
< return 0;
11a11
> /* Commento finale */