Gestione progetti software
Michelangelo Diligenti Ingegneria Informatica e
dell'Informazione
diligmic@dii.unisi.it
Sommario
● Cosa fare quando il progetto software diviene grande?
Dividere bene le classi in .cc e .h
Makefiles per gestire la compilazione
● Gestione di progetti in un gruppo di programmatori
svn e programmazione condivisa
.cc e .h
● Importante separare l'interfaccia ed implementazione
Interfaccia in .h, ricordatevi la seguente struttura del .h per evitare che il file possa venire incluso più volte
#ifndef NOME_FILE_H
#define NOME_FILE_H // codice …
#endif // NOME_FILE_H
Implementazione in .cc, VANTAGGI
Interfaccia chiara e leggibile
Tempi di compilazione ridotti
Forward Declarations
● Non includere un file da un file .h se possibile evitarlo con forward declaration
● Forward declaration dichiara l'esistenza di un tipo senza specificarne le caratteristiche
● Velocizza la compilazione ed aiuta ad evitare dipendenze cicliche
● Usare forward declaration quando non serve sapere la dimensione del tipo per compilare
Possibile ogni volta che nel .h ci si riferisce al tipo per indirizzo o riferimento
Forward Declaration: esempio
class RefParam;
class ValParam;
class PtrParam;
class User{
PtrParam* dato; // ok non devo sapere dimensione per definire puntatore public:
void Do(const RefParam &inParam ); // ok, riferimento
void Do(ValParam inParam); // strano ma ok, purché senza implementazione, // ma solo definizione del metodo
void Do(PtrParam* inParam); // ok, puntatore void Do(ValParam& inParam); // ok, riferimento };
Forward Declaration: esempio
class ValMember;
class PtrMember;
class User{
public:
User();
ValMember valmember; // no! serve sapere dimensione PtrMember* ptrmember; // ok! non serve dimensione };
Makefiles
● Dato un progetto formato da un insieme di sorgenti
● Definiscono come la compilazione deve avvenire per creare i binari
● Definiscono un insieme di direttive di compilazione
● Permettono di definire le dipendenze tra sorgenti
Solo cosa cambia e ciò che dipende da quello che è cambiato va ricompilato
Velocizzazione dei tempi di compilazione durante lo sviluppo
Makefiles
● Strumenti di sviluppo comuni come Eclipse o Netbeans costruiscono Makefile per noi
Tuttavia bene capire il funzionamento dei Makefile
Quando si crea pacchetto software non opportuno
assumere che lo strumento di sviluppo sia installato
Si esporta solo il codice ed il makefile (con eventuali librerie)
Il processo di istallazione usa il makefile per ricompilare il codice sulla macchina target
Makefiles: invocazione
● Come lo si invoca
Per costruire i binari di default usando un makefile chiamato “makefile” nella directory corrente
digitare: “make”
Per costruire il binario programma usando il makefile di default
digitare: “make programma”
Per costruire il binario programma usando il makefile
“makefile”
digitare: “make -f makefile programma”
Makefiles: struttura
● Righe che iniziano per # sono commenti
● Makefile è una sequenza di comandi nella forma:
● target: lista_dipendenze_di_target comando
Target: nome del risultato del comando, in genere è od un binario od un file oggetto .o
lista_dipendenze_di_target: i target da cui target dipende
Target costruito se una delle dipendenze è cambiata
comando: regola da eseguire usando il terminale
Eseguito per creare il target
tab
Makefiles: esempio
# Compiling source files (uno per ogni file .cc) main.o : main.cc mydefs.h
g++ -c main.cc
file1.o : file1.cc file1.h mydefs.h g++ -c file1.cc
file2.o : file2.cc command.h g++ -c file2.cc
display.o : display.cc command.h g++ -c display.cc
# Alla fine, linking file oggetto
programma : main.o file1.o file2.o display.o
g++ -o programma main.o file1.o file2.o display.o clean :
rm -f programma *.o
se file1.cc o file1.h o mydef.h sono cambiati, genera target file1.o eseguendo il comando
“g++ -c file1.cc”
genera target clean: ripulisce il disco e reinizia la compilazione da zero: esegue il comando “rm -f programma *.o”
Makefiles: esempio più binari
main.o : main.cc mydefs.h g++ -c main.cc
main1.o : main1.cc mydefs.h g++ -c main1.cc
file1.o : file1.cc file1.h mydefs.h g++ -c file1.cc
display.o : display.cc command.h g++ -c display.cc
programma : main.o file1.o
g++ -o programma main.o file1.o display.o programma1 : main1.o file1.o file2.o display.o
g++ -o programma1 main1.o file1.o file2.o display.o all: programma programma1
clean :
rm -f programma programma1 *.o
ogni binario ha linking separato
target all costruisce tutti i
binari, si chiama con “make all”
Make: esercizio
● Costruire il makefile per la compilazione di 2-3 eseguibili a vostra scelta, meglio se su piu' sorgenti, costruiti
durante il corso
Phony targets
● Sono targets che specificano azioni, non creano targets e non hanno dipendenze
● Usati per ripulire lo stato della compilazione o creare archivi
clean:
rm -f *.o lista_binary tgz:
tar cvfz pacchetto.tar.gz *.cc *.h Makefile
ripulisce i file generati dal
makefile stesso, si chiama con:
make clean
crea archivio del pacchetto dei sorgenti, si chiama con:
make tgz
Targets e dipendenze
● Le dipendenze possono essere aggiunte in modo incrementale
– Possibile aggiungere targets che specificano solo dipendenze ma non azioni, vedremo come usarlo
– Esempio
#Regola di compilazione, per ora .o dipende solo da .cc corrispondente file1.o : file1.cc
g++ -c file1.cc
# Regola che specifica dipendenze aggiuntive
# gfile1.o ora dipende sia da file1.cc che da file1.h e mydef.h file1.o: file1.h mydef.h
Makefiles: variabili
● Possibile definire variabili
NOME_VAR = VALORE
Per accedere al valore della variabile (equivalenti):
${NOME_VAR} o $(NOME_VAR)
Makefiles: esempio con variabili
LD=g++ # linker
CC=g++ # compilatore
programma : main.o file1.o file2.o display.o ${LD} -o prog1 main.o file1.o file2.o display.o
# Compiling source files main.o : main.cc mydefs.h ${CC} -c main.cc
file1.o : file1.cc mydefs.h ${CC} -c file1.cc
file2.o : file2.cc command.h ${CC} -c file2.cc
display.o : display.cc command.h ${CC} -c display.cc
...
Variabili speciali
● $@: il target per la regola corrente
● $< : il nome della prima dipendenza
● $?: il nome di tutte le dipendenze che sono cambiate
● $^: il nome di tutte le dipendenze separate da uno spazio
● $+ : come $^ ma eventuali dipendenze ripetute sono rimosse
Regole implicite
● Usando la seguente sintassi, possibile specificare target generici
● Per fornire una regola generale di come passare da ogni file .cc al corrispondente .o
%.o:%.cc
${CC} -c $<
Makefile: esempio generale
CC=g++
CFLAGS=-O6 -Wall LDFLAGS=-O6
objs = file1.o file2.o display.o
# Linking, più binari possono essere generati programma : programma.o $(objs)
${CC} ${LDFLAGS} -o $@ $+
programma1 : programma1.o $(objs) ${CC} ${LDFLAGS} -o $@ $+
# Compilazione
%.o:%.cc
${CC} ${CFLAGS} -c $<
# Aggiunta dipendenze ulteriori
programma.o : mydefs.h programma.h programma1.o : mydefs.h
...
all: programma programma1 clean:
rm -f programma programma1 $(objs)
primo binario, costruito se:
make programma
secondo binario, costruito se make programma1
make all costruisce tutto
Make depend
● Permette di non aggiungere le dipendenze a mano
● Analizza i files, esegue il preprocessore e controlla #include
Infine aggiunge al makefile le dipendenze
Si esegue con comando (cerca file Makefile):
makedepend *.cc *.h
Oppure makedepend -f makefile_specifico
Make depend
● Spesso si mette direttamente come phony target nel makefile
SRCS = file1.cc file2.cc … CFLAGS = …
depend:
makedepend -- $(CFLAGS) -- $(SRCS) oppure
depend:
makedepend -- $(CFLAGS) -- *.cc
Modifica Makefile aggiungendo una linea
# DO NOT DELETE THIS LINE -- make depend depends on it.
Dopo questa linea vengono messe le dipendenze
Make: esercizio
● Modificare il makefile costruito usando il template generale precedentemente fornito
Programmazione condivisa
● Source Managment Control (SMC)
Software che permette di sviluppare software in team
Tracciano i cambiamenti e chi li ha fatti
Permettono di tornare indietro nel tempo con il codice
Agevolano la risoluzione di cambiamenti conflittuali allo stesso codice
FONDAMENTALI per lo sviluppo in azienda
Programmazione condivisa
● Source Managment Control (SMC)
CVS: il primo ed il classico
oggi un po antiquato
ma ha imposto un modello che tutti conoscono
Subversion (SVN), quello che studieremo
Utilizzo simile al CVS
Ma riscritto completamente
Perforce: ottimo ma non gratuito
e molti altri
SMC centrealizzati e non
● SMC centralizzati hanno un repository centralizzato
Ogni client ha sua copia decentralizzata
Un server centrale tiene la copia di riferimento (master)
SVN è centralizzato
● SMC decentralizati non hanno repository centrale
Spesso basati su Peer-to-peer, i client tengono le copie ed il repository in modo distribuito
Repository/Master
Working Copy Working Copy Working Copy
SVN, client e server
● SVN server tiene il repository master
Riferimento per tutte le altre copie
Le working copy sono inizializzate dal master
Cambiamenti su working copy sottomessi al master
Server contiene database ed altri tools come
Web server per controllare lo stato del master
SSH per sottomettere sul o leggere i dati dal master
Server
Working Copy Working Copy Working Copy
Clients
Repository/Master
SVN: architettura
Database che Memorizza Repository Libreria per
accesso e modifica del master
Accesso da
linea di comando o interfaccia
grafica Libreria per
accesso e modifica
working copy
Working Copy Files
Files che formano la working copy
Metodi per accesso alla working copy (http, apache,...)
SVN e comandi
● Vedremo la gestione da linea di comando
Possibile anche gestire via Web
● Effettuata tramite il binario svn ed svnadmin
Necessario installare i pacchetti corrispondenti
● Help generico digitando
svn help
● Per avere help specifico di un comando
svn help comando
Esempio “svn help import”
Tutti i comandi sono relativi ad una directory di lavoro
SVN creazione del master
# Creo directory sul server mkdir -p /var/svn/repos
# creazione del master repository chiamato pps sudo svnadmin create /var/svn/repos/pps
SVN importazione codice
● Per iniziare un repository è possibile importare un'intera directory con tutto il codice in essa contenuto
● Tale comando viene chiamato import
Necessario solo una volta e nel caso di progetti già avviati
Se il progetto parte da zero tale comando non è necessario
Vedremo come aggiungere singoli files al repository
SVN importazione codice
# Creo directory di lavoro sul client e ci copio codice
mkdir $HOME/Documents/Corsi/ProgrammazioneProgettazioneSoftware2011/src/svn/pps
# Vado su directory di lavoro, da ora in poi assumerò di essere qui
cd $HOME/Documents/Corsi/ProgrammazioneProgettazioneSoftware2011/src/svn cp $CODICE/costruttori/* pps
# Importo il codice nel repository pps
svn import pps svn+ssh://michi@localhost//var/svn/repos/pps -m 'inital import'
Binario che implementa
il client Operazione di
importazione Directory che viene importata
Metodo di
accesso
URL
del master
Path di accesso al master Opzionale:
descrizione dell'operazione
Utente che accede
SVN creazione working copies
● In qualsiasi momento è possibile creare una nuova working copy
● Comando detto checkout
# Creo una nuova working copy a partire dal repository
svn checkout svn+ssh://michi@localhost//var/svn/repos/pps pps1
Path dove viene fatto checkout rispetto alla
directory corrente Repository che
viene preso URL
SVN aggiunta files
● Aggiunta files sulla working copy
● Attenzione non basta avere file nella directory che si è checked out perché siano tracciati
● Solo i nuovi file aggiunti esplicitamente saranno tracciati
● Tale comando di aggiunta è detto add
# Aggiungo file da tracciare (in path su cui è stato fatto un checkout) svn add path/file
SVN edit files
● I files nella working copy che sono tracciati possono essere editati da un normale editor
SVN rimozione files
● Rimozione files sulla working copy
● Comando detto delete o rm
# Rimuovo file da tracciare (in path su cui è stato fatto un checkout) svn delete path/file
SVN sincronizzazione
● Sincronizzazione da client a master per
sottomettere i cambiamenti fatti viene detto commit
● Sottomette i cambimenti relativi all'aggiunta, rimozione e cambiamenti sui files tracciati
svn commit -m “commento opzionale su cosa si sottomette”
SVN sincronizzazione
● Sincronizzazione da master a client per ricevere i cambiamenti fatti su altre working copy viene detta sync o update
● Sincronizza i cambimenti relativi all'aggiunta,
rimozione e cambiamenti sui files fatti da altri client
svn update
SVN iterazione di lavoro
● A parte le inizializzazioni, le iterazioni di lavoro con SVN sono semplici
Il 95% del tempo si digitano i seguenti comandi
svn update
svn commit -m “commento”
● Se i cambiamenti sono su stesso file da parte di
diversi client, svn cercherà di fare l'unione (merge)
SVN e conflitti
● Tutto è semplice tranne in un caso
● Cosa succede se due persone modificano lo stesso file nello stesso punto?
● In tal caso SVN non permette a chi fa il commit per secondo di effettuarlo
Siamo in presenza di uno o più conflitti
Possibile vedere files con conflitti con comando svn status
Mostra files con estensione .mine e .rNumero
Necessario effettuare risoluzione dei conflitti
SVN e risoluzione conflitti
● Il file con conflitto (riportato da svn status) ha linee con conflitti evidenziate con dei markers speciali
<<<<<<< .mine
CODICE INSERITO SULLA WORKING COPY
===========
CODICE INSERITO SUL MASTER DOPO NOSTRO UPDATE
>>>>>>> .r23
● Markers evidenziano codice introdotto in working copy (.mine) e dall'altro client (.r$VERSIONE)
Markers devono essere eliminati, finché non si è risolto il conflitto: il codice compila e funziona correttamente
SVN e risoluzione conflitti
● Dopo aver editato il file, risolto il conflitto ed eliminato i markers, il conflitto è risolto
● Per dire a SVN che il conflitto è risolto
svn resolved path/file
● Adesso possibile effettuare il commit
Se ci sono conflitti, svn blocca i commit
SVN e backups
● SVN usa un semplice database open-source chiamato Berkley DB
Possibile fare backups del database con il comando svnadmin dump /var/svn/repos/pps > backup_file
Consigliabile effettuare backups attraverso cron ogni poche ore/minuti
Possibile ricaricare un backup precedente sul repository master con il comando
svnadmin load /var/svn/repos/pps < backup_file
SVN ed export
● Se volete rilasciare il codice a terze parti (non sviluppatori)
Potete fargli fare un checkout ma questo è pesante visto che SVN si porta dietro meta-data
Meglio semplicemente dargli i files
Possibile esportare l'albero di files (tutti i files
ricorsivamente nel repository) con il comando export
svn export svn+ssh://michi@localhost//var/svn/repos/pps output
Directory dove si esporta
Repository esportato
SVN: uso avanzato
● La trattazione fatta sulle slide di SVN copre solo una piccola parte di cosa può fare SVN
● Cose rilevanti non trattate
Branching: quando si vuole avere più copie del codice
Esempio per la versione stabile e quella sperimentale dello stesso
Script automatici da eseguire quando si effettuano operazioni
Esempio: per controllare che il codice rispetti un certo stile o compili correttamente quando viene fatto il commit
SVN: uso avanzato
● Manuale completo ed ufficiale è ben 405 pagne!
● Scaricabile gratuitamente da http://svnbook.red-bean.com/
● Copre tutti gli utilizzi più avanzati anche se è dispersivo