Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

generate build timestamp in Go

Tags:

go

I want in a Go program (using Go 1.11.1 on Debian/Linux/x86-64) to keep the build timestamp with a line explaining the last git commit.

In a C program (FWIW my bismon project is doing something very similar), I would just generate some _timestamp.c file, e.g. with a Makefile recipe like:

_timestamp.c: 
    date +'const char my_timestamp[]='%c';%n' > $@
    (echo -n 'const char my_lastgitcommit[]="'; \
     git log --format=oneline --abbrev=12 --abbrev-commit -q | head -1 \
       | tr -d '\n\r\f\"\\\\' ; echo '";') >> $@

and I would link my program myprog with something like:

myprog: $(MYOBJECTS) _timestamp.c
    $(LINK.c) $(MYOBJECTS) _timestamp.c -o $@
    $(RM) _timestamp.c

Notice that _timestamp.c is automatically removed at each successful link. Of course in some header I would declare extern const char my_timestamp[]; and extern const char my_lastgitcommit[]: and I would use e.g. my_timestamp and my_lastgitcommit in my main.c (and have MYOBJECTS contain main.o)

It looks like go generate could be used to behave in a similar way. I would like to have a package "timestamp" defining two string globals timestamp.My_timestamp and timestamp.My_gitcommit but I don't exactly understand how to do it.

I tried to add some timestamp/timestamp.go file with

package timestamp

//go:generate date +'var My_timestamp = "%c"%n'

// Code generated - DO NOT EDIT.

But it did not change with go generate then go install

Of course, these timestamps should be constant strings at compile time, and I expect to find them when running the strings(1) utility on the ELF executable.

BTW, I recall one of the motivations of the go command:

An explicit goal for Go from the beginning was to be able to build Go code using only the information found in the source itself, not needing to write a makefile or one of the many modern replacements for makefiles. If Go needed a configuration file to explain how to build your program, then Go would have failed.

So I am still expecting something to go into the source code alone, without extra configuration for building.

In other words, I want to generate at every build a Go file similar to:

// generated timestamp.go file
package timestamp
var Buildtime = "Tue 30 Oct 2018 09:39:01 AM MET";
var Buildlastgitcommit = "7fde394b60bc adding timestamp.go"

The Buildtime string is generated by date +%c. The Buildlastgitcommit string might be generated by commands similar to what my _timestamp.c make rule is doing.

I need these strings to be constant and built-in the ELF executable produced by a Go build (which I would prefer to be done by usual commands, either without extra arguments to go build or any other build automation tool, or with some way to fail the build if the mandatory arguments are forgotten; hence atanayel's answer is not enough). So I want the strings(1) utility to find these strings quickly in the executable. And the generation of such files should be configured in some kind of files, not requiring extra arguments to builders.

I could consider switching to some other, Go-friendly, build automation system (but it seems that even with gb I can't easily do what I want: quickly generate some simple .go file at every build). But I don't understand why it is so difficult to use generated Go files in Go programs. Generating simple code is following the Unix philosophy, and has been practiced since many decades (e.g. see goyacc inspired by the old yacc program).

NB: the rationale for go generate explicitly mentions that:

It is not a goal of this proposal to build a generalized build system like the Unix make(1) utility.

and later

once things are settled, the author commits the generated files to the source repository,

(and this is not my use case)

PS. I only care about POSIX systems; I really don't care if my Go software cannot be built on Windows. And I tend to think that (contrarily to what go command motivation explains), in my particular case, I do need some build automation tool. In my bastawigo toy project (GPLv3+), I am using make (driving the go command)

like image 427
Basile Starynkevitch Avatar asked Oct 28 '18 11:10

Basile Starynkevitch


Video Answer


2 Answers

You can use build time flags to do this. This answer explains it for a different use case, but yours is identical to it. You should do something like this.

  • Define a constant in your code something like buildTime
  • When building for release , do something like go build -ldflags='-X buildTime="My build time output from a command"'
  • Now your variable buildTime is constant and equals to My build time output from a command.
  • You can use your previous command to find the correct My build time output froma a command.

Refer to the answer I linked for better explanation.

like image 182
atakanyenel Avatar answered Sep 21 '22 14:09

atakanyenel


As other answers mentioned, you can inject the needed values into the binary at build time.

For example if we have these package variables:

// build flags
var (
    BuildTime  string
    CommitHash string
    GoVersion  string
    GitTag     string
)

Then we can fill them using this bash script:

#!/bin/sh

clear

TRG_PKG='main'
BUILD_TIME=$(date +"%Y%m%d.%H%M%S")
CommitHash=N/A
GoVersion=N/A
GitTag=N/A

if [[ $(go version) =~ [0-9]+\.[0-9]+\.[0-9]+ ]];
then
    GoVersion=${BASH_REMATCH[0]}
fi

GV=$(git tag || echo 'N/A')
if [[ $GV =~ [^[:space:]]+ ]];
then
    GitTag=${BASH_REMATCH[0]}
fi

GH=$(git log -1 --pretty=format:%h || echo 'N/A')
if [[ GH =~ 'fatal' ]];
then
    CommitHash=N/A
else
    CommitHash=$GH
fi

FLAG="-X $TRG_PKG.BuildTime=$BUILD_TIME"
FLAG="$FLAG -X $TRG_PKG.CommitHash=$CommitHash"
FLAG="$FLAG -X $TRG_PKG.GoVersion=$GoVersion"
FLAG="$FLAG -X $TRG_PKG.GitTag=$GitTag"

if [[ $1 =~ '-i' ]];
then
    echo 'go install'
    go install -v -ldflags "$FLAG"
else
    echo 'go build'
    go build -v -ldflags "$FLAG"
fi

Sample code took from this repo.

like image 29
Kaveh Shahbazian Avatar answered Sep 19 '22 14:09

Kaveh Shahbazian