├── README.md ├── cmd ├── Makefile ├── server │ └── server.go └── version.go └── server.go /README.md: -------------------------------------------------------------------------------- 1 | # go-embed-version 2 | 3 | This is a quick tutorial on embedding build-time information, such as version, into a Go binary. I'm using `make` in this case but it's not required. I'm also not very adept with `make` so take that into consideration. 4 | 5 | # -ldflags -X 6 | 7 | `go build` will accept a flag `-ldflags` to pass specific arguments to the linker. The `link` tool will accept a `-X` flag to set the value of an exported variable. It will only set variables, not constants. The full import path to the variable must be specified. 8 | 9 | # A Version variable 10 | 11 | In my projects I tend to create a `/cmd/version.go` file. Each binary can import this package. The location for the variable can be in any package you like. 12 | 13 | `cmd/version.go`: 14 | 15 | ``` 16 | package cmd 17 | 18 | import ( 19 | "flag" 20 | "fmt" 21 | "os" 22 | 23 | ) 24 | 25 | var ( 26 | Version = "" // set at compile time with -ldflags "-X versserv/cmd.Version=x.y.yz" 27 | 28 | FVersion = flag.Bool("version", false, "show version and exit") 29 | 30 | ) 31 | 32 | func ShowVersion() { 33 | fmt.Println("version:", Version) 34 | os.Exit(0) 35 | 36 | } 37 | ``` 38 | 39 | I also provide a flag and utility function to show the version and exit. If the version isn't set ont he command line during `go build` the variable is unchanged: 40 | 41 | ``` 42 | cmd$ go build -o /tmp/versserv server/server.go 43 | cmd$ /tmp/versserv -version 44 | version: 45 | ``` 46 | 47 | You can set it to an arbitrary value by hand: 48 | 49 | ``` 50 | cmd$ go build -o /tmp/versserv -ldflags "-X github.com/AgentZombie/go-embed-version/cmd.Version=foo" server/server.go 51 | cmd$ /tmp/versserv -version 52 | version: foo 53 | ``` 54 | 55 | # Setting the variable automatically 56 | 57 | I'm using `make` and I have it create the version string based on `git` tag or a timestamp, depending on if it's a dev or release binary. 58 | 59 | `Makefile`: 60 | 61 | ``` 62 | VERSION := $(shell git tag | grep ^v | sort -V | tail -n 1) 63 | LDFLAGS = -ldflags "-X github.com/AgentZombie/go-embed-version/cmd.Version=${VERSION}" 64 | TIMESTAMP := $(shell date +%Y%m%d-%H%M) 65 | DEVLDFLAGS = -ldflags "-X github.com/AgentZombie/go-embed-version/cmd.Version=dev-${TIMESTAMP}" 66 | 67 | SERVBIN=/tmp/versserv 68 | 69 | dev-serv: server/* 70 | go build ${DEVLDFLAGS} -o ${SERVBIN} server/*.go 71 | 72 | serv: server/* 73 | go build ${LDFLAGS} -o ${SERVBIN} server/*.go 74 | ``` 75 | 76 | The `VERSION` pipeline in the `Makefile` lists all tags visible from this commit, selects those that being with `v` assuming those are semver tags. It sorts those using `sort`'s version sorting, then keeps only the latest one.' 77 | 78 | ``` 79 | cmd$ make dev-serv 80 | go build -ldflags "-X github.com/AgentZombie/go-embed-version/cmd.Version=dev-20190608-1945" -o /tmp/versserv server/*.go 81 | cmd$ /tmp/versserv -version 82 | version: dev-20190608-1945 83 | cmd$ rm /tmp/versserv 84 | cmd$ make serv 85 | go build -ldflags "-X github.com/AgentZombie/go-embed-version/cmd.Version=v0.2.0" -o /tmp/versserv server/*.go 86 | cmd$ /tmp/versserv -version 87 | version: v0.2.0 88 | ``` 89 | 90 | # Tagging a version 91 | 92 | In `git` tags are arbitrary labels but it's a common convention to use tags with [Semantic Versioning](https://semver.org/). Tags apply to _commits_ so any changes you want a tag to apply to must be committed. A simple tag operation looks like this: 93 | 94 | ``` 95 | git tag -a v0.3.0 96 | ``` 97 | 98 | You can also apply a message with the tag and it's good to use this to communicate what's captured in this version: 99 | 100 | ``` 101 | git tag -a v0.3.0 -m 'Add tutorial in README.md' 102 | ``` 103 | 104 | By default, tags aren't pushed upstream with other commits. To push with tags add `--follow-tags`: 105 | 106 | ``` 107 | git push --follow-tags 108 | ``` 109 | 110 | You can also configure `git` to always push tags with commits but it's not the best idea. 111 | 112 | # Additional use for the version 113 | 114 | I also build `docker` images using `make` and include the version tag in the build: 115 | 116 | ``` 117 | docker-image: 118 | docker build -t versserv:${VERSION} dockerdir 119 | ``` 120 | 121 | # Special Thanks 122 | 123 | Special thanks to [subcon](https://github.com/subcon42) for gently nudging me to publish this. 124 | -------------------------------------------------------------------------------- /cmd/Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell git tag | grep ^v | sort -V | tail -n 1) 2 | LDFLAGS = -ldflags "-X github.com/AgentZombie/go-embed-version/cmd.Version=${VERSION}" 3 | TIMESTAMP := $(shell date +%Y%m%d-%H%M) 4 | DEVLDFLAGS = -ldflags "-X github.com/AgentZombie/go-embed-version/cmd.Version=dev-${TIMESTAMP}" 5 | 6 | SERVBIN=/tmp/versserv 7 | 8 | dev-serv: server/* 9 | go build ${DEVLDFLAGS} -o ${SERVBIN} server/*.go 10 | 11 | serv: server/* 12 | go build ${LDFLAGS} -o ${SERVBIN} server/*.go 13 | -------------------------------------------------------------------------------- /cmd/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/AgentZombie/go-embed-version" 7 | "github.com/AgentZombie/go-embed-version/cmd" 8 | ) 9 | 10 | func main() { 11 | flag.Parse() 12 | if *cmd.FVersion { 13 | cmd.ShowVersion() 14 | } 15 | 16 | s, err := versserv.NewServer() 17 | if err != nil { 18 | panic("creating server: " + err.Error()) 19 | } 20 | 21 | if err := s.ListenAndServe(); err != nil { 22 | panic("listening: " + err.Error()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | var ( 10 | Version = "" // set at compile time with -ldflags "-X versserv/cmd.Version=x.y.yz" 11 | 12 | FVersion = flag.Bool("version", false, "show version and exit") 13 | ) 14 | 15 | func ShowVersion() { 16 | fmt.Println("version:", Version) 17 | os.Exit(0) 18 | } 19 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package versserv 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/AgentZombie/go-embed-version/cmd" 9 | ) 10 | 11 | type Server struct { 12 | // server components 13 | } 14 | 15 | func NewServer() (*Server, error) { 16 | // make your own Server, Mux 17 | s := &Server{} 18 | http.HandleFunc("/", s.root) 19 | return s, nil 20 | } 21 | 22 | func (s *Server) ListenAndServe() error { 23 | log.Print("starting server version ", cmd.Version) 24 | return http.ListenAndServe("localhost:8000", nil) 25 | } 26 | 27 | func (s *Server) root(w http.ResponseWriter, r *http.Request) { 28 | w.Header().Set("X-Version", cmd.Version) 29 | w.Header().Set("Content-Type", "text/html") 30 | fmt.Fprintln(w, "

Hello World!

") 31 | } 32 | --------------------------------------------------------------------------------