Negli ultimi giorni ho studiato MSBuild per automatizzare 
la pubblicazione su un server Web (che supporta PHP & MySQL, alla 
faccia di ASP.NET 
) di un file ZIP contenente i sorgenti di 
Fatturazione. Ne ho sempre parlato in modo un po' vago, per cui 
in questo post voglio descrivere nel modo più specifico quello che ho fatto.
Introduzione
In linea di massima, lo script parte da 
questi concetti:
  - ho i sorgenti  da qualche parte, che devo 
  prelevare 
  
 - copio i sorgenti trasferendoli da qualche altra 
  parte
  
 - zippo i sorgenti copiati al punto (2) 
  
 - faccio l'upload del file zippato su un server FTP
 
Perchè ho messo in corsivo e in blu le locazioni dei sorgenti? E' presto detto. I 
sorgenti al punto (1) possono stare - per adesso - in due tipi di posti 
diversi: o una directory sul mio hard-disk (D:\Documenti\Visual Studio 
2005\Projects\Fatturazione) oppure mantenuti da un source control (come il 
 Vault SourceGear). Per adesso, partiamo dal presupposto di avere la nostra 
soluzione in una directory, che è il caso più semplice che 
capita a tutti noi, poi vedremo come modificare in modo semplice lo 
script per fare diversamente.
Quindi, dicevamo, dobbiamo copiare tutti i 
files dalla directory di origine alla directory di destinazione, 
dobbiamo zippare i sorgenti ed 
infine dobbiamo uploadare i 
files zippati. Contate i grassetti nella frase precedente e scoprirete quanti 
task deve eseguire il nostro script per MSBuild: solo 3? In realtà, ce n'è uno in più, ovvero 
quello iniziale che si occupa di cancellare gli 
eventuali residui di una precedente esecuzione dello script 
(cancellazione della directory di destinazione ed il file ZIP 
vecchio.).
Detto questo, passiamo alla pratica.
Struttura dello script per MSBuild
Uno script di MSBuild 
non è nient'altro che un file XML creato in modo opportuno, rispettando un certo 
schema e così via. Vediamo la struttura di base.
<Project DefaultTargets="All" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    ...
    ...
    ...
    
    <Target Name="All">
        <CallTarget Targets="DeleteLastDownload" />
        <CallTarget Targets="CopyFiles" />
        <CallTarget Targets="CreateZip" />
    </Target>
</Project
Il tag Project comincia lo script. La proprietà 
DefaultTargets indica il task che viene eseguito per primo: il 
task si chiama All, che esegue poi uno alla volta gli altri 
task che compongono lo script, ovvero: DeleteLastDownload, 
CopyFiles e CreateZip. In questo momento, non 
abbiamo ancora considerato l'upload via FTP, ne parleremo più avanti. I punti di 
sospensione indicano un blocco XML contenente uno alla volta il codice relativo 
a ciascun task.
Esaminiamoli nel dettaglio adesso uno alla volta.
Il task DeleteLastDownload
Questo task fa semplicemente 
due cose: cancella la directory di destinazione (se esiste) e il file ZIP (se 
esiste). Questo task ha senso solo se abbiamo eseguito lo script già 
una volta e quindi abbiamo i residui di una precedente esecuzione, appunto. Se 
la directory o il file non esistono, le corrispondenti operazioni vengono 
skippate senza danni. Il codice è:
<Target Name="DeleteLastDownload">
    <RemoveDir Directories="$(DownloadDir)"/>
    <Delete Files="$(ZipFileName)"/>
</Target>
$(DownloadDir) e $(ZipFileName) sono due costanti: ne parleremo un'altra 
volta.
Il task CopyFiles
Questo task copia i sorgenti da una 
directory di origine ad un'altra di destinazione. Per farlo, sfrutta una copia 
ricorsiva: come tutti sappiamo, una soluzione .NET è articolata in molte 
directory, una innestata all'altra, ma noi con un semplice comando possiamo 
fare il tutto senza problemi. Il codice è questo:
<Target Name="CopyFiles">
    <Copy
        SourceFiles="@(MySourceFiles)"
        DestinationFiles="@(MySourceFiles->'$(DownloadDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
Non chiedetemi molti dettagli, perchè io stesso l'ho copiata & incollata dal sito MSDN. 
 Anche in questo caso, $(MySourceFiles) e $(DownloadDir) fanno riferimento a costanti.
Il task CreateZip
Questo task - che non fa parte del 
pacchetto standard di MSBuild - crea un file ZIP partendo da uno o più files. Il 
comando, su cui sono impazzito parecchio 
, è il seguente:
<ItemGroup>
    <ZipFiles Include="$(DownloadDir)\**\*.*" />
</ItemGroup>
<Target Name="CreateZip">
    <Zip ZipFileName="$(ZipFileName)" WorkingDirectory="$(DownloadDir)" Files="@(ZipFiles)"/> 
</Target>
Attenzione: il gruppo di files da zippare è indicato dal tag 
ItemGroup. Se anche voi dovete fare una cosa del genere, 
assicuratevi di metterlo assolutamente prima del task 
CreateZip. Sto ancora indagando sulla questione, ma ho la netta sensazione che se nello script voi copiate dei 
files (come in questo caso) e 
quell'include viene "valutato" prima della copia, nello ZIP non vengono 
inclusi i files copiati durante l'esecuzione dello script. Io 
sono diventato matto, perchè continuavo a generare il file ZIP vuoto, 
oppure che non corrispondeva al 100% ai files effettivamente contenuti nella 
directory di destinazione.
E se devo usare Vault SourceGear?
Se 
i sorgenti sono mantenuti su un source control, possiamo recuperarli attraverso 
il client di Vault disponibile da linea di comando. In pratica, il task 
CopyFiles non ci servirebbe più, ma verrebbe sostituiti da un 
GetLatestVersion che concettualmente fa la stessa cosa 
(fornisce i sorgenti aggiornati), ma li preleva fisicamente da un repository 
raggiungibile via http. Il task è:
<Target Name="GetLastestVersion">
    <Exec Command=' $(SourceGearClientPath) GETWILDCARD -makewritable -destpath $(DownloadDir) $(SourceGearPath) * -user $(SourceGearUsername) -password $(SourceGearPassword) -host $(SourceGearHost) -REPOSITORY "Default Repository" ' />
</Target>
Il tag Exec non fa altro che eseguire il comando 
riportato nella proprietà Command del tag stesso. Utilizza 
tutta una serie di costanti $(qualcosa) per rendere più manutenibile lo 
script. Una cosa importante: il comando qui sopra non fa il check-out dei 
sorgenti, ma chiede una semplice copia dei files. Il check-out non avrebbe 
senso, dal momento che lo si fa solo che bisogna modificare un file, cosa che a 
noi non serve.
Conclusioni
C'è ancora qualcosa da vedere, ma non ho 
tempo: come definire le costanti e, soprattutto capire quello che non ho ancora 
capito, ovvero che differenza c'è tra i tag ItemGroup e 
PropertyGroup. 
 Stay tuned!