<commit_before>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// +build ignore

package main

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"text/template"
	"time"
)

var (
	versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
	goarch    string
	goos      string
	noupgrade bool
	version   string
	goVersion float64
	race      bool
)

type target struct {
	name         string
	buildPkg     string
	binaryName   string
	archiveFiles []archiveFile
	debianFiles  []archiveFile
	tags         []string
}

type archiveFile struct {
	src  string
	dst  string
	perm os.FileMode
}

var targets = map[string]target{
	"all": {
		// Only valid for the "build" and "install" commands as it lacks all
		// the archive creation stuff.
		buildPkg: "./cmd/...",
		tags:     []string{"purego"},
	},
	"syncthing": {
		// The default target for "build", "install", "tar", "zip", "deb", etc.
		name:       "syncthing",
		buildPkg:   "./cmd/syncthing",
		binaryName: "syncthing", // .exe will be added automatically for Windows builds
		archiveFiles: []archiveFile{
			{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
			{src: "README.md", dst: "README.txt", perm: 0644},
			{src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
			// All files from etc/ and extra/ added automatically in init().
		},
		debianFiles: []archiveFile{
			{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
			{src: "README.md", dst: "deb/usr/share/doc/syncthing/README.txt", perm: 0644},
			{src: "LICENSE", dst: "deb/usr/share/doc/syncthing/LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing/AUTHORS.txt", perm: 0644},
			{src: "man/syncthing.1", dst: "deb/usr/share/man/man1/syncthing.1", perm: 0644},
			{src: "man/syncthing-config.5", dst: "deb/usr/share/man/man5/syncthing-config.5", perm: 0644},
			{src: "man/syncthing-stignore.5", dst: "deb/usr/share/man/man5/syncthing-stignore.5", perm: 0644},
			{src: "man/syncthing-device-ids.7", dst: "deb/usr/share/man/man7/syncthing-device-ids.7", perm: 0644},
			{src: "man/syncthing-event-api.7", dst: "deb/usr/share/man/man7/syncthing-event-api.7", perm: 0644},
			{src: "man/syncthing-faq.7", dst: "deb/usr/share/man/man7/syncthing-faq.7", perm: 0644},
			{src: "man/syncthing-networking.7", dst: "deb/usr/share/man/man7/syncthing-networking.7", perm: 0644},
			{src: "man/syncthing-rest-api.7", dst: "deb/usr/share/man/man7/syncthing-rest-api.7", perm: 0644},
			{src: "man/syncthing-security.7", dst: "deb/usr/share/man/man7/syncthing-security.7", perm: 0644},
			{src: "man/syncthing-versioning.7", dst: "deb/usr/share/man/man7/syncthing-versioning.7", perm: 0644},
			{src: "etc/linux-systemd/system/syncthing@.service", dst: "deb/lib/systemd/system/syncthing@.service", perm: 0644},
			{src: "etc/linux-systemd/system/syncthing-resume.service", dst: "deb/lib/systemd/system/syncthing-resume.service", perm: 0644},
			{src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
		},
	},
	"discosrv": {
		name:       "discosrv",
		buildPkg:   "./cmd/discosrv",
		binaryName: "discosrv", // .exe will be added automatically for Windows builds
		archiveFiles: []archiveFile{
			{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
			{src: "cmd/discosrv/README.md", dst: "README.txt", perm: 0644},
			{src: "cmd/discosrv/LICENSE", dst: "LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
		},
		debianFiles: []archiveFile{
			{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
			{src: "cmd/discosrv/README.md", dst: "deb/usr/share/doc/discosrv/README.txt", perm: 0644},
			{src: "cmd/discosrv/LICENSE", dst: "deb/usr/share/doc/discosrv/LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "deb/usr/share/doc/discosrv/AUTHORS.txt", perm: 0644},
		},
		tags: []string{"purego"},
	},
	"relaysrv": {
		name:       "relaysrv",
		buildPkg:   "./cmd/relaysrv",
		binaryName: "relaysrv", // .exe will be added automatically for Windows builds
		archiveFiles: []archiveFile{
			{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
			{src: "cmd/relaysrv/README.md", dst: "README.txt", perm: 0644},
			{src: "cmd/relaysrv/LICENSE", dst: "LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
		},
		debianFiles: []archiveFile{
			{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
			{src: "cmd/relaysrv/README.md", dst: "deb/usr/share/doc/relaysrv/README.txt", perm: 0644},
			{src: "cmd/relaysrv/LICENSE", dst: "deb/usr/share/doc/relaysrv/LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "deb/usr/share/doc/relaysrv/AUTHORS.txt", perm: 0644},
		},
	},
}

func init() {
	// The "syncthing" target includes a few more files found in the "etc"
	// and "extra" dirs.
	syncthingPkg := targets["syncthing"]
	for _, file := range listFiles("etc") {
		syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
	}
	for _, file := range listFiles("extra") {
		syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
	}
	for _, file := range listFiles("extra") {
		syncthingPkg.debianFiles = append(syncthingPkg.debianFiles, archiveFile{src: file, dst: "deb/usr/share/doc/syncthing/" + filepath.Base(file), perm: 0644})
	}
	targets["syncthing"] = syncthingPkg
}

const minGoVersion = 1.3

func main() {
	log.SetOutput(os.Stdout)
	log.SetFlags(0)

	if os.Getenv("GOPATH") == "" {
		setGoPath()
	}

	// We use Go 1.5+ vendoring.
	os.Setenv("GO15VENDOREXPERIMENT", "1")

	// Set path to $GOPATH/bin:$PATH so that we can for sure find tools we
	// might have installed during "build.go setup".
	os.Setenv("PATH", fmt.Sprintf("%s%cbin%c%s", os.Getenv("GOPATH"), os.PathSeparator, os.PathListSeparator, os.Getenv("PATH")))

	parseFlags()

	switch goarch {
	case "386", "amd64", "arm", "arm64", "ppc64", "ppc64le":
		break
	default:
		log.Printf("Unknown goarch %q; proceed with caution!", goarch)
	}

	goVersion, _ = checkRequiredGoVersion()

	// Invoking build.go with no parameters at all is equivalent to "go run
	// build.go install all" as that builds everything (incrementally),
	// which is what you want for maximum error checking during development.
	if flag.NArg() == 0 {
		var tags []string
		if noupgrade {
			tags = []string{"noupgrade"}
		}
		install(targets["all"], tags)

		vet("cmd", "lib")
		lint("./cmd/...")
		lint("./lib/...")
		return
	}

	// Otherwise, with any command given but not a target, the target is
	// "syncthing". So "go run build.go install" is "go run build.go install
	// syncthing" etc.
	targetName := "syncthing"
	if flag.NArg() > 1 {
		targetName = flag.Arg(1)
	}
	target, ok := targets[targetName]
	if !ok {
		log.Fatalln("Unknown target", target)
	}

	cmd := flag.Arg(0)
	switch cmd {
	case "setup":
		setup()

	case "install":
		var tags []string
		if noupgrade {
			tags = []string{"noupgrade"}
		}
		install(target, tags)

	case "build":
		var tags []string
		if noupgrade {
			tags = []string{"noupgrade"}
		}
		build(target, tags)

	case "test":
		test("./lib/...", "./cmd/...")

	case "bench":
		bench("./lib/...", "./cmd/...")

	case "assets":
		rebuildAssets()

	case "xdr":
		xdr()

	case "translate":
		translate()

	case "transifex":
		transifex()

	case "tar":
		buildTar(target)

	case "zip":
		buildZip(target)

	case "deb":
		buildDeb(target)

	case "clean":
		clean()

	case "vet":
		vet("build.go")
		vet("cmd", "lib")

	case "lint":
		lint(".")
		lint("./cmd/...")
		lint("./lib/...")
		if isGometalinterInstalled() {
			dirs := []string{".", "./cmd/...", "./lib/..."}
			gometalinter("deadcode", dirs, "test/util.go")
			gometalinter("structcheck", dirs)
			gometalinter("varcheck", dirs)
		}

	default:
		log.Fatalf("Unknown command %q", cmd)
	}
}

// setGoPath sets GOPATH correctly with the assumption that we are
// in $GOPATH/src/github.com/syncthing/syncthing.
func setGoPath() {
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
	log.Println("GOPATH is", gopath)
	os.Setenv("GOPATH", gopath)
}

func parseFlags() {
	flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
	flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
	flag.BoolVar(&noupgrade, "no-upgrade", noupgrade, "Disable upgrade functionality")
	flag.StringVar(&version, "version", getVersion(), "Set compiled in version string")
	flag.BoolVar(&race, "race", race, "Use race detector")
	flag.Parse()
}

func checkRequiredGoVersion() (float64, bool) {
	re := regexp.MustCompile(`go(\d+\.\d+)`)
	ver := runtime.Version()
	if m := re.FindStringSubmatch(ver); len(m) == 2 {
		vs := string(m[1])
		// This is a standard go build. Verify that it's new enough.
		f, err := strconv.ParseFloat(vs, 64)
		if err != nil {
			log.Printf("*** Couldn't parse Go version out of %q.\n*** This isn't known to work, proceed on your own risk.", vs)
			return 0, false
		}
		if f < 1.5 {
			log.Printf("*** Go version %.01f doesn't support the vendoring mechanism.\n*** Ensure correct dependencies in your $GOPATH.", f)
		} else if f < minGoVersion {
			log.Fatalf("*** Go version %.01f is less than required %.01f.\n*** This is known not to work, not proceeding.", f, minGoVersion)
		}
		return f, true
	}

	log.Printf("*** Unknown Go version %q.\n*** This isn't known to work, proceed on your own risk.", ver)
	return 0, false
}

func setup() {
	runPrint("go", "get", "-v", "golang.org/x/tools/cmd/cover")
	runPrint("go", "get", "-v", "golang.org/x/net/html")
	runPrint("go", "get", "-v", "github.com/FiloSottile/gvt")
	runPrint("go", "get", "-v", "github.com/axw/gocov/gocov")
	runPrint("go", "get", "-v", "github.com/AlekSi/gocov-xml")
	runPrint("go", "get", "-v", "bitbucket.org/tebeka/go2xunit")
	runPrint("go", "get", "-v", "github.com/alecthomas/gometalinter")
	runPrint("go", "get", "-v", "github.com/mitchellh/go-wordwrap")
}

func test(pkgs ...string) {
	lazyRebuildAssets()

	useRace := runtime.GOARCH == "amd64"
	switch runtime.GOOS {
	case "darwin", "linux", "freebsd", "windows":
	default:
		useRace = false
	}

	if useRace {
		runPrint("go", append([]string{"test", "-short", "-race", "-timeout", "60s", "-tags", "purego"}, pkgs...)...)
	} else {
		runPrint("go", append([]string{"test", "-short", "-timeout", "60s", "-tags", "purego"}, pkgs...)...)
	}
}

func bench(pkgs ...string) {
	lazyRebuildAssets()
	runPrint("go", append([]string{"test", "-run", "NONE", "-bench", "."}, pkgs...)...)
}

func install(target target, tags []string) {
	lazyRebuildAssets()

	tags = append(target.tags, tags...)

	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
	args := []string{"install", "-v", "-ldflags", ldflags()}
	if len(tags) > 0 {
		args = append(args, "-tags", strings.Join(tags, ","))
	}
	if race {
		args = append(args, "-race")
	}
	args = append(args, target.buildPkg)

	os.Setenv("GOOS", goos)
	os.Setenv("GOARCH", goarch)
	runPrint("go", args...)
}

func build(target target, tags []string) {
	lazyRebuildAssets()

	tags = append(target.tags, tags...)

	rmr(target.binaryName)
	args := []string{"build", "-i", "-v", "-ldflags", ldflags()}
	if len(tags) > 0 {
		args = append(args, "-tags", strings.Join(tags, ","))
	}
	if race {
		args = append(args, "-race")
	}
	args = append(args, target.buildPkg)

	os.Setenv("GOOS", goos)
	os.Setenv("GOARCH", goarch)
	runPrint("go", args...)
}

func buildTar(target target) {
	name := archiveName(target)
	filename := name + ".tar.gz"

	var tags []string
	if noupgrade {
		tags = []string{"noupgrade"}
		name += "-noupgrade"
	}

	build(target, tags)

	if goos == "darwin" {
		macosCodesign(target.binaryName)
	}<commit_msg>build: Tags must be joined by space, not comma (fixes #3262)<commit_after>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// +build ignore

package main

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"text/template"
	"time"
)

var (
	versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
	goarch    string
	goos      string
	noupgrade bool
	version   string
	goVersion float64
	race      bool
)

type target struct {
	name         string
	buildPkg     string
	binaryName   string
	archiveFiles []archiveFile
	debianFiles  []archiveFile
	tags         []string
}

type archiveFile struct {
	src  string
	dst  string
	perm os.FileMode
}

var targets = map[string]target{
	"all": {
		// Only valid for the "build" and "install" commands as it lacks all
		// the archive creation stuff.
		buildPkg: "./cmd/...",
		tags:     []string{"purego"},
	},
	"syncthing": {
		// The default target for "build", "install", "tar", "zip", "deb", etc.
		name:       "syncthing",
		buildPkg:   "./cmd/syncthing",
		binaryName: "syncthing", // .exe will be added automatically for Windows builds
		archiveFiles: []archiveFile{
			{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
			{src: "README.md", dst: "README.txt", perm: 0644},
			{src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
			// All files from etc/ and extra/ added automatically in init().
		},
		debianFiles: []archiveFile{
			{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
			{src: "README.md", dst: "deb/usr/share/doc/syncthing/README.txt", perm: 0644},
			{src: "LICENSE", dst: "deb/usr/share/doc/syncthing/LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing/AUTHORS.txt", perm: 0644},
			{src: "man/syncthing.1", dst: "deb/usr/share/man/man1/syncthing.1", perm: 0644},
			{src: "man/syncthing-config.5", dst: "deb/usr/share/man/man5/syncthing-config.5", perm: 0644},
			{src: "man/syncthing-stignore.5", dst: "deb/usr/share/man/man5/syncthing-stignore.5", perm: 0644},
			{src: "man/syncthing-device-ids.7", dst: "deb/usr/share/man/man7/syncthing-device-ids.7", perm: 0644},
			{src: "man/syncthing-event-api.7", dst: "deb/usr/share/man/man7/syncthing-event-api.7", perm: 0644},
			{src: "man/syncthing-faq.7", dst: "deb/usr/share/man/man7/syncthing-faq.7", perm: 0644},
			{src: "man/syncthing-networking.7", dst: "deb/usr/share/man/man7/syncthing-networking.7", perm: 0644},
			{src: "man/syncthing-rest-api.7", dst: "deb/usr/share/man/man7/syncthing-rest-api.7", perm: 0644},
			{src: "man/syncthing-security.7", dst: "deb/usr/share/man/man7/syncthing-security.7", perm: 0644},
			{src: "man/syncthing-versioning.7", dst: "deb/usr/share/man/man7/syncthing-versioning.7", perm: 0644},
			{src: "etc/linux-systemd/system/syncthing@.service", dst: "deb/lib/systemd/system/syncthing@.service", perm: 0644},
			{src: "etc/linux-systemd/system/syncthing-resume.service", dst: "deb/lib/systemd/system/syncthing-resume.service", perm: 0644},
			{src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
		},
	},
	"discosrv": {
		name:       "discosrv",
		buildPkg:   "./cmd/discosrv",
		binaryName: "discosrv", // .exe will be added automatically for Windows builds
		archiveFiles: []archiveFile{
			{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
			{src: "cmd/discosrv/README.md", dst: "README.txt", perm: 0644},
			{src: "cmd/discosrv/LICENSE", dst: "LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
		},
		debianFiles: []archiveFile{
			{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
			{src: "cmd/discosrv/README.md", dst: "deb/usr/share/doc/discosrv/README.txt", perm: 0644},
			{src: "cmd/discosrv/LICENSE", dst: "deb/usr/share/doc/discosrv/LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "deb/usr/share/doc/discosrv/AUTHORS.txt", perm: 0644},
		},
		tags: []string{"purego"},
	},
	"relaysrv": {
		name:       "relaysrv",
		buildPkg:   "./cmd/relaysrv",
		binaryName: "relaysrv", // .exe will be added automatically for Windows builds
		archiveFiles: []archiveFile{
			{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
			{src: "cmd/relaysrv/README.md", dst: "README.txt", perm: 0644},
			{src: "cmd/relaysrv/LICENSE", dst: "LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
		},
		debianFiles: []archiveFile{
			{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
			{src: "cmd/relaysrv/README.md", dst: "deb/usr/share/doc/relaysrv/README.txt", perm: 0644},
			{src: "cmd/relaysrv/LICENSE", dst: "deb/usr/share/doc/relaysrv/LICENSE.txt", perm: 0644},
			{src: "AUTHORS", dst: "deb/usr/share/doc/relaysrv/AUTHORS.txt", perm: 0644},
		},
	},
}

func init() {
	// The "syncthing" target includes a few more files found in the "etc"
	// and "extra" dirs.
	syncthingPkg := targets["syncthing"]
	for _, file := range listFiles("etc") {
		syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
	}
	for _, file := range listFiles("extra") {
		syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
	}
	for _, file := range listFiles("extra") {
		syncthingPkg.debianFiles = append(syncthingPkg.debianFiles, archiveFile{src: file, dst: "deb/usr/share/doc/syncthing/" + filepath.Base(file), perm: 0644})
	}
	targets["syncthing"] = syncthingPkg
}

const minGoVersion = 1.3

func main() {
	log.SetOutput(os.Stdout)
	log.SetFlags(0)

	if os.Getenv("GOPATH") == "" {
		setGoPath()
	}

	// We use Go 1.5+ vendoring.
	os.Setenv("GO15VENDOREXPERIMENT", "1")

	// Set path to $GOPATH/bin:$PATH so that we can for sure find tools we
	// might have installed during "build.go setup".
	os.Setenv("PATH", fmt.Sprintf("%s%cbin%c%s", os.Getenv("GOPATH"), os.PathSeparator, os.PathListSeparator, os.Getenv("PATH")))

	parseFlags()

	switch goarch {
	case "386", "amd64", "arm", "arm64", "ppc64", "ppc64le":
		break
	default:
		log.Printf("Unknown goarch %q; proceed with caution!", goarch)
	}

	goVersion, _ = checkRequiredGoVersion()

	// Invoking build.go with no parameters at all is equivalent to "go run
	// build.go install all" as that builds everything (incrementally),
	// which is what you want for maximum error checking during development.
	if flag.NArg() == 0 {
		var tags []string
		if noupgrade {
			tags = []string{"noupgrade"}
		}
		install(targets["all"], tags)

		vet("cmd", "lib")
		lint("./cmd/...")
		lint("./lib/...")
		return
	}

	// Otherwise, with any command given but not a target, the target is
	// "syncthing". So "go run build.go install" is "go run build.go install
	// syncthing" etc.
	targetName := "syncthing"
	if flag.NArg() > 1 {
		targetName = flag.Arg(1)
	}
	target, ok := targets[targetName]
	if !ok {
		log.Fatalln("Unknown target", target)
	}

	cmd := flag.Arg(0)
	switch cmd {
	case "setup":
		setup()

	case "install":
		var tags []string
		if noupgrade {
			tags = []string{"noupgrade"}
		}
		install(target, tags)

	case "build":
		var tags []string
		if noupgrade {
			tags = []string{"noupgrade"}
		}
		build(target, tags)

	case "test":
		test("./lib/...", "./cmd/...")

	case "bench":
		bench("./lib/...", "./cmd/...")

	case "assets":
		rebuildAssets()

	case "xdr":
		xdr()

	case "translate":
		translate()

	case "transifex":
		transifex()

	case "tar":
		buildTar(target)

	case "zip":
		buildZip(target)

	case "deb":
		buildDeb(target)

	case "clean":
		clean()

	case "vet":
		vet("build.go")
		vet("cmd", "lib")

	case "lint":
		lint(".")
		lint("./cmd/...")
		lint("./lib/...")
		if isGometalinterInstalled() {
			dirs := []string{".", "./cmd/...", "./lib/..."}
			gometalinter("deadcode", dirs, "test/util.go")
			gometalinter("structcheck", dirs)
			gometalinter("varcheck", dirs)
		}

	default:
		log.Fatalf("Unknown command %q", cmd)
	}
}

// setGoPath sets GOPATH correctly with the assumption that we are
// in $GOPATH/src/github.com/syncthing/syncthing.
func setGoPath() {
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
	log.Println("GOPATH is", gopath)
	os.Setenv("GOPATH", gopath)
}

func parseFlags() {
	flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
	flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
	flag.BoolVar(&noupgrade, "no-upgrade", noupgrade, "Disable upgrade functionality")
	flag.StringVar(&version, "version", getVersion(), "Set compiled in version string")
	flag.BoolVar(&race, "race", race, "Use race detector")
	flag.Parse()
}

func checkRequiredGoVersion() (float64, bool) {
	re := regexp.MustCompile(`go(\d+\.\d+)`)
	ver := runtime.Version()
	if m := re.FindStringSubmatch(ver); len(m) == 2 {
		vs := string(m[1])
		// This is a standard go build. Verify that it's new enough.
		f, err := strconv.ParseFloat(vs, 64)
		if err != nil {
			log.Printf("*** Couldn't parse Go version out of %q.\n*** This isn't known to work, proceed on your own risk.", vs)
			return 0, false
		}
		if f < 1.5 {
			log.Printf("*** Go version %.01f doesn't support the vendoring mechanism.\n*** Ensure correct dependencies in your $GOPATH.", f)
		} else if f < minGoVersion {
			log.Fatalf("*** Go version %.01f is less than required %.01f.\n*** This is known not to work, not proceeding.", f, minGoVersion)
		}
		return f, true
	}

	log.Printf("*** Unknown Go version %q.\n*** This isn't known to work, proceed on your own risk.", ver)
	return 0, false
}

func setup() {
	runPrint("go", "get", "-v", "golang.org/x/tools/cmd/cover")
	runPrint("go", "get", "-v", "golang.org/x/net/html")
	runPrint("go", "get", "-v", "github.com/FiloSottile/gvt")
	runPrint("go", "get", "-v", "github.com/axw/gocov/gocov")
	runPrint("go", "get", "-v", "github.com/AlekSi/gocov-xml")
	runPrint("go", "get", "-v", "bitbucket.org/tebeka/go2xunit")
	runPrint("go", "get", "-v", "github.com/alecthomas/gometalinter")
	runPrint("go", "get", "-v", "github.com/mitchellh/go-wordwrap")
}

func test(pkgs ...string) {
	lazyRebuildAssets()

	useRace := runtime.GOARCH == "amd64"
	switch runtime.GOOS {
	case "darwin", "linux", "freebsd", "windows":
	default:
		useRace = false
	}

	if useRace {
		runPrint("go", append([]string{"test", "-short", "-race", "-timeout", "60s", "-tags", "purego"}, pkgs...)...)
	} else {
		runPrint("go", append([]string{"test", "-short", "-timeout", "60s", "-tags", "purego"}, pkgs...)...)
	}
}

func bench(pkgs ...string) {
	lazyRebuildAssets()
	runPrint("go", append([]string{"test", "-run", "NONE", "-bench", "."}, pkgs...)...)
}

func install(target target, tags []string) {
	lazyRebuildAssets()

	tags = append(target.tags, tags...)

	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
	args := []string{"install", "-v", "-ldflags", ldflags()}
	if len(tags) > 0 {
		args = append(args, "-tags", strings.Join(tags, " "))
	}
	if race {
		args = append(args, "-race")
	}
	args = append(args, target.buildPkg)

	os.Setenv("GOOS", goos)
	os.Setenv("GOARCH", goarch)
	runPrint("go", args...)
}

func build(target target, tags []string) {
	lazyRebuildAssets()

	tags = append(target.tags, tags...)

	rmr(target.binaryName)
	args := []string{"build", "-i", "-v", "-ldflags", ldflags()}
	if len(tags) > 0 {
		args = append(args, "-tags", strings.Join(tags, " "))
	}
	if race {
		args = append(args, "-race")
	}
	args = append(args, target.buildPkg)

	os.Setenv("GOOS", goos)
	os.Setenv("GOARCH", goarch)
	runPrint("go", args...)
}

func buildTar(target target) {
	name := archiveName(target)
	filename := name + ".tar.gz"

	var tags []string
	if noupgrade {
		tags = []string{"noupgrade"}
		name += "-noupgrade"
	}

	build(target, tags)

	if goos == "darwin" {
		macosCodesign(target.binaryName)
	}
{'fraction_non_alphanumeric': 0.19976948092042976, 'fraction_numerical': 0.021323014860247808, 'mean_word_length': 3.783225044300059, 'pattern_counts': {'":': 42, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3324599', 'n_tokens_mistral': 10230, 'n_tokens_neox': 9334, 'n_words': 2194}