tailscale/tool/gocross/gocross.go
David Anderson 7e65a11df5 tool/gocross: write the wrapper script directly, rather than printing
Turns out directing the printed script into the bootstrap location leads
to irritating "text file busy" problems and then having to muck about with
tempfiles and chmod and all that. Instead, have gocross write everything
with the right values.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-23 02:03:14 +00:00

148 lines
3.8 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// gocross is a wrapper around the `go` tool that invokes `go` from Tailscale's
// custom toolchain, with the right build parameters injected based on the
// native+target GOOS/GOARCH.
//
// In short, when aliased to `go`, using `go build`, `go test` behave like the
// upstream Go tools, but produce correctly configured, correctly linked
// binaries stamped with version information.
package main
import (
_ "embed"
"fmt"
"os"
"path/filepath"
runtimeDebug "runtime/debug"
"tailscale.com/atomicfile"
)
func main() {
if len(os.Args) > 1 {
// These additional subcommands are various support commands to handle
// integration with Tailscale's existing build system. Unless otherwise
// specified, these are not stable APIs, and may change or go away at
// any time.
switch os.Args[1] {
case "gocross-version":
hash, err := embeddedCommit()
if err != nil {
fmt.Fprintf(os.Stderr, "getting commit hash: %v", err)
os.Exit(1)
}
fmt.Println(hash)
os.Exit(0)
case "is-gocross":
// This subcommand exits with an error code when called on a
// regular go binary, so it can be used to detect when `go` is
// actually gocross.
os.Exit(0)
case "make-goroot":
_, gorootDir, err := getToolchain()
if err != nil {
fmt.Fprintf(os.Stderr, "getting toolchain: %v\n", err)
os.Exit(1)
}
fmt.Println(gorootDir)
os.Exit(0)
case "gocross-get-toolchain-go":
toolchain, _, err := getToolchain()
if err != nil {
fmt.Fprintf(os.Stderr, "getting toolchain: %v\n", err)
os.Exit(1)
}
fmt.Println(filepath.Join(toolchain, "bin/go"))
os.Exit(0)
case "gocross-write-wrapper-script":
if len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "usage: gocross write-wrapper-script <path>\n")
os.Exit(1)
}
if err := atomicfile.WriteFile(os.Args[2], wrapperScript, 0755); err != nil {
fmt.Fprintf(os.Stderr, "writing wrapper script: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}
}
toolchain, goroot, err := getToolchain()
if err != nil {
fmt.Fprintf(os.Stderr, "getting toolchain: %v\n", err)
os.Exit(1)
}
args := os.Args
if os.Getenv("GOCROSS_BYPASS") == "" {
newArgv, env, err := Autoflags(os.Args, goroot)
if err != nil {
fmt.Fprintf(os.Stderr, "computing flags: %v\n", err)
os.Exit(1)
}
// Make sure the right version of cmd/go is the first thing in the PATH
// for tests that execute `go build` or `go test`.
// TODO: if we really need to do this, do it inside Autoflags, not here.
path := filepath.Join(toolchain, "bin") + string(os.PathListSeparator) + os.Getenv("PATH")
env.Set("PATH", path)
debug("Input: %s\n", formatArgv(os.Args))
debug("Command: %s\n", formatArgv(newArgv))
debug("Set the following flags/envvars:\n%s\n", env.Diff())
args = newArgv
if err := env.Apply(); err != nil {
fmt.Fprintf(os.Stderr, "modifying environment: %v\n", err)
os.Exit(1)
}
}
doExec(filepath.Join(toolchain, "bin/go"), args, os.Environ())
}
//go:embed gocross-wrapper.sh
var wrapperScript []byte
func debug(format string, args ...interface{}) {
debug := os.Getenv("GOCROSS_DEBUG")
var (
out *os.File
err error
)
switch debug {
case "0", "":
return
case "1":
out = os.Stderr
default:
out, err = os.OpenFile(debug, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0640)
if err != nil {
fmt.Fprintf(os.Stderr, "opening debug file %q: %v", debug, err)
out = os.Stderr
} else {
defer out.Close() // May lose some write errors, but we don't care.
}
}
fmt.Fprintf(out, format, args...)
}
func embeddedCommit() (string, error) {
bi, ok := runtimeDebug.ReadBuildInfo()
if !ok {
return "", fmt.Errorf("no build info")
}
for _, s := range bi.Settings {
if s.Key == "vcs.revision" {
return s.Value, nil
}
}
return "", fmt.Errorf("no git commit found")
}