From 6f998dab6930314f56639b4c5035a4426eb57a77 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 23 Apr 2020 14:34:29 -0600 Subject: [PATCH] Improved cross-platform support --- builder.go | 59 +++++++++++++++++++++++++++++++++++------- platforms.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 platforms.go diff --git a/builder.go b/builder.go index a6c6304..ab63f16 100644 --- a/builder.go +++ b/builder.go @@ -34,6 +34,7 @@ import ( // Builder can produce a custom Caddy build with the // configuration it represents. type Builder struct { + Compile CaddyVersion string `json:"caddy_version,omitempty"` Plugins []Dependency `json:"plugins,omitempty"` Replacements []Replace `json:"replacements,omitempty"` @@ -54,21 +55,42 @@ func (b Builder) Build(ctx context.Context, outputFile string) error { return err } - env, err := b.newEnvironment(ctx) + // set some defaults from the environment, if applicable + if b.OS == "" { + b.OS = os.Getenv("GOOS") + } + if b.Arch == "" { + b.Arch = os.Getenv("GOARCH") + } + if b.ARM == "" { + b.ARM = os.Getenv("GOARM") + } + + // prepare the build environment + buildEnv, err := b.newEnvironment(ctx) if err != nil { return err } - defer env.Close() + defer buildEnv.Close() + + // prepare the environmen for the go command; for + // the most part we want it to inherit our current + // environment, with a few customizations + env := os.Environ() + env = setEnv(env, "GOOS="+b.OS) + env = setEnv(env, "GOARCH="+b.Arch) + env = setEnv(env, "GOARM="+b.ARM) + env = setEnv(env, fmt.Sprintf("CGO_ENABLED=%t", b.Cgo)) log.Println("[INFO] Building Caddy") - cmd := env.newCommand("go", "build", + // compile + cmd := buildEnv.newCommand("go", "build", "-o", absOutputFile, "-ldflags", "-w -s", // trim debug symbols "-trimpath", ) - cmd.Env = append(os.Environ(), "CGO_ENABLED=0") - err = env.runCommand(ctx, cmd, 5*time.Minute) + err = buildEnv.runCommand(ctx, cmd, 5*time.Minute) if err != nil { return err } @@ -78,24 +100,41 @@ func (b Builder) Build(ctx context.Context, outputFile string) error { return nil } +// setEnv sets an environment variable-value pair in +// env, overriding an existing variable if it already +// exists. The env slice is one such as is returned +// by os.Environ(), and set must also have the form +// of key=value. +func setEnv(env []string, set string) []string { + parts := strings.SplitN(set, "=", 2) + key := parts[0] + for i := 0; i < len(env); i++ { + if strings.HasPrefix(env[i], key+"=") { + env[i] = set + return env + } + } + return append(env, set) +} + // Dependency pairs a Go module path with a version. type Dependency struct { // The name (path) of the Go module. If at a version > 1, it // should contain semantic import version suffix (i.e. "/v2"). // Used with `go get` - ModulePath string + ModulePath string `json:"module_path,omitempty"` // The version of the Go module, like used with `go get`. - Version string + Version string `json:"version,omitempty"` } // Replace represents a Go module replacement. type Replace struct { // The import path of the module being replaced. - Old string + Old string `json:"old,omitempty"` // The path to the replacement module. - New string + New string `json:"new,omitempty"` } // newTempFolder creates a new folder in a temporary location. @@ -148,7 +187,7 @@ func versionedModulePath(modulePath, moduleVersion string) (string, error) { // only return the error if we know they were trying to use a semantic version // (could have been a commit SHA or something) if strings.HasPrefix(moduleVersion, "v") { - return "", err + return "", fmt.Errorf("%s: %v", moduleVersion, err) } return modulePath, nil } diff --git a/platforms.go b/platforms.go new file mode 100644 index 0000000..94b3227 --- /dev/null +++ b/platforms.go @@ -0,0 +1,73 @@ +package xcaddy + +import ( + "encoding/json" + "os/exec" +) + +// Compile contains parameters for compilation. +type Compile struct { + Platform + Cgo bool `json:"cgo,omitempty"` +} + +// Platform represents a build target. +type Platform struct { + OS string `json:"os,omitempty"` + Arch string `json:"arch,omitempty"` + ARM string `json:"arm,omitempty"` +} + +// SupportedPlatforms runs `go tool dist list` to make +// a list of possible build targets. +func SupportedPlatforms() ([]Compile, error) { + out, err := exec.Command("go", "tool", "dist", "list", "-json").Output() + if err != nil { + return nil, err + } + var dists []dist + err = json.Unmarshal(out, &dists) + if err != nil { + return nil, err + } + + // translate from the go command's output structure + // to our own user-facing structure + var compiles []Compile + for _, d := range dists { + comp := d.toCompile() + if d.GOARCH == "arm" { + if d.GOOS == "linux" { + // only linux supports ARMv5; see https://github.com/golang/go/issues/18418 + comp.ARM = "5" + compiles = append(compiles, comp) + } + comp.ARM = "6" + compiles = append(compiles, comp) + comp.ARM = "7" + compiles = append(compiles, comp) + } else { + compiles = append(compiles, comp) + } + } + + return compiles, nil +} + +// dist is the structure that fits the output +// of the `go tool dist list -json` command. +type dist struct { + GOOS string `json:"GOOS"` + GOARCH string `json:"GOARCH"` + CgoSupported bool `json:"CgoSupported"` +} + +func (d dist) toCompile() Compile { + return Compile{ + Platform: Platform{ + OS: d.GOOS, + Arch: d.GOARCH, + }, + Cgo: d.CgoSupported, + } +}