[help] Is date/time available as a variable during compilation?

685 views
Skip to first unread message

Hotei

unread,
May 9, 2014, 10:50:21 AM5/9/14
to golan...@googlegroups.com
Trying to do some program documentation and would like to automatically document when the program was compiled by embedding it in the version string.  I can get the go version with runtime.Version() and in C/C++ I'd use __DATE__ and __TIME__.  Does go have anything similar to that? Would like to end up with something like this :

$ foo -version

and have it print

$ program "foo" version 1.2.3 compiled with go1.2.1 on 2014-05-09.10:11:12


Konstantin Kulikov

unread,
May 9, 2014, 11:07:58 AM5/9/14
to golan...@googlegroups.com
http://play.golang.org/p/g9Dv_1YOFR

go tool 6l -h for more info.

minux

unread,
May 9, 2014, 11:07:56 AM5/9/14
to David Rook, golang-nuts

No. you will need other ways to embed timestamps in go binaries (using -ldflags -X).

Jan Mercl

unread,
May 9, 2014, 11:08:10 AM5/9/14
to Hotei, golang-nuts
Partial solution (sans the v 1.2.3 thing):

package main

import (
"flag"
"fmt"
"os"
"runtime"
)

func main() {
version := flag.Bool("version", false, "print info about
version and exit")
flag.Parse()

if *version {
bin := os.Args[0]
var dtg string
fi, err := os.Stat(bin)
if err == nil {
dtg = "on " + fi.ModTime().Format("2006-01-02.15:04:05")
}
fmt.Printf("program %q, compiled with go %s %s\n",
bin, runtime.Version(), dtg)
}
}

/*

(17:00) jnml@r550:~/src/tmp/version$ go clean
(17:00) jnml@r550:~/src/tmp/version$ date
Pá kvě 9 17:00:55 CEST 2014
(17:00) jnml@r550:~/src/tmp/version$ go build
(17:00) jnml@r550:~/src/tmp/version$ ./version -version
program "./version", compiled with go devel +f8b50ad4cac4 Mon Apr 21
17:00:27 2014 -0700 on 2014-05-09.17:00:58
(17:01) jnml@r550:~/src/tmp/version$ date
Pá kvě 9 17:01:08 CEST 2014
(17:01) jnml@r550:~/src/tmp/version$ go build
(17:01) jnml@r550:~/src/tmp/version$ ./version -version
program "./version", compiled with go devel +f8b50ad4cac4 Mon Apr 21
17:00:27 2014 -0700 on 2014-05-09.17:01:10
(17:01) jnml@r550:~/src/tmp/version$

*/

-j

Hotei

unread,
May 9, 2014, 12:06:41 PM5/9/14
to golan...@googlegroups.com, Hotei
Jan,
That works perfectly!  I was imagining some complex workarounds but this is simple and portable.  Thanks for the help.  I really appreciate it.
David

Marvin Renich

unread,
May 9, 2014, 2:43:17 PM5/9/14
to golan...@googlegroups.com
* Hotei <hote...@gmail.com> [140509 12:07]:
> Jan,
> That works perfectly! I was imagining some complex workarounds but this is
> simple and portable. Thanks for the help. I really appreciate it.
> David

Except that it doesn't do what you asked. It reports the file
modification timestamp of the executable, which is highly unreliable as
a means for determining when the compile was done. Simply copying the
file on most *nix systems will not preserve the file timestamp, and
while some of the ways that the file can be moved around on Windows will
preserve this, not all do.

Also, this requires that the file be executed in such a way that
os.Args[0] is either an absolute path to the executable or you are
executing the file from the current directory. Try moving (not copying)
the file from the current directory into a directory in your path. I
have ~/bin in my path, so moving the executable to ~/bin/showversion and
then running showversion -version will not give you the information you
want.

...Marvin

Hotei

unread,
May 9, 2014, 4:31:10 PM5/9/14
to golan...@googlegroups.com, mr...@renich.org
Marvin,
You're right on both counts.  :-(   While the previous solution works for maybe 90% of my intended usage it is more fragile than I wished.

On reflection, I decided on a slightly more complex strategy.  I use makefiles to build so I can include go vet and gofmt all in one pass.  I'll simply write a short program godatetime whose only function is to write something like this to stdout:

package main 

var CompiledDate = 2014-04-09  (from time.Now() of course)
var CompiledTime = 01:02:03


Then I can use CompiledDate and CompiledTime inside the code I'm building.

The build makefile would look something like this

all:
  godatetime > compiledate.go
  go build
  gofmt -w *.go
  govet

minux

unread,
May 9, 2014, 4:35:57 PM5/9/14
to David Rook, Marvin Renich, golang-nuts


On May 9, 2014 4:31 PM, "Hotei" <hote...@gmail.com> wrote:
>
> Marvin,
> You're right on both counts.  :-(   While the previous solution works for maybe 90% of my intended usage it is more fragile than I wished.
>
> On reflection, I decided on a slightly more complex strategy.  I use makefiles to build so I can include go vet and gofmt all in one pass.  I'll simply write a short program godatetime whose only function is to write something like this to stdout:
>
> package main 
>
> var CompiledDate = 2014-04-09  (from time.Now() of course)
> var CompiledTime = 01:02:03
>
>
> Then I can use CompiledDate and CompiledTime inside the code I'm building.

instead of generating source file, why not use -ldflags -X? this way you don't need to recompile the package every time.

godoc cmd/ld has the documentation about -X.

Hotei

unread,
May 9, 2014, 4:55:01 PM5/9/14
to golan...@googlegroups.com, David Rook, Marvin Renich
minux,
Two reasons - primarily because I'm not familiar with the linker's operation, but also because I have the get the date / time strings from somewhere anyway. I'd need to generate the -X CompiledDate "date" and -X CompileTime "time" string values.  While not a showstopper it's a two shell calls to extract them and save as variables in the makefile.  

Hotei

unread,
May 9, 2014, 5:33:58 PM5/9/14
to golan...@googlegroups.com, mr...@renich.org
Here's source for godatetime:

package main

import (
"fmt"
"time"
)

func main() {
var t1 = `package main

var CompileDateTime = "`


fmt.Printf("%s%s\"\n",t1,time.Now())

}

It worked out fine this way.

Hotei

unread,
May 9, 2014, 5:37:26 PM5/9/14
to golan...@googlegroups.com, mr...@renich.org
Here's what I came up with for godatetime:

package main

import (
"fmt"
"time"
)

func main() {
var t1 = `package main

var CompileDateTime = "`


fmt.Printf("%s%s\"\n",t1,time.Now())

}

It worked out fine this way.  It's also expandable should other things need to be "saved" at their compile time values.

minux

unread,
May 9, 2014, 5:45:38 PM5/9/14
to David Rook, golang-nuts, Marvin Renich


On May 9, 2014 4:55 PM, "Hotei" <hote...@gmail.com> wrote:
>
> minux,
> Two reasons - primarily because I'm not familiar with the linker's operation, but also because I have the get the date / time strings from somewhere anyway. I'd need to generate the -X CompiledDate "date" and -X CompileTime "time" string values.  While not a showstopper it's a two shell calls to extract them and save as variables in the makefile.  

it's as simple as this:
// main.go

package main
var buildtime string

// ....

go build -ldflags "-X main.buildtime '`date`'"

actually much simpler than generating a new source file.

Ian Dawes

unread,
May 9, 2014, 8:16:48 PM5/9/14
to golan...@googlegroups.com, David Rook, Marvin Renich
I think it might be worth noting that embedding the compile time into the executable removes your ability to produce 100% reproducible builds without messing with your system clock. 

  -- Ian

minux

unread,
May 9, 2014, 8:24:25 PM5/9/14
to Ian Dawes, David Rook, golang-nuts, Marvin Renich


On May 9, 2014 8:16 PM, "Ian Dawes" <i...@dawesnet.net> wrote:
> I think it might be worth noting that embedding the compile time into the executable removes your ability to produce 100% reproducible builds without messing with your system clock. 

embedding the build time and 100% reproducible builds are fundamentally incompatible goals.

the alternative is perhaps embedding the source code revision into the binary. and i think that makes more sense if you also want reproducible builds.

Hotei

unread,
May 9, 2014, 11:19:10 PM5/9/14
to golan...@googlegroups.com, Ian Dawes, David Rook, Marvin Renich
minux,

Thanks for the solution you provided.  It's simple - once you gave the example it's simpler than I thought. I'll definitely try it.

Reproducible builds aren't a goal for me.   

Nate Finch

unread,
May 10, 2014, 7:21:23 AM5/10/14
to golan...@googlegroups.com, Ian Dawes, David Rook, Marvin Renich
I'm curious why you want the compile time in there and not a timestamp of the last code change.  If you compile the exact same code a week apart, why does the timestamp need to change?  Wouldn't it be more useful if the second time you compiled that it was clear that the code is the same as the first time?  Otherwise the second time you compile, it'll look like it's a new binary, even though it's exactly the same code as the previous build, which could be confusing.

Ian Davis

unread,
May 10, 2014, 8:09:43 AM5/10/14
to golan...@googlegroups.com
 
On Sat, May 10, 2014, at 12:21 PM, Nate Finch wrote:
I'm curious why you want the compile time in there and not a timestamp of the last code change.  If you compile the exact same code a week apart, why does the timestamp need to change?  Wouldn't it be more useful if the second time you compiled that it was clear that the code is the same as the first time?  Otherwise the second time you compile, it'll look like it's a new binary, even though it's exactly the same code as the previous build, which could be confusing.
 
I think the usual reason is that dependencies may have been updated.
 
Ian

Hotei

unread,
May 10, 2014, 3:26:31 PM5/10/14
to golan...@googlegroups.com, Ian Dawes, David Rook, Marvin Renich
Nate,
You make a good point.  However, in my case I wouldn't normally recompile it unless I had changed the code or one of the supporting libs changed.  It's mostly an aid to help me figure out which git commit a given program binary corresponds to.  It's imprecise, but its also (usually) a transient requirement.  Once the code settles down it's not as relevant since then we're talking days or weeks between changes, not minutes.

Jason Del Ponte

unread,
May 11, 2014, 1:47:15 AM5/11/14
to golan...@googlegroups.com, Ian Dawes, David Rook, Marvin Renich
Have you considered using a build script that will generate a temporary non-committed go file containing the build time, or git commit? You could do cool things with this too. Such as labelling builds as beta/dev in the binary it self.

I've done this on other projects especially for build numbers so CS can provide better support, and the system worked pretty well.

Cheers,
Jason
Reply all
Reply to author
Forward
0 new messages