Mondi su mondi, sistemi di sistemi.

Jenkins: non solo Java

Monday, June 27th, 2011

Stavo scri­vendo un post dopo aver letto l’annuncio di que­sto stru­mento per il con­trollo dei CSS quando mi è venuto in mente che forse sarebbe più inte­res­sante discu­tere dell’evoluzione gene­rale del (mio) modo di creare appli­ca­zioni1 per il web. Quindi ho but­tato nel cestino il post sui CSS.

Oltre il “plain text”

Una delle ragioni del suc­cesso del web è il fatto di essere basato su for­mati testuali: se voglio vedere com’è fatta una pagina, un foglio di stile, uno script, basta che lo apra con un qual­siasi edi­tor di testo. Anche su tutti i pro­cessi che stanno a con­torno del lavoro crea­tivo vero e pro­prio bene­fi­ciano di que­sta sem­pli­cità. Una volta creato un file, que­sto è pronto per svol­gere la sua fun­zione senza pas­saggi inter­medi, basta cari­carlo sul sito.

Tutto que­sto, però, è sem­pre meno vero per­ché la pres­sione per otte­nere pre­sta­zioni sem­pre migliori sta por­tando sem­pre di più verso un con­te­nuto che, se ancora si può tec­ni­ca­mente defi­nire “plain text”, risulta illeg­gi­bile in modo diretto. 2.

Poi ci sono l’ottimizzazione delle imma­gini o, come abbiamo visto, i con­trolli di qua­lità ecc. Insomma, si può dire che i pro­cessi usati nel creare delle appli­ca­zioni “tra­di­zio­nali”, con lin­guaggi com­pi­lati e anche il sem­plice sito sta­tico, si asso­mi­gliano sem­pre di più.

I pros­simi passi

Sono per­ciò arri­vato alla con­clu­sione che è sen­sato usare gli stessi stru­menti in tutti casi.

Quindi, anche se devo ancora pro­gram­mare la que­stione nel det­ta­glio, sicu­ra­mente comin­cerò a spo­stare i pro­getti sotto Jenkins; da lì vedrò poi di deci­dere caso per caso le ope­ra­zioni da fare. In alcuni casi gli stadi del pro­cesso saranno delle sem­plici otti­miz­za­zioni, in altri ci saranno anche tutte le varie bat­te­rie di test ma l’infrastruttura di base sarà la stessa.

  1. C’è sicu­ra­mente una bella dif­fe­renza fra un sito “sta­tico” e un’applicazione, ma credo che sia con­ve­niente comun­que con­si­de­rarli come estremi di uno stesso spet­tro. Anche per le ragioni espo­ste in que­sto post.
  2. Gli esempi ovvi sono la minia­tu­riz­za­zione dei fogli di stile e degli script. Anzi, per JavaScript siamo ormai arri­vati alla com­pi­la­zione vera e pro­pria. Poi ci sono altre forze di cam­bia­mento come la sem­pre mag­giore dina­mi­cità delle pagine, il cui con­te­nuto finale, una volta che il bro­w­ser ha com­ple­tato il pro­ces­sa­mento, non asso­mi­glia nem­meno lon­ta­na­mente a quello par­tenza.

Git per il controllo di qualità

Wednesday, June 8th, 2011

Dopo aver par­lato dell’uso dei fil­tri con Git, penso sia una buona idea descri­vere gli usi degli hook in pre com­mit, ad esem­pio, per fare dei con­trolli pre­ven­tivi di qualità.

JavaScript non è cat­tivo, sono i bro­w­ser che lo dipin­gono così

Chiunque abbia un po’ di dime­sti­chezza con JavaScript se che ci sono delle dif­fe­renze fra le varie imple­men­ta­zioni dei bro­w­ser che pos­sono dare pro­blemi, anche molto gravi1 , a par­tire da un codice appa­ren­te­mente senza difetti.

Un caso clas­sico è l’uso della vir­gola dopo l’ultimo ele­mento di una col­le­zione: lo stan­dard lo proi­bi­sce ma quasi tutti i bro­w­ser lo inter­pre­tano senza pro­blemi. La frase “quasi tutti i bro­w­ser” di solito signi­fica “tutti tranne Explorer” ed è così anche in que­sto caso ma a parti inver­tite. Qui è Explorer che applica alla let­tera le spe­ci­fi­che inter­rom­pendo il par­sing e lasciando gli utenti a piedi2.

Un altro caso clas­sico, que­sta volta appli­ca­bile a tutti i bro­w­ser, è l’uso di varia­bili non dichia­rate. In que­sto caso la cosa è più sub­dola per­ché non si tratta di un errore dalle con­se­guenze vistose, ma spe­ci­fico per qual­che bro­w­ser, bensì di una cosa per­fet­ta­mente legale che può cau­sare pro­blemi solo in certe con­di­zioni. Neanche que­sta è una bella prospettiva!

Insomma, è neces­sa­rio un qual­che stru­mento che ci per­metta di con­trol­lare la qua­lità del codice che scri­viamo prima che venga man­dato nella repo.

