Files sorgente multipli

Il package main non deve essere necessariamente in un unico file
L’unico requisito è che la func main() sia nel package main
Tutti i file che dichiarano package main devono essere compilati e costruiti in un unico comando go

Proviamo un esempio in una directory dedicata:

mkdir 72multiple
cd 72multiple

In questa directory creiamo due file

  • main.go – con la funzione main
  • utils.go – con funzioni di utilità

Entrambi i file appartengono al package main

(main.go):

package main

func main() {
        // Se usata spesso p è meglio di fmt.Println
        p("Version: ", version)
}

(utils.go):

package main

import "fmt"

// Definizione di p
func p(a ...interface{}) {
        fmt.Println(a)
}

var version = "unknown"

Per il run:

go run main.go util.go

Per il build:

go build main.go util.go

L’eseguibile è main, come il file che ha func main()

Se I file che dichiarano package main sono molti si può usare un po’ di magia shell:

go build `grep -H "package main" *| cut -d: -f1`

(attenzione agli apici rovesciati)

Per più flessibilità si può scrivere un Makefile.

(Makefile):

# Basta cambiare le tre linee seguenti
PROGS = main.go utils.go
MAIN = main
EXEC = printver
# ----- in qualsiasi progetto

help:
        @echo "usage: make target"
        @echo "\trun - run immediately"
        @echo "\tbuild - compile and link the executable"
        @echo "\tclobber - clean up the project"

run:
        go run $(PROGS)

build:
        go build $(PROGS)
        mv $(MAIN) $(EXEC)

clobber:
        rm -f $(EXEC)

Non è veramente parte della filosofia Go scrivere un Makefile. Può tornare però utile quando si mescola la tecnologia Go con altre e diverse utilities di Linux.


Attenzione

Col ‘copia-e-incolla’ i TAB del Makefile diventano spazi.
Il Makefile richiede che le linee siano indentate da un TAB
Usare il comando vi di sostituzione globale

:g/^  */s//^I/

Qui ^ indica l’inizio riga, poi vi sono due spazi e un *
Almeno uno spazio seguito da un numero qualsiasi di spazi (anche zero)
Il ^I rappresenta il TAB e si ottiene battendo Ctrl-V seguito dal TAB.
Ctrl-V toglie il significato al carattere che segue e lo rappresenta letteralmente

Interfaccia con git

Il nostro programma sopra stampa

[Version:  unknown]

che è proprio quello che dice utils.go.

Di solito però i nostri progetti, che si evolvono nel tempo, sono sottoposti al controllo versione con l'utility git.

Facciamolo:

git init
git add .
git commit -m "Prima versione"

Ora aggiungiamo un tag di versione:

git tag -a 0.1 -m "Versione 0.1"

La compilazione del programma avviene ora con un flag passato al linker:

go build -i -v -ldflags="-X main.version=\
  $(git describe --always --long --dirty)" \
  main.go utils.go

Il \ rappresenta che in realtà il comando sopra deve occupare un'unica linea logica ma qui è spezzato su più linee fisiche per convenienza visiva. (Si può compiere il copia e incolla anche con i \ e la shell riconoscerà lo spezzamento fisico).

Se ora lanciamo il programma verrà qualcosa tipo:

[Version:  0.1-0-g1cf7ed9]

Nel comando sopra:

  • -ldflags passa i flag al linker
  • -X dice di compiere una sostituzione
  • main.version è il simbolo version del package main
  • $( ... ) è un costrutto shell che esegue il comando racchiuso tra tonde e interpola localmente l'output del comando
  • git describe --always --long --dirty è il comando git per recuperare l'ultimo tag
    • --always anche se il tag è light e non annotato
    • --long formato lungo
    • --dirty anche se non vi è stato ancora un commit dopo l'attribuzione del tag

Si può modificare il Makefile per inglobare questo comando:

(Makefile):

# Basta cambiare le tre linee seguenti
PROGS = main.go utils.go
MAIN = main
EXEC = printver
# ----- in qualsiasi progetto
# $$ diventa $ quando a sua volta interpolato
LDFLAGS = "-X main.version=$$(git describe \
--always --long --dirty)"

help:
        @echo "usage: make target"
        @echo "\trun - run immediately"
        @echo "\tbuild - compile and link the executable"
        @echo "\tclobber - clean up the project"

run:
        go run $(PROGS)

build:
        go build -i -v -ldflags=$(LDFLAGS) $(PROGS)
        mv $(MAIN) $(EXEC)

clobber:
        rm -f $(EXEC)