Skip to content

Commit

Permalink
feat(buildtool): fetch and use correct go version
Browse files Browse the repository at this point in the history
  • Loading branch information
bassosimone committed Feb 1, 2024
1 parent 9b59892 commit 2999e15
Show file tree
Hide file tree
Showing 22 changed files with 125 additions and 50 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/goversion.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Ensures that ./internal/cmd/buildtool works with different go versions

name: goversion
on:
push:
branches:
- "release/**"
- "fullbuild"
tags:
- "v*"
schedule:
- cron: "17 7 * * *"

jobs:
build_with_unexpected_go_version:
strategy:
matrix:
goversion: ["1.17", "1.18", "1.19", "1.21"]
system: [ubuntu-latest, windows-latest, macos-latest]
runs-on: "${{ matrix.system }}"
steps:
- uses: actions/checkout@v3

- uses: actions/setup-go@v4
with:
go-version: "${{ matrix.goversion }}"

- run: go run ./internal/cmd/buildtool generic miniooni
4 changes: 1 addition & 3 deletions internal/cmd/buildtool/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ func androidSubcommand() *cobra.Command {
// androidBuildGomobile invokes the gomobile build.
func androidBuildGomobile(deps buildtoolmodel.Dependencies) {
deps.PsiphonMaybeCopyConfigFiles()
deps.GolangCheck()

androidHome := deps.AndroidSDKCheck()
ndkDir := deps.AndroidNDKCheck(androidHome)
Expand Down Expand Up @@ -146,7 +145,6 @@ func androidNDKCheck(androidHome string) string {
// androidBuildCLIAll builds all products in CLI mode for Android
func androidBuildCLIAll(deps buildtoolmodel.Dependencies) {
deps.PsiphonMaybeCopyConfigFiles()
deps.GolangCheck()

androidHome := deps.AndroidSDKCheck()
ndkDir := deps.AndroidNDKCheck(androidHome)
Expand Down Expand Up @@ -177,7 +175,7 @@ func androidBuildCLIProductArch(

log.Infof("building %s for android/%s", product.Pkg, ooniArch)

argv := runtimex.Try1(shellx.NewArgv("go", "build"))
argv := runtimex.Try1(shellx.NewArgv(deps.GolangBinary(), "build"))
if deps.PsiphonFilesExist() {
argv.Append("-tags", "ooni_psiphon_config,ooni_libtor")
} else {
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/buildtool/android_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func TestAndroidBuildGomobile(t *testing.T) {
buildtooltest.TagGOPATH: 2,
buildtooltest.TagAndroidNDKCheck: 1,
buildtooltest.TagAndroidSDKCheck: 1,
buildtooltest.TagGolangCheck: 1,
buildtooltest.TagGolangBinary: 1,
buildtooltest.TagPsiphonMaybeCopyConfigFiles: 1,
buildtooltest.TagPsiphonFilesExist: 1,
}
Expand Down Expand Up @@ -406,7 +406,7 @@ func TestAndroidBuildCLIAll(t *testing.T) {
expectCalls := map[string]int{
buildtooltest.TagAndroidNDKCheck: 1,
buildtooltest.TagAndroidSDKCheck: 1,
buildtooltest.TagGolangCheck: 1,
buildtooltest.TagGolangBinary: 1,
buildtooltest.TagPsiphonMaybeCopyConfigFiles: 1,
buildtooltest.TagPsiphonFilesExist: 8,
}
Expand Down
6 changes: 3 additions & 3 deletions internal/cmd/buildtool/builddeps.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ func (*buildDeps) GOPATH() string {
return golangGOPATH()
}

// GolangCheck implements buildtoolmodel.Dependencies
func (*buildDeps) GolangCheck() {
golangCheck("GOVERSION")
// GolangBinary implements buildtoolmodel.Dependencies
func (*buildDeps) GolangBinary() string {
return golangBinary()
}

// LinuxReadGOVERSION implements buildtoolmodel.Dependencies
Expand Down
3 changes: 1 addition & 2 deletions internal/cmd/buildtool/darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ func darwinSubcommand() *cobra.Command {
// darwinBuildAll builds all the packages for darwin.
func darwinBuildAll(deps buildtoolmodel.Dependencies) {
deps.PsiphonMaybeCopyConfigFiles()
deps.GolangCheck()
archs := []string{"amd64", "arm64"}
products := []*product{productMiniooni, productOoniprobe}
for _, arch := range archs {
Expand All @@ -41,7 +40,7 @@ func darwinBuildAll(deps buildtoolmodel.Dependencies) {
func darwinBuildPackage(deps buildtoolmodel.Dependencies, goarch string, product *product) {
log.Infof("building %s for darwin/%s", product.Pkg, goarch)

argv := runtimex.Try1(shellx.NewArgv("go", "build"))
argv := runtimex.Try1(shellx.NewArgv(deps.GolangBinary(), "build"))
if deps.PsiphonFilesExist() {
argv.Append("-tags", "ooni_psiphon_config")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/buildtool/darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func TestDarwinBuildAll(t *testing.T) {
})

expectCalls := map[string]int{
buildtooltest.TagGolangCheck: 1,
buildtooltest.TagGolangBinary: 1,
buildtooltest.TagPsiphonMaybeCopyConfigFiles: 1,
buildtooltest.TagPsiphonFilesExist: 4,
}
Expand Down
8 changes: 4 additions & 4 deletions internal/cmd/buildtool/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ func genericSubcommand() *cobra.Command {
return cmd
}

// TODO(bassosimone): golangBinary() MUST be a method of the buildtoolmodel.Dependencies

// genericBuildPackage is the generic function for building a package.
func genericBuildPackage(deps buildtoolmodel.Dependencies, product *product) {
deps.PsiphonMaybeCopyConfigFiles()
deps.GolangCheck()

log.Infof("building %s for %s/%s", product.Pkg, runtime.GOOS, runtime.GOARCH)

argv := runtimex.Try1(shellx.NewArgv("go", "build"))
argv := runtimex.Try1(shellx.NewArgv(golangBinary(), "build"))
if deps.PsiphonFilesExist() {
argv.Append("-tags", "ooni_psiphon_config")
}
Expand All @@ -67,7 +68,6 @@ func genericBuildPackage(deps buildtoolmodel.Dependencies, product *product) {

// genericBuildLibrary is the generic function for building a library.
func genericBuildLibrary(deps buildtoolmodel.Dependencies, product *product) {
deps.GolangCheck()
os := deps.GOOS()

log.Infof("building %s for %s/%s", product.Pkg, os, runtime.GOARCH)
Expand All @@ -76,7 +76,7 @@ func genericBuildLibrary(deps buildtoolmodel.Dependencies, product *product) {
// packages paths are separated by forward slashes!
library := runtimex.Try1(generateLibrary(path.Base(product.Pkg), os))

argv := runtimex.Try1(shellx.NewArgv("go", "build"))
argv := runtimex.Try1(shellx.NewArgv(golangBinary(), "build"))
argv.Append("-buildmode", "c-shared")
argv.Append("-o", library)
argv.Append(product.Pkg)
Expand Down
6 changes: 3 additions & 3 deletions internal/cmd/buildtool/generic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func TestGenericBuildPackage(t *testing.T) {
})

expectCalls := map[string]int{
buildtooltest.TagGolangCheck: 1,
buildtooltest.TagGolangBinary: 1,
buildtooltest.TagPsiphonMaybeCopyConfigFiles: 1,
buildtooltest.TagPsiphonFilesExist: 1,
}
Expand Down Expand Up @@ -164,8 +164,8 @@ func TestCheckGenericBuildLibrary(t *testing.T) {
})

expectCalls := map[string]int{
buildtooltest.TagGolangCheck: 1,
buildtooltest.TagGOOS: 1,
buildtooltest.TagGolangBinary: 1,
buildtooltest.TagGOOS: 1,
}

if diff := cmp.Diff(expectCalls, deps.Counter); diff != "" {
Expand Down
60 changes: 55 additions & 5 deletions internal/cmd/buildtool/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,73 @@ package main
//

import (
"fmt"
"path/filepath"
"strings"
"sync"

"github.com/apex/log"
"github.com/ooni/probe-cli/v3/internal/must"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)

// golangCheck checks whether the "go" binary is the correct version
func golangCheck(filename string) {
// golangCheckCorrectVersion returns true if the version of Go is correct.
func golangCheckCorrectVersion(filename string) bool {
// read the version of go that we would like to use
expected := string(must.FirstLineBytes(must.ReadFile(filename)))

// read the version of go that we're using
firstline := string(must.FirstLineBytes(must.RunOutput(log.Log, "go", "version")))
vec := strings.Split(firstline, " ")
runtimex.Assert(len(vec) == 4, "expected four tokens")
if got := vec[2]; got != "go"+expected {
log.Fatalf("expected go%s but got %s", expected, got)

// make sure they're equal
return vec[2] == "go"+expected
}

// golangInstall installs and returns the path to the correct version of Go.
func golangInstall(filename string) string {
// read the version of Go we would like to use
expected := string(must.FirstLineBytes(must.ReadFile(filename)))

// install the downloaded script
packageName := fmt.Sprintf("golang.org/dl/go%s@latest", expected)
must.Run(log.Log, "go", "install", "-v", packageName)

// run the downloader script
gobinary := filepath.Join(
string(must.FirstLineBytes(must.RunOutput(log.Log, "go", "env", "GOPATH"))),
"bin",
fmt.Sprintf("go%s", expected),
)
must.Run(log.Log, gobinary, "download")

// if all is good, then we have the right gobinary
return gobinary
}

// golangBinaryWithoutCache returns the path to the correct golang binary to use.
func golangBinaryWithoutCache() string {
if !golangCheckCorrectVersion("GOVERSION") {
return golangInstall("GOVERSION")
}
return "go"
}

// golangCachedBinary is the cached golang binary.
var golangCachedBinary string

// golangCacheMu synchronizes accesses to [golangCachedBinary].
var golangCacheMu sync.Mutex

// golangBinary returns the path to the correct golang binary to use.
func golangBinary() string {
defer golangCacheMu.Unlock()
golangCacheMu.Lock()
if golangCachedBinary == "" {
golangCachedBinary = golangBinaryWithoutCache()
}
log.Infof("using go%s", expected)
return golangCachedBinary
}

// golangGOPATH returns the GOPATH value.
Expand Down
10 changes: 6 additions & 4 deletions internal/cmd/buildtool/golang_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package main

import (
"path/filepath"
"testing"

"github.com/ooni/probe-cli/v3/internal/must"
)

func TestGolangCheck(t *testing.T) {
// make sure the code does not panic when it runs
golangCheck(filepath.Join("..", "..", "..", "GOVERSION"))
func TestGolangBinary(t *testing.T) {
// make sure the code does not panic when it runs and returns a valid binary
value := golangBinary()
must.RunQuiet(value, "version")
}
6 changes: 3 additions & 3 deletions internal/cmd/buildtool/gomobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ type gomobileConfig struct {
// gomobileBuild runs a build based on gomobile.
func gomobileBuild(config *gomobileConfig) {
// Undoes the effects of go-getting golang.org/x/mobile/cmd/gomobile
defer must.Run(log.Log, "go", "mod", "tidy")
defer must.Run(log.Log, config.deps.GolangBinary(), "mod", "tidy")

must.Run(log.Log, "go", "install", "golang.org/x/mobile/cmd/gomobile@latest")
must.Run(log.Log, config.deps.GolangBinary(), "install", "golang.org/x/mobile/cmd/gomobile@latest")

gopath := config.deps.GOPATH()
gomobile := filepath.Join(gopath, "bin", "gomobile")
must.Run(log.Log, gomobile, "init")

// Adding gomobile to go.mod as documented by golang.org/wiki/Mobile
must.Run(log.Log, "go", "get", "-d", "golang.org/x/mobile/cmd/gomobile")
must.Run(log.Log, config.deps.GolangBinary(), "get", "-d", "golang.org/x/mobile/cmd/gomobile")

argv := runtimex.Try1(shellx.NewArgv(gomobile, "bind"))
argv.Append("-target", config.target)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ type Dependencies interface {
// GOPATH returns the current GOPATH.
GOPATH() string

// GolangCheck ensures we have the correct
// version of golang as the "go" binary.
GolangCheck()
// GolangBinary returns the golang binary to use.
GolangBinary() string

// LinuxWriteDockerfile writes the dockerfile for linux.
LinuxWriteDockerfile(filename string, content []byte, mode fs.FileMode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const (
TagAndroidNDKCheck = "androidNDK"
TagAndroidSDKCheck = "androidSDK"
TagGOPATH = "GOPATH"
TagGolangCheck = "golangCheck"
TagGolangBinary = "golangBinary"
TagLinuxReadGOVERSION = "linuxReadGOVERSION"
TagLinuxWriteDockerfile = "linuxWriteDockerfile"
TagMustChdir = "mustChdir"
Expand Down Expand Up @@ -188,8 +188,9 @@ func (cc *DependenciesCallCounter) GOPATH() string {
}

// golangCheck implements buildtoolmodel.Dependencies
func (cc *DependenciesCallCounter) GolangCheck() {
cc.increment(TagGolangCheck)
func (cc *DependenciesCallCounter) GolangBinary() string {
cc.increment(TagGolangBinary)
return "go"
}

// linuxReadGOVERSION implements buildtoolmodel.Dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,13 @@ func TestDependenciesCallCounter(t *testing.T) {

t.Run("GolangCheck", func(t *testing.T) {
cc := &DependenciesCallCounter{}
cc.GolangCheck()
if cc.Counter[TagGolangCheck] != 1 {
value := cc.GolangBinary()
if cc.Counter[TagGolangBinary] != 1 {
t.Fatal("did not increment")
}
if value != "go" {
t.Fatal("expected 'go', got", value)
}
})

t.Run("LinuxReadGOVERSION", func(t *testing.T) {
Expand Down
1 change: 0 additions & 1 deletion internal/cmd/buildtool/ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func iosSubcommand() *cobra.Command {
// iosBuildGomobile invokes the gomobile build.
func iosBuildGomobile(deps buildtoolmodel.Dependencies) {
deps.PsiphonMaybeCopyConfigFiles()
deps.GolangCheck()

config := &gomobileConfig{
deps: deps,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/buildtool/ios_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func TestIOSBuildGomobile(t *testing.T) {

expectCalls := map[string]int{
buildtooltest.TagGOPATH: 1,
buildtooltest.TagGolangCheck: 1,
buildtooltest.TagGolangBinary: 1,
buildtooltest.TagPsiphonMaybeCopyConfigFiles: 1,
buildtooltest.TagPsiphonFilesExist: 1,
}
Expand Down
3 changes: 1 addition & 2 deletions internal/cmd/buildtool/linuxstatic.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ func (b *linuxStaticBuilder) main(*cobra.Command, []string) {
// linuxStaticBuildAll builds all the packages on a linux-static environment.
func linuxStaticBuilAll(deps buildtoolmodel.Dependencies, goarch string, goarm int64) {
deps.PsiphonMaybeCopyConfigFiles()
deps.GolangCheck()

// TODO(bassosimone): I am running the container with the right userID but
// apparently this is not enough to make git happy--why?
Expand All @@ -73,7 +72,7 @@ func linuxStaticBuildPackage(

ooniArch := linuxStaticBuildOONIArch(goarch, goarm)

argv := runtimex.Try1(shellx.NewArgv("go", "build"))
argv := runtimex.Try1(shellx.NewArgv(deps.GolangBinary(), "build"))
if deps.PsiphonFilesExist() {
argv.Append("-tags", "ooni_psiphon_config")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/buildtool/linuxstatic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func TestLinuxStaticBuildAll(t *testing.T) {
})

expectCalls := map[string]int{
buildtooltest.TagGolangCheck: 1,
buildtooltest.TagGolangBinary: 1,
buildtooltest.TagPsiphonMaybeCopyConfigFiles: 1,
buildtooltest.TagPsiphonFilesExist: 2,
}
Expand Down
4 changes: 1 addition & 3 deletions internal/cmd/buildtool/oohelperd.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ func oohelperdSubcommand() *cobra.Command {
// oohelperdBuildAndMaybeDeploy builds oohelperd for linux/amd64 and
// possibly deploys the build to the 0.th.ooni.org server.
func oohelperdBuildAndMaybeDeploy(deps buildtoolmodel.Dependencies, deploy bool) {
deps.GolangCheck()

log.Info("building oohelperd for linux/amd64")
oohelperdBinary := filepath.Join("CLI", "oohelperd-linux-amd64")

Expand All @@ -52,7 +50,7 @@ func oohelperdBuildAndMaybeDeploy(deps buildtoolmodel.Dependencies, deploy bool)
envp.Append("GOOS", "linux")
envp.Append("GOARCH", "amd64")

argv := runtimex.Try1(shellx.NewArgv("go", "build"))
argv := runtimex.Try1(shellx.NewArgv(deps.GolangBinary(), "build"))
argv.Append("-o", oohelperdBinary)
argv.Append("-tags", "netgo")
argv.Append("-ldflags", "-s -w -extldflags -static")
Expand Down
Loading

0 comments on commit 2999e15

Please sign in to comment.