JSLint e JSHint

Per con­trol­lare la qua­lità di JavaScript cono­sco due stru­menti: JSLint e JSHint.

Il primo è quello più famoso, è in cir­co­la­zione da molto e fa un lavoro egre­gio nel segna­lare una serie ster­mi­nata di pos­si­bili problemi.

Il secondo è una deri­va­zione di JSLint, creata con l’intenzione di dare uno stru­mento un po’ più adat­ta­bile alle con­ven­zioni usate in un certo progetto.

Ho scelto JSHint pro­prio per que­sta ragione per­ché negli ultimi tempi mi sono abi­tuato a scri­vere le vir­gole prima dell’argomento suc­ces­sivo3:

var items = [
    'a'
    , 'b'
    , 'c'
];


Il pro­blema è che JSLint non ne vuol sapere di con­si­de­rarlo valido.

Come invo­care i controlli

Il pro­blema con entrambi que­sti stru­menti è che sono scritti essi stessi in JavaScript e quindi, visto che dob­biamo invo­care i con­trolli da Git, ci serve un inter­prete al di fuori del browser.

Per fare que­sto, l’avrete già capito, pos­siamo usare Node.js, che pos­siamo instal­lare con MacPorts o Homebrew o a mano; poi ci serve npm per instal­lare i moduli di Node.js, in par­ti­co­lare node-jshint, che con­sente di usare JSHint con Node.js.

Node.js ed npm sono molto sem­plici da instal­lare. Per quanto riguarda JSHint biso­gna solo avere l’accortezza di instal­larlo nella direc­tory dei moduli con­di­visa, defi­nita da npm come instal­la­zione glo­bale.

Una volta instal­lato il tutto ci serve un script di shell che invo­che­remo dall’hook di pre-commit di git. Questo script ha il solo com­pito di leg­gere l’output di JSHint ed emet­tere l’eventuale segnale d’errore, visto che la uti­lità di suo non lo fa anche se ci sono pro­blemi. Limitandoci al minimo indi­spen­sa­bile:

#!/bin/sh
#
# Verifica la conformità dei file js alle linee guida stabilite.

