Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic version from git with go get

I'm using git tags to inject versions in my go programs, e.g. in a Makefile:

VERSION = $(shell git describe --always --dirty)

github_pki: main.go
    CGO_ENABLED=0 GOOS=linux \
      go build -a \
          -ldflags="-X main.version=$(VERSION)" \
        -installsuffix cgo -o $@ $<

Where version is defined in main.go as:

var version = "undefined"

This works great using make, but not when using go get or go build. Is there a way to make this ldflags work without using an external build system (i.e. with go build/go get)?

like image 486
raphink Avatar asked Jun 28 '16 22:06

raphink


1 Answers

From issue 22147 cmd/go: Make it easier to embed compiled git SHA into binary , you now have (with Go 1.12+, Q1 2019, 2+ years later) an alternative for the dependencies (not yet for your main program).

So you still need -ldflags to record the version of your sources.
But not to record the version of your dependencies: that is now available.

As of Go 1.12, you can get the versions of all modules compiled into the binary (in module mode) using runtime/debug.BuildInfo.

If the version in use is a pseudo-version, it also includes the first dozen or so characters of the VCS commit ID.

// BuildInfo represents the build information read from the running binary.

type BuildInfo struct {
    Path string    // The main package path
    Main Module    // The main module information
    Deps []*Module // Module dependencies
}

// ReadBuildInfo returns the build information embedded in the running binary. 
   The information is available only in binaries built with module support. 

func ReadBuildInfo() (info *BuildInfo, ok bool)

This comes from commit 45e9c55:

runtime/debug: add API to read module info in binary

When module is enabled, the go tool embeds build information related to the module in the binary including the dependencies and the replace information (See src/cmd/go/internal/modload#PackageBuildInfo(), from commit f7248f0).

The newly introduced ReadBuildInfo reads the information and makes it accessible programmatically.

You can see it used in issue 26404: "cmd/go: export module information for binaries programmatically"

package main

import (
    "log"
    "runtime/debug"

    "github.com/kr/pretty"
)

func main() {
    bi, ok := debug.ReadBuildInfo()
    if !ok {
        log.Fatal("!ok")
    }
    pretty.Println(bi)
}

You get:

&debug.BuildInfo{
    Path: "foo.to/v",
    Main: debug.Module{
        Path:    "foo.to/v",
        Version: "(devel)",
        Sum:     "",
        Replace: (*debug.Module)(nil),
    },
    Deps: {
        &debug.Module{
            Path:    "github.com/kr/pretty",
            Version: "v0.1.0",
            Sum:     "h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=",
            Replace: (*debug.Module)(nil),
        },
        &debug.Module{
            Path:    "github.com/kr/text",
            Version: "v0.1.0",
            Sum:     "h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=",
            Replace: (*debug.Module)(nil),
        },
    },
}

If you build the binary in module mode from outside of a module using 1.12 (#24250), then you'll pick up all of its version information.

but the reported version of the binary is always (devel) at the moment. Even if it is committed and tagged

So the main issue remains: issues/29814 "cmd/go: use version control to discover the main module's version? ".

When a binary is build from within a module's source tree, the output from runtime/debug.ReadBuildInfo currently reports that module as having version (devel).

If the source tree is a pristine checkout from a version-control system — or is within the (read-only) module cache — we could instead interrogate the version-control system to find the corresponding version or pseudo-version to embed.

However, that has a couple of caveats:

  1. It would require us to run VCS commands within the user's source tree. Historically, running VCS commands has been a source of entirely too many go command vulnerabilities, so we would really like to avoid issuing VCS commands except when they are absolutely necessary.
  2. Given a commit, we can produce a pseudo-version for that commit, but we can't tell whether that commit has been published to the origin.
    It would be nice to preserve the invariant that only published versions are advertised in debug info, but that may incur an extra network fetch.
  3. Within a module, we apply that module's replacements and exclusions, and the user's VCS checkout may also have applied some transformations.
    (devel) currently provides a clue that those module-specific changes are in effect: if we were to indicate an explicit version instead, we would need to provide some way to indicate that replacements and exclusions were applied.

Go 1.13 (Q3 2019) includes:

The go version command now accepts arguments naming executable and directories.

  • When invoked on an executable, go version prints the version of Go used to build the executable.
  • If the -m flag is used, go version prints the executable's embedded module version information, if available. (runtime/debug#BuildInfo)
  • When invoked on a directory, go version prints information about executable contained in the directory and its subdirectories.
like image 119
VonC Avatar answered Nov 16 '22 00:11

VonC