github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/bin/cross-compile.go (about)

     1  // +build ignore
     2  
     3  // Cross compile rclone - in go because I hate bash ;-)
     4  
     5  package main
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"log"
    12  	"os"
    13  	"os/exec"
    14  	"path"
    15  	"path/filepath"
    16  	"regexp"
    17  	"runtime"
    18  	"sort"
    19  	"strings"
    20  	"sync"
    21  	"text/template"
    22  	"time"
    23  )
    24  
    25  var (
    26  	// Flags
    27  	debug       = flag.Bool("d", false, "Print commands instead of running them.")
    28  	parallel    = flag.Int("parallel", runtime.NumCPU(), "Number of commands to run in parallel.")
    29  	copyAs      = flag.String("release", "", "Make copies of the releases with this name")
    30  	gitLog      = flag.String("git-log", "", "git log to include as well")
    31  	include     = flag.String("include", "^.*$", "os/arch regexp to include")
    32  	exclude     = flag.String("exclude", "^$", "os/arch regexp to exclude")
    33  	cgo         = flag.Bool("cgo", false, "Use cgo for the build")
    34  	noClean     = flag.Bool("no-clean", false, "Don't clean the build directory before running.")
    35  	tags        = flag.String("tags", "", "Space separated list of build tags")
    36  	compileOnly = flag.Bool("compile-only", false, "Just build the binary, not the zip.")
    37  )
    38  
    39  // GOOS/GOARCH pairs we build for
    40  var osarches = []string{
    41  	"windows/386",
    42  	"windows/amd64",
    43  	"darwin/386",
    44  	"darwin/amd64",
    45  	"linux/386",
    46  	"linux/amd64",
    47  	"linux/arm",
    48  	"linux/arm64",
    49  	"linux/mips",
    50  	"linux/mipsle",
    51  	"freebsd/386",
    52  	"freebsd/amd64",
    53  	"freebsd/arm",
    54  	"netbsd/386",
    55  	"netbsd/amd64",
    56  	"netbsd/arm",
    57  	"openbsd/386",
    58  	"openbsd/amd64",
    59  	"plan9/386",
    60  	"plan9/amd64",
    61  	"solaris/amd64",
    62  }
    63  
    64  // Special environment flags for a given arch
    65  var archFlags = map[string][]string{
    66  	"386":    {"GO386=387"},
    67  	"mips":   {"GOMIPS=softfloat"},
    68  	"mipsle": {"GOMIPS=softfloat"},
    69  }
    70  
    71  // runEnv - run a shell command with env
    72  func runEnv(args, env []string) error {
    73  	if *debug {
    74  		args = append([]string{"echo"}, args...)
    75  	}
    76  	cmd := exec.Command(args[0], args[1:]...)
    77  	if env != nil {
    78  		cmd.Env = append(os.Environ(), env...)
    79  	}
    80  	if *debug {
    81  		log.Printf("args = %v, env = %v\n", args, cmd.Env)
    82  	}
    83  	out, err := cmd.CombinedOutput()
    84  	if err != nil {
    85  		log.Print("----------------------------")
    86  		log.Printf("Failed to run %v: %v", args, err)
    87  		log.Printf("Command output was:\n%s", out)
    88  		log.Print("----------------------------")
    89  	}
    90  	return err
    91  }
    92  
    93  // run a shell command
    94  func run(args ...string) {
    95  	err := runEnv(args, nil)
    96  	if err != nil {
    97  		log.Fatalf("Exiting after error: %v", err)
    98  	}
    99  }
   100  
   101  // chdir or die
   102  func chdir(dir string) {
   103  	err := os.Chdir(dir)
   104  	if err != nil {
   105  		log.Fatalf("Couldn't cd into %q: %v", dir, err)
   106  	}
   107  }
   108  
   109  // substitute data from go template file in to file out
   110  func substitute(inFile, outFile string, data interface{}) {
   111  	t, err := template.ParseFiles(inFile)
   112  	if err != nil {
   113  		log.Fatalf("Failed to read template file %q: %v %v", inFile, err)
   114  	}
   115  	out, err := os.Create(outFile)
   116  	if err != nil {
   117  		log.Fatalf("Failed to create output file %q: %v %v", outFile, err)
   118  	}
   119  	defer func() {
   120  		err := out.Close()
   121  		if err != nil {
   122  			log.Fatalf("Failed to close output file %q: %v %v", outFile, err)
   123  		}
   124  	}()
   125  	err = t.Execute(out, data)
   126  	if err != nil {
   127  		log.Fatalf("Failed to substitute template file %q: %v %v", inFile, err)
   128  	}
   129  }
   130  
   131  // build the zip package return its name
   132  func buildZip(dir string) string {
   133  	// Now build the zip
   134  	run("cp", "-a", "../MANUAL.txt", filepath.Join(dir, "README.txt"))
   135  	run("cp", "-a", "../MANUAL.html", filepath.Join(dir, "README.html"))
   136  	run("cp", "-a", "../rclone.1", dir)
   137  	if *gitLog != "" {
   138  		run("cp", "-a", *gitLog, dir)
   139  	}
   140  	zip := dir + ".zip"
   141  	run("zip", "-r9", zip, dir)
   142  	return zip
   143  }
   144  
   145  // Build .deb and .rpm packages
   146  //
   147  // It returns a list of artifacts it has made
   148  func buildDebAndRpm(dir, version, goarch string) []string {
   149  	// Make internal version number acceptable to .deb and .rpm
   150  	pkgVersion := version[1:]
   151  	pkgVersion = strings.Replace(pkgVersion, "β", "-beta", -1)
   152  	pkgVersion = strings.Replace(pkgVersion, "-", ".", -1)
   153  
   154  	// Make nfpm.yaml from the template
   155  	substitute("../bin/nfpm.yaml", path.Join(dir, "nfpm.yaml"), map[string]string{
   156  		"Version": pkgVersion,
   157  		"Arch":    goarch,
   158  	})
   159  
   160  	// build them
   161  	var artifacts []string
   162  	for _, pkg := range []string{".deb", ".rpm"} {
   163  		artifact := dir + pkg
   164  		run("bash", "-c", "cd "+dir+" && nfpm -f nfpm.yaml pkg -t ../"+artifact)
   165  		artifacts = append(artifacts, artifact)
   166  	}
   167  
   168  	return artifacts
   169  }
   170  
   171  // build the binary in dir returning success or failure
   172  func compileArch(version, goos, goarch, dir string) bool {
   173  	log.Printf("Compiling %s/%s", goos, goarch)
   174  	output := filepath.Join(dir, "rclone")
   175  	if goos == "windows" {
   176  		output += ".exe"
   177  	}
   178  	err := os.MkdirAll(dir, 0777)
   179  	if err != nil {
   180  		log.Fatalf("Failed to mkdir: %v", err)
   181  	}
   182  	args := []string{
   183  		"go", "build",
   184  		"--ldflags", "-s -X github.com/rclone/rclone/fs.Version=" + version,
   185  		"-trimpath",
   186  		"-i",
   187  		"-o", output,
   188  		"-tags", *tags,
   189  		"..",
   190  	}
   191  	env := []string{
   192  		"GOOS=" + goos,
   193  		"GOARCH=" + goarch,
   194  	}
   195  	if !*cgo {
   196  		env = append(env, "CGO_ENABLED=0")
   197  	} else {
   198  		env = append(env, "CGO_ENABLED=1")
   199  	}
   200  	if flags, ok := archFlags[goarch]; ok {
   201  		env = append(env, flags...)
   202  	}
   203  	err = runEnv(args, env)
   204  	if err != nil {
   205  		log.Printf("Error compiling %s/%s: %v", goos, goarch, err)
   206  		return false
   207  	}
   208  	if !*compileOnly {
   209  		artifacts := []string{buildZip(dir)}
   210  		// build a .deb and .rpm if appropriate
   211  		if goos == "linux" {
   212  			artifacts = append(artifacts, buildDebAndRpm(dir, version, goarch)...)
   213  		}
   214  		if *copyAs != "" {
   215  			for _, artifact := range artifacts {
   216  				run("ln", artifact, strings.Replace(artifact, "-"+version, "-"+*copyAs, 1))
   217  			}
   218  		}
   219  		// tidy up
   220  		run("rm", "-rf", dir)
   221  	}
   222  	log.Printf("Done compiling %s/%s", goos, goarch)
   223  	return true
   224  }
   225  
   226  func compile(version string) {
   227  	start := time.Now()
   228  	wg := new(sync.WaitGroup)
   229  	run := make(chan func(), *parallel)
   230  	for i := 0; i < *parallel; i++ {
   231  		wg.Add(1)
   232  		go func() {
   233  			defer wg.Done()
   234  			for f := range run {
   235  				f()
   236  			}
   237  		}()
   238  	}
   239  	includeRe, err := regexp.Compile(*include)
   240  	if err != nil {
   241  		log.Fatalf("Bad -include regexp: %v", err)
   242  	}
   243  	excludeRe, err := regexp.Compile(*exclude)
   244  	if err != nil {
   245  		log.Fatalf("Bad -exclude regexp: %v", err)
   246  	}
   247  	compiled := 0
   248  	var failuresMu sync.Mutex
   249  	var failures []string
   250  	for _, osarch := range osarches {
   251  		if excludeRe.MatchString(osarch) || !includeRe.MatchString(osarch) {
   252  			continue
   253  		}
   254  		parts := strings.Split(osarch, "/")
   255  		if len(parts) != 2 {
   256  			log.Fatalf("Bad osarch %q", osarch)
   257  		}
   258  		goos, goarch := parts[0], parts[1]
   259  		userGoos := goos
   260  		if goos == "darwin" {
   261  			userGoos = "osx"
   262  		}
   263  		dir := filepath.Join("rclone-" + version + "-" + userGoos + "-" + goarch)
   264  		run <- func() {
   265  			if !compileArch(version, goos, goarch, dir) {
   266  				failuresMu.Lock()
   267  				failures = append(failures, goos+"/"+goarch)
   268  				failuresMu.Unlock()
   269  			}
   270  		}
   271  		compiled++
   272  	}
   273  	close(run)
   274  	wg.Wait()
   275  	log.Printf("Compiled %d arches in %v", compiled, time.Since(start))
   276  	if len(failures) > 0 {
   277  		sort.Strings(failures)
   278  		log.Printf("%d compile failures:\n  %s\n", len(failures), strings.Join(failures, "\n  "))
   279  		os.Exit(1)
   280  	}
   281  }
   282  
   283  func main() {
   284  	flag.Parse()
   285  	args := flag.Args()
   286  	if len(args) != 1 {
   287  		log.Fatalf("Syntax: %s <version>", os.Args[0])
   288  	}
   289  	version := args[0]
   290  	if !*noClean {
   291  		run("rm", "-rf", "build")
   292  		run("mkdir", "build")
   293  	}
   294  	chdir("build")
   295  	err := ioutil.WriteFile("version.txt", []byte(fmt.Sprintf("rclone %s\n", version)), 0666)
   296  	if err != nil {
   297  		log.Fatalf("Couldn't write version.txt: %v", err)
   298  	}
   299  	compile(version)
   300  }