RESULT=$(node-hint public/scripts/*.js)

if [ "$RESULT" != 'Lint Free!' ] ; then
    echo "$RESULT"
    exit 1
fi

Lo script appena mostrato va messo in .git/hooks/pre-commit.

Altre pos­si­bi­lità

In que­sto caso spe­ci­fico, ovvero, quello del con­trollo dei file JavaScript ci sono altre pos­si­bi­lità. Per TextMate ci sono un paio di bund­les: js-tools e javascript-tools che fanno que­sto e molto altro; per Emacs la situa­zione è meno strut­tu­rata e, come sem­pre, biso­gna arra­bat­tarsi per adat­tare le varie info che si tro­vano in rete alla pro­pria situa­zione; per ora ne fac­cio tran­quil­la­mente a meno.

  1. Quanto gravi? Il caso descritto più avanti, ovvero, la pre­senza di una vir­gola in coda ad un oggetto let­te­rale, ha cau­sato un’interruzione di quasi un giorno del sito di Lifehacker, all’inizio di Febbraio.

    In que­sto caso va notato che il pro­blema è stato esa­cer­bato dall’eccessiva dipen­denza da JavaScript per costruire la pagina, un anti-pattern che è cir­co­lato molto negli ultimi mesi. L’abuso dello scrip­ting per com­porre il con­te­nuto in pagina ha tante forme ma quella più cono­sciuta, usata appunto anche da Lifehacker, è il cosid­detto hash­bang pat­tern.

    Mi sem­bra un caso da manuale di inver­sione dei fini: Google intro­duce l’hashbang per poter indi­ciz­zare anche le pagine con con­te­nuti dina­mici, met­tendo una toppa ai siti costruiti senza tenere in con­si­de­ra­zione il pro­gres­sive enhan­ce­ment; gli svi­lup­pa­tori pren­dono que­sto rime­dio e lo usano come base di par­tenza su cui costruire l’architettura dei link di un sito, com­pro­met­ten­done la robu­stezza.

  2. Sto sem­pli­fi­cando sel­vag­gia­mente per­ché il punto del post non riguarda le spe­ci­fi­che di JavaScript e le sue imple­men­ta­zioni. In par­ti­co­lare non tengo conto né delle ver­sioni dei bro­w­ser né di quelle dello stan­dard.
  3. Quando ho visto que­sta con­ven­zione per la prima volta l’ho tro­vata biz­zarra, tut­ta­via mi sem­bra che renda il codice più facil­mente mani­po­la­bile per­ché la vir­gola serve a sepa­rare l’elemento suc­ces­sivo e quindi ha senso che viag­gino insieme.

Git e l’ottimizzazione delle immagini

Wednesday, February 9th, 2011

Si sa. Una delle cose da fare per otti­miz­zare le pre­sta­zioni di un sito è quella di sce­gliere accu­ra­ta­mente il for­mato delle imma­gini e ridurne il più pos­si­bile le dimen­sioni.1 Analogamente anche i css e java­script andreb­bero ridotti usando la minia­tu­riz­za­zione.2

Qual è il problema?

Queste otti­miz­za­zioni però pos­sono diven­tare un bell’intralcio se non auto­ma­tiz­ziamo tutti i passi neces­sari. Le imma­gini potrebbe essere otti­miz­zate prima dell’aggiunta al pro­getto ma spesso ven­gono pas­sate avanti e indie­tro prima di diven­tare defi­ni­tive; per non par­lare dei css o degli script, che pos­sono essere trat­tati solo all’ultimo momento prima del deploy­ment.3

Usiamo git

Ho pen­sato quindi di sfrut­tare git, aggan­ciando il post-processamento di que­sti file al momento dell’esportazione, quando viene creato il pac­chetto d’installazione.4

Per farlo dob­biamo modi­fi­care due file: config e info/attributes. Nel primo impo­stiamo i fil­tri:

     [filter "minify-css"]
         clean = cat
         smudge = java -jar /usr/local/bin/yuicompressor-2.4.2.jar --charset UTF8 --type css

     [filter "minify-js"]
         clean = cat
         smudge = java -jar /usr/local/bin/yuicompressor-2.4.2.jar --charset UTF8 --type js

     [filter "optimize-img"]
         clean = cat
         smudge = /Users/giorgio/bin/optimize-imgs

Nel secondo spe­ci­fi­chiamo i cri­teri di sele­zione dei file a cui appli­care que­sti fil­tri:

*.css filter=minify-css
 *.js filter=minify-js
 *.png filter=optimize-img
 *.gif filter=optimize-img
 *.jpg filter=optimize-img
 *.jpeg filter=optimize-img

Aggiriamo il pro­blema della pipeline

Il pro­blema prin­ci­pale è che git usa delle pipe­line5 per il pro­ces­sa­mento e non tutte le uti­li­ties usate per l’ottimizzazione sono pen­sate per essere impie­gate in que­sto modo, in par­ti­co­lare png­crush. La fun­zione di optimize-imgs è quindi quella di for­nire un minimo di uni­for­mità nell’utilizzo.

Lo script è incluso diret­ta­mente qui e pre­sup­pone di tro­vare nel PATH pngcrush, jpegtran e convert (ovvero, ImageMagick). Inoltre richiede tcl­lib, che è una dipen­denza di cui potrei fare a meno ma adesso non ho voglia di cam­biare lo script! ;-)

#! /usr/bin/env tclsh

package require cmdline

variable tmpFile /tmp/image

interp alias {} pngcrush {} exec pngcrush -rem alla -brute -reduce
interp alias {} jpegtran {} exec jpegtran
interp alias {} convert {} exec convert
interp alias {} cat {} exec cat
interp alias {} fileType {} exec file --brief --mime-type

proc createTempFile {} {
    variable tmpFile
    cat > $tmpFile
    switch [fileType /tmp/image] {
        {image/png} {
            set ext png
        }
        {image/gif} {
            set ext gif
        }
        {image/jpeg} {
            set ext jpg
        }
    }
    file rename -force $tmpFile $tmpFile.$ext
    return $tmpFile.$ext
}
proc optimize img {
    switch -glob -- $img {
        *.jpg -
        *.jpeg {
            set proc optimizeJPEG
        }
        *.png {
            set proc optimizePNG
        }
        *.gif {
            set proc optimizeGIF
        }
    }
    $proc $img /tmp/opt-image
}
proc optimizePNG {img dest} {
    pngcrush -rem alla -brute -reduce $img $dest > /dev/null
}

proc optimizeGIF {img dest} {
    convert -quiet $img $dest > /dev/null
}

proc optimizeJPEG {img dest} {
    set progressiveVariant [file dirname $dest]/prog-[file tail $dest]

    jpegtran -copy none -optimize -outfile $dest $img
    jpegtran -copy none -progressive -outfile $progressiveVariant $img

    if {[file size $dest] > [file size $progressiveVariant]} {
        file rename -force $progressiveVariant $dest
    } else {
        file delete $progressiveVariant
    }
}

proc main {args} {
    set args [::cmdline::getoptions args {
        f.arg {}
    } errore]
    dict with args {
        if {[string is false $f]} {
            set img [createTempFile]
            optimize $img
            cat /tmp/opt-image >@ stdout
        } else {
            optimize $f
            file rename -force /tmp/opt-image opt-[file tail $f]
        }

    }
}

main {*}$::argv

  1. Image opti­mi­za­tion. Sono quat­tro post che affron­tano in modo appro­fon­dito la que­stione.
  2. Come vedrete di seguito, uso YUI Compressor
  3. A meno che vi piac­cia leg­gere i file minia­tu­riz­zati!
  4. Git Attributes.
  5. Unix pipe­line: a set of pro­ces­ses chai­ned by their stan­dard streams, so that the out­put of each pro­cess (stdout) feeds direc­tly as input (stdin) to the next one.

Da Subversion a Git

Wednesday, August 4th, 2010

Qualche tempo fa avevo descritto alcuni dei miei pro­ce­di­menti per snel­lire le pro­ce­dure di deploy­ment sfrut­tando gli hooks1 di Subversion.2

Addio Subversion, buon­giorno Git!

Da allora sono pas­sato a Git. Dovendo quindi rive­dere il pro­cesso ho cer­cato anche di sfrut­tare le poten­zia­lità di que­sto soft­ware per il con­trollo di ver­sione. Per chi non avesse mai sen­tito par­lare di Git, diciamo che la dif­fe­renza prin­ci­pale rispetto a Subversion è che il primo è un sistema distri­buito, men­tre il secondo è cen­tra­liz­zato3.

Perché Git?

La prima con­se­guenza è una distin­zione molto più netta fra il con­trollo di ver­sione e la pub­bli­ca­zione delle pro­prie modi­fi­che. In Subversion l’atto stesso di fare il com­mit implica la pub­bli­ca­zione della nostra attività.

Per alle­viare il pro­blema potremmo fare il com­mit su un ramo pri­vato per poi far con­fluire que­ste revi­sioni nel filone prin­ci­pale. Qui però ci scon­triamo con un secondo limite di Subversion che è una minore robu­stezza nel ricon­ci­liare le revi­sioni. Subversion cal­cola le dif­fe­renze al momento della ricon­ci­lia­zione, igno­rando la genea­lo­gia delle due revi­sioni; Git applica le ope­ra­zioni che hanno por­tato da una revi­sione all’altra.

Nuove pos­si­bi­lità

Comunque, il punto fon­da­men­tale in que­sto con­te­sto è che in virtù della natura distri­buita di Git, ogni repo­si­tory fa sto­ria a sé ed è anche per que­sto che può dia­lo­gare con altre repo­si­tory, scam­bian­dosi le revi­sioni. Possiamo quindi crearne più d’una, anche per un sin­golo pro­getto, ognuna con la pro­pria con­fi­gu­ra­zione e con gli hooks adatti. Facciamo un paio di esempi.

Il primo è quello in cui abbiamo due repo­si­tory, una per lo sta­ging e una per la pro­du­zione. Dalla mia mac­china di svi­luppo posso man­dare le revi­sioni sull’una o sull’altra, oppure spe­dire le revi­sioni da sta­ging a pro­du­zione: gli hooks pen­se­ranno al resto. In que­sto modo pos­siamo ripor­tare in pro­du­zione la ver­sione testata in sta­ging o, se neces­sa­rio, instal­lare una modi­fica urgente diret­ta­mente in produzione.

Il secondo riguarda i fil­tri, che di solito uso per minia­tu­riz­zare CSS e JavaScript, che si appli­cano all’entrata e all’uscita, per così dire, dei con­te­nuti dalla repo­si­tory. In que­sto caso, le ope­ra­zioni aggan­ciate a que­sti fil­tri hanno senso solo nelle fasi di deploy­ment, per­ché altri­menti ci por­te­remmo nel pro­getto le ver­sioni minia­tu­riz­zate. Per fare que­sto basta atti­varli solo sulla repo­si­tory di sta­ging ed entre­ranno in azione quando espor­te­remo da lì il sorgente.

Cos’è cam­biato negli script, quindi?

Il prin­ci­pio di base non è cam­biato e con­si­ste nello sfrut­tare gli hooks messi a dispo­si­zione, in par­ti­co­lare quello di post-receive, che viene appli­cato quando una repo­si­tory riceve delle revi­sioni da un’altra4. In aggiunta ho sfrut­tato la pos­si­bi­lità di defi­nire dei fil­tri per scar­tare alcuni file, come gli unit tests o appli­care delle tra­sfor­ma­zioni ad altri.

Dato che però ora ogni repo­si­tory fa sto­ria a sé, non esi­ste più un elenco di tutti i pro­getti da con­trol­lare; poi, visto che Git non usa un numero pro­gres­sivo per indi­care la ver­sione, le con­tor­sioni per tro­vare il tag cor­retto. Per il resto la logica è invariata.

  1. Ovvero, script ese­guiti in con­co­mi­tanza di deter­mi­nati eventi
  2. Coda & Subversion
  3. In realtà la que­stione è un po’ più com­plessa per­ché Git non nasce come VCS ma, di fatto, lo usano tutti per quello
  4. È inte­res­sante notare che non esi­stono agganci alla crea­zione di un tag su una repo­si­tory. Per farlo devo rice­vere la ver­sione tag­gata da un’altra repo­si­tory

Coda & Subversion

Thursday, March 25th, 2010

Uso Coda ormai da qual­che tempo. È un buon pro­gramma per lo svi­luppo dei siti, pia­ce­vole da usare e affi­da­bile, non ho avuto biso­gno di molto altro per diverso tempo.

I limiti più evi­denti di Coda

Dopo qual­che tempo, però, ho comin­ciato a sen­tire la man­canza di Emacs e così ho con­fi­gu­rato Coda in modo da usarlo come edi­tor esterno. A quel punto ho sco­perto con fasti­dio che la lista dei file da cari­care sul sito non veniva sem­pre aggior­nata con le modi­fi­che da Emacs, nono­stante fos­sero cor­ret­ta­mente indi­vi­duati come file modi­fi­cati rispetto alla repo­si­tory nel VCS (Version Control Software; attual­mente è Subversion), costrin­gen­domi ogni volta a mar­carli a mano. No fun.

Inoltre non sem­pre abbiamo a che fare con un deploy­ment diretto. Spesso dob­biamo prima cari­care il sito su un ser­ver di sta­ging e poi, veri­fi­cato che tutto vada bene, pas­sarlo in pro­du­zione. Teoricamente potremmo “ripun­tare” Coda fra i due ser­ver ma, in pra­tica, sarebbe un delirio.

Sfruttiamo Subversion!

Il modo ovvio di risol­vere que­sti pro­blemi è usare i punti di aggan­cio che Subversion mette a dispo­si­zione, ovvero, la pos­si­bi­lità di ese­guire delle ope­ra­zioni arbi­tra­rie in con­co­mi­tanza di alcuni eventi:

  • post-commit
  • post-lock
  • post-revprop-change
  • post-unlock
  • pre-commit
  • pre-lock
  • pre-revprop-change
  • pre-unlock
  • start-commit

In que­sto caso, lo script aggan­ciato sarà aggan­ciato in post-commit ed effet­tuerà il deployment.

I det­ta­gli importanti

Una cosa piut­to­sto sem­plice, insomma, anche se ci sono diverse cosine di cui tener conto. In primo luogo, è sem­pre pre­fe­ri­bile disim­pe­gnare Subversion il prima pos­si­bile1 e quindi dob­biamo posporre le varie ope­ra­zioni; inol­tre, dato che lo script è con­di­viso per tutta la repo­si­tory, dob­biamo avere l’accortezza di fare le azioni giu­ste per il pro­getto giu­sto; infine, visto che ogni pro­getto fa sto­ria a sé, dob­biamo tro­vare un buona fles­si­bi­lità2 per poter spe­ci­fi­care le azioni che vogliamo, nel modo che rite­niamo più oppor­tuno per quel progetto.

Disimpegnare velo­ce­mente la repository

Ad ogni com­mit viene creato uno script di shell (a par­tire da un tem­plate descritto più avanti) chia­mato con il numero di release. La crea­zione è pra­ti­ca­mente istantanea.

Questo script va a finire in una spe­cie di coda sul file­sy­stem. Il fatto di usare una direc­tory invece di un file sin­golo evita pro­blemi di accesso con­tem­po­ra­neo, lasciando com­pleta libertà nel suo con­te­nuto e mi con­sente di sfrut­tare laun­chd per fare in modo che il pro­ces­sa­mento degli script nella coda venga lan­ciato solo al momento oppor­tuno. Questo è il file di configurazione:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>it.refactor.deployment-queue</string>
<key>OnDemand</key>
<true/>
<key>Program</key>
<string>/usr/local/bin/projects-auto-deployment</string>
<key>QueueDirectories</key>
<array>
<string>/var/tmp/deployment-queue</string>
</array>
</dict>
</plist>

Il det­ta­glio impor­tante è la diret­tiva QueueDirectories che, a dif­fe­renza di WatchPaths, lan­cia il pro­cesso alla modi­fica del con­te­nuto della direc­tory, ma solo quando in pre­ce­denza era vuota.

In alter­na­tiva avremmo potuto usare un qual­che dae­mon. Non un gran pro­blema ma mi sem­brava un po’ meno robusto.

Progetti diversi, copioni diversi

Come dicevo prima, lo scopo di usare degli script di shell è quello di lasciare la mas­sima libertà.

Ogni voce della con­fi­gu­ra­zione letta dallo script è simile a questa:

    <regexp> {
        env {
            REL=$rev
        }
        preflight {...}
        deploy {...}
        postflight {...}
    }

Le quat­tro voci: env, preflight, deploy e postflight rag­grup­pano e ordi­nano i diversi fram­menti che vanno a com­porre lo script di deploy­ment, che viene atti­vato in base all’eventuale match dell’espressione regolare.

L’unica varia­bile cono­sciuta è il numero della release, che è pas­sato da Subversion, il resto è tutto libero. Più gene­rico di così!

Di solito, dopo aver defi­nito le varia­bili ambien­tali in env, il preflight si occupa di fare il chec­kout dal VCS; il deploy fa quello che si può imma­gi­nare; il postflight si occupa di fare un po’ di puli­zia ecc. Ribadisco però il fatto che quelle descritte prima sono solo sezioni: se per assurdo voglio met­tere tutto nella sezione env lo posso fare.

Ma non è finita qui…

Tutto bene? Quasi. Così com’è pen­sato, il sistema non per­mette di fare alcune cose molto importanti.

Percorsi varia­bili

Se vogliamo usarlo anche per il deploy­ment delle release “tag­gate”3, dob­biamo aggan­ciare lo script al per­corso dei tag. Tuttavia, dato che l’url com­pleta cam­bia ad ogni nuovo tag — ad esem­pio avremo …/tags/REL1, …/tags/REL2 — come fac­ciamo a sapere in post-commit quale prendere?

Per ora non ho tro­vato una solu­zione gene­rica. Mi sono affi­dato al fatto che il nome della ver­sione in tags è uguale al numero pre­ce­dente della release di post-commit4. Ad esem­pio, se l’operazione è stata ese­guita alla release 3753, cer­che­remo la direc­tory r3752. Una volta otte­nuta l’URL rien­triamo nel caso standard.

Azioni spe­ci­fi­che per le sin­gole release

Un’altra cosa abba­stanza fre­quente è l’esecuzione di ope­ra­zioni spe­ci­fi­che per una data ver­sione. Esempio tipico: delle modi­fi­che allo schema del data­base, che vanno fatte una volta sola e non devono essere riapplicate.

Per risol­vere que­sto pro­blema, all’interno del pro­getto, in una direc­tory nota, creiamo uno script che verrà cer­cato dal sistema di deploy­ment per essere ese­guito5.

roll-up delle modifiche

Infine, nello sce­na­rio sta­ging & deploy­ment è desi­de­ra­bile che una serie di ope­ra­zioni fatte in sta­ging pos­sano venire appli­cate in una volta sola in deployment.

Dato che lo script spe­ci­fico per una data release è anch’esso sotto con­trollo di ver­sione, abbiamo accesso a tutta la sua sto­ria. Bisognerà andare a pren­dere quella por­zione di modi­fi­che inter­corse fra l’ultimo deploy­ment e quello attuale e appen­derle ad un unico file, con un comando del genere, ad esem­pio:
svn log -r3778:3783 -q URL | egrep ^r | awk '{ print $1 }' | sort | while read r; do svn cat -$r URL; done

Come ottengo il range da pas­sare al comando appena visto? Partiamo da un sce­na­rio sem­pli­fi­cato all’osso, sup­po­nendo di par­tire da una repo­si­tory nuova di zecca.

Dalla r1 alla r10, lavoro solo nel trunk; quando creo un nuovo tag, in base alle con­ven­zioni adot­tate prima, il suo nome sarà r10 e il suo numero di release sarà r11. Proseguendo, dalla r12 alla r20, lavoro sem­pre nel trunk; come prima, creando un nuovo tag, il nome sarà r20 e il suo numero di release sarà r21.

A que­sto punto, se cerco tutte le modi­fi­che in tags, esclu­dendo quella rela­tiva alla crea­zione della direc­tory tags, avrò r11 ed r21. Come prima, in base al numero di release pas­sato in post-commit, che al secondo tag sarà 20, posso rica­vare il range da con­si­de­rare, cal­co­la­bile come (release immediatamente precedente):(numero di release - 1), ovvero, r11:r19.

There’s more than one way to do it

Per chi non avesse voglia di rein­ven­tare la ruota, ci sono delle alter­na­tive. In par­ti­co­lare, una delle pos­si­bi­lità che ho preso in con­si­de­ra­zione è quella di instal­lare il soft­ware di VCS diret­ta­mente sul server.

In effetti è una solu­zione piut­to­sto comoda, dato che basta fare un svn up per aggior­nare l’installazione e con VCS distri­buiti le pos­si­bi­lità sono ancora più ampie.

Ci sono però diversi punti dolenti. Come visto prima è raro che basti un sem­plice aggior­na­mento del codice; in secondo luogo non mi piace mol­tis­simo l’idea che si crei un’ulteriore dipen­denza fra il sistema che pub­blica gli aggior­na­menti e quello che li riceve.

Tolta que­sta pos­si­bi­lità, ci sono i soft­ware di SCM veri e pro­pri, da Puppet a Capistrano, pas­sando per Fabric. Il primo pro­pone un modello in pull, dove i cliente con­tat­tano il ser­ver per sta­bi­lire cosa c’è da fare; il secondo e il terzo sono più vicini ai sistemi in push, dove l’azione è ini­ziata dal server.

Infine, non dimen­ti­chia­moci di Hudson che, pur essendo un ser­ver di inte­gra­zione, può auto­ma­tiz­zare pra­ti­ca­mente qual­siasi cosa e quindi anche pro­cessi come questi.

Hudson è quello più adatto, essendo un vero e pro­prio ser­ver di con­ti­nous inte­gra­tion. Anzi, non escludo di comin­ciare ad usarlo a breve, ma per ora mi bastano le due righe che ho scritto in Tcl.

Per quanto riguarda gli SCM, più vicini al mondo dei sysad­min, Puppet mi piace di più, anche per­ché ci ho gio­cato un paio di volte, men­tre ho visto usare Capistrano in situa­zioni simili a quelle descritte.

Come si può notare, alcune delle parti più con­torte sono dovute a Subversion. Dato che ho in pro­getto di pas­sare a Git, sono curioso di sapere se e come il pro­ce­di­mento si semplificherà.

  1. In post-commit Subversion ignora l’esito delle ope­ra­zioni, dato che il com­mit è già stato fatto; tut­ta­via non mi è chiaro se lo ese­gua in asin­crono
  2. avete mai sen­tito di un pro­getto che non deve essere fles­si­bile?
  3. Per chi non lo sapesse, un tag è un’istantanea del codice sor­gente ad un dato momento. In Subversion, è di prassi che le ver­sioni uffi­ciali stiano in tags e le ver­sioni di svi­luppo che deviano da quella prin­ci­pale stiano in branches.
  4. l’uso dei numeri di release come nome del tag non mi risulta sia la cosa più con­si­glia­bile, lo so, tut­ta­via nelle appli­ca­zioni con rila­sci con­ti­nui la cosa non mi sem­bra così grave
  5. Qualcuno stor­cerà il naso nel vedere una com­mi­stione fra deploy­ment e svi­luppo, all’interno del sor­gente. Non è sicu­ra­mente la solu­zione più pulita anche se ha l’indubbio van­tag­gio di tenere sotto con­trollo di ver­sione anche cose che spesso ven­gono lasciate un po’ a se stesse. Ad oggi credo che il bilan­cio costi/benefici sia posi­tivo.

Semantic versioning in tcl

Tuesday, December 29th, 2009

Leggendo que­sto arti­colo che defi­ni­sce le pra­ti­che per quello che viene defi­nito seman­tic ver­sio­ning, mi è venuto in mente che tcl sup­porta diret­ta­mente a livello di lin­guag­gio il con­trollo della ver­sione dei pac­kage caricati.

Package require e i moduli in soccorso

L’uso è banale: basta spe­ci­fi­care la ver­sione che si vuole usare, con la pos­si­bi­lità di essere restrit­tivi fino al punto di cari­care una ver­sione spe­ci­fica e non una sem­pli­ce­mente compatibile.

La cosa mi è stata molto utile durante lo svi­luppo di alcuni moduli. Come spesso suc­cede, man mano che le idee diven­tano più chiare, le prime ver­sioni sono quasi sem­pre da rifare; tut­ta­via era spesso impos­si­bile modi­fi­care subito tutti gli script che uti­liz­za­vano il modulo rivi­sto per far­gli cari­care subito la nuova ver­sione. È bastato quindi spe­ci­fi­care la ver­sione cor­retta nel codice che usava quel modulo.

Bisogna anche notare che le due ver­sioni dei moduli pos­sono con­vi­vere nel file system tran­quil­la­mente l‘una accanto all’altra gra­zie all’infra­strut­tura dei moduli.

Non è comun­que pos­si­bile cari­care due ver­sioni diverse all’interno dello stesso inter­prete 1 ma visto che in Tcl è faci­lis­simo creare inter­preti aggiun­tivi, il pro­blema potrebbe essere miti­ga­bile:

% set slave1 [interp create]
interp0
% set slave2 [interp create]
interp1
% $slave1 eval {source a.tcl}
% $slave1 eval {source a2.tcl}
conflicting versions provided
for package "a": 1.0, then 1.2.0
# a.tcl e a2.tcl contengono due
# versioni dello stesso package
% $slave2 eval {source a2.tcl}

E gli altri?

Che io sap­pia, non mi risulta che né PerlPythonRuby pos­sie­dano que­sta caratteristica.

Qualcuno ha infor­ma­zioni più pre­cise su que­sti o altri linguaggi?

  1. cosa invece pos­si­bile in Erlang

StarKits: ultimi dettagli

Monday, December 21st, 2009

Nel chiu­dere que­sta pano­ra­mica sugli Starkit, riporto qual­che infor­ma­zione in più sugli aspetti che mi sem­brano meri­tino un qual­che approfondimento.

VFS: Virtual File System

Una delle carat­te­ri­sti­che più inte­res­santi può essere usata anche al di fuori degli Starkit ed è il Virtual File System. Ricorda un po’ il clas­sico approc­cio Unix, dove uno dei modi pre­fe­riti di ren­dere dispo­ni­bili degli oggetti al sistema ope­ra­tivo è quello di rap­pre­sen­tarli come file system.

Allo stesso modo, con i VFS, è pos­si­bile “mon­tare” un sito ftp remoto, come un data­base Metakit, addi­rit­tura un name­space, come in que­sto esem­pio, dove leg­giamo il con­te­nuto della pro­ce­dura unknown:


package require vfs::ns
vfs::ns::Mount :: ::
cd ::
set f [open "unknown" r]
set proc_definition [read $f]
close $f

Ci sono anche altri modi di otte­nere lo stesso risul­tato, a volte anche più adatti, ma l’uso dei comandi di I/O può essere a volte il sistema giusto.

StarKits auto–aggiornanti

Gli Starkit (ma non gli Starpack) pos­sono essere modi­fi­cati al run­time e in tran­quil­lità, visto che Metakit è transazionale.

In que­sto modo diventa pos­si­bile usare gli Starkit un po’ come con Java Web Start, dove lo Starkit distri­buito si inca­ri­cherà esso stesso di sca­ri­care e instal­lare i com­po­nenti neces­sari diret­ta­mente al suo interno.

Gli StarPack

Saturday, December 12th, 2009

Adesso che abbiamo visto come sono costruiti gli StarKit, è sem­plice esten­dere il discorso agli StarPacks.

Premesse

Prima di tutto dob­biamo ricor­dare che uno StarKit può con­te­nere delle com­po­nenti com­pi­late. In virtù di que­sto fatto, pos­siamo costruire un StarKit che con­terrà al suo interno l’interprete spe­ci­fico per una data piat­ta­forma. In pra­tica, la dif­fe­renza fra uno StarKit e uno StarPack sta nel fatto che quest’ultimo usa un ese­gui­bile com­pi­lato per avviarsi e include tutte le libre­rie nor­mal­mente incluse con TclKit.

Pregi e difetti

Le limi­ta­zioni più impor­tanti di uno StarPack sono:

  1. dato che usiamo dei com­pi­lati, ogni StarPack sarà spe­ci­fico per un dato sistema operativo;
  2. gli StarPack non pos­sono auto–modificarsi 1.

La con­tro­par­tita è un sistema di distri­bu­zione com­ple­ta­mente auto–contenuto, senza alcuna instal­la­zione aggiuntiva.

La crea­zione di uno StarPack è molto simile a quella di uno StarKit: l’unica dif­fe­renza è che nel primo caso dovremo spe­ci­fi­care il run­time da asso­ciare al pro­dotto finito.

È inte­res­sante notare che gli stessi TclKit sono creati come StarPack, StarPack che non hanno codice appli­ca­tivo ma solo l’interprete e libre­rie neces­sa­rie 2

  1. par­le­remo più avanti di que­sta pos­si­bi­lità per gli StarKit
  2. Credo sia anche que­sto il motivo per cui, durante la crea­zione di uno StarPack, non pos­siamo lo stesso TclKit sia per ese­guire sdx che run­time da copiare nello StarPack. Probabilmente (non ho tro­vato infor­ma­zioni certe) il pro­cesso di costru­zione pren­derà il TclKit, mon­terà il vir­tual file system e ci copierà l’applicazione.

Anatomia di uno StarKit

Thursday, December 10th, 2009

Una defi­ni­zione

Uno StarKit è un cri­te­rio per impac­chet­tare tutte le risorse neces­sa­rie ad un’applicazione in modo auto–contenuto, che non neces­sita di alcuna instal­la­zione e portabile.

In cosa dif­fe­ri­sce uno StarKit?

Non è un’idea nuova, pen­siamo per esem­pio ai Jar nel mondo Java, che sono degli zip con un file – il MANIFEST.MF – che indica la classe per avviare l’applicazione. Tuttavia, un Jar non è stato pen­sato per essere com­ple­ta­mente indi­pen­dente, tant’è vero che è pos­si­bile indi­care nel MANIFEST.MF dei para­me­tri per il clas­spath, anche se non fun­ziona molto bene, anzi. Lo stesso può essere detto, grosso modo, anche per for­mati come war, ear e com­pa­gnia bella.

Se tra­la­sciamo la que­stione dell’auto–contenimento, il tratto distin­tivo di uno StarKit è l’uso di un file system vir­tuale, che con­sente all’applicazione di com­por­tarsi esat­ta­mente come se fosse lan­ciata da una ver­sione “spac­chet­tata”. All’avvio, que­sto file system viene infatti mon­tato in modo che appaia dal punto di vista dell’applicazione come una nor­male directory.

Creare uno StarKit con sdx

Uno StarKit viene creato con… un altro StarKit, chia­mato StarKit Developer eXten­sion (sdx), a cui pas­se­remo la nostra appli­ca­zione da impac­chet­tare. In ogni caso la strut­tura risul­tante sarà una cosa del genere:

  • myKit.vfs
    • main.tcl
    • lib
    • app-myKit
      • myKit.tcl
      • pkgIndex.tcl

La sequenza di avvio

Ovvero, tutta l’applicazione viene messa sotto lib, men­tre in main.tcl viene inse­rito il codice (gene­rato da sdx) neces­sa­rio all’avvio dell’applicazione:

 package require starkit
 starkit::startup
 package require app-myKit

La prime due righe cari­cano il modulo dello star­kit, ini­zia­liz­zando il file system vir­tuale e con­fi­gu­rando la varia­bile auto_path; la terza riga causa il cari­ca­mento di myKit.tcl e l’avvio vero e pro­prio dell’applicazione.

La pros­sima volta par­le­remo degli StarPack.