github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/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  		"-i",
   186  		"-o", output,
   187  		"-tags", *tags,
   188  		"..",
   189  	}
   190  	env := []string{
   191  		"GOOS=" + goos,
   192  		"GOARCH=" + goarch,
   193  	}
   194  	if !*cgo {
   195  		env = append(env, "CGO_ENABLED=0")
   196  	} else {
   197  		env = append(env, "CGO_ENABLED=1")
   198  	}
   199  	if flags, ok := archFlags[goarch]; ok {
   200  		env = append(env, flags...)
   201  	}
   202  	err = runEnv(args, env)
   203  	if err != nil {
   204  		log.Printf("Error compiling %s/%s: %v", goos, goarch, err)
   205  		return false
   206  	}
   207  	if !*compileOnly {
   208  		artifacts := []string{buildZip(dir)}
   209  		// build a .deb and .rpm if appropriate
   210  		if goos == "linux" {
   211  			artifacts = append(artifacts, buildDebAndRpm(dir, version, goarch)...)
   212  		}
   213  		if *copyAs != "" {
   214  			for _, artifact := range artifacts {
   215  				run("ln", artifact, strings.Replace(artifact, "-"+version, "-"+*copyAs, 1))
   216  			}
   217  		}
   218  		// tidy up
   219  		run("rm", "-rf", dir)
   220  	}
   221  	log.Printf("Done compiling %s/%s", goos, goarch)
   222  	return true
   223  }
   224  
   225  func compile(version string) {
   226  	start := time.Now()
   227  	wg := new(sync.WaitGroup)
   228  	run := make(chan func(), *parallel)
   229  	for i := 0; i < *parallel; i++ {
   230  		wg.Add(1)
   231  		go func() {
   232  			defer wg.Done()
   233  			for f := range run {
   234  				f()
   235  			}
   236  		}()
   237  	}
   238  	includeRe, err := regexp.Compile(*include)
   239  	if err != nil {
   240  		log.Fatalf("Bad -include regexp: %v", err)
   241  	}
   242  	excludeRe, err := regexp.Compile(*exclude)
   243  	if err != nil {
   244  		log.Fatalf("Bad -exclude regexp: %v", err)
   245  	}
   246  	compiled := 0
   247  	var failuresMu sync.Mutex
   248  	var failures []string
   249  	for _, osarch := range osarches {
   250  		if excludeRe.MatchString(osarch) || !includeRe.MatchString(osarch) {
   251  			continue
   252  		}
   253  		parts := strings.Split(osarch, "/")
   254  		if len(parts) != 2 {
   255  			log.Fatalf("Bad osarch %q", osarch)
   256  		}
   257  		goos, goarch := parts[0], parts[1]
   258  		userGoos := goos
   259  		if goos == "darwin" {
   260  			userGoos = "osx"
   261  		}
   262  		dir := filepath.Join("rclone-" + version + "-" + userGoos + "-" + goarch)
   263  		run <- func() {
   264  			if !compileArch(version, goos, goarch, dir) {
   265  				failuresMu.Lock()
   266  				failures = append(failures, goos+"/"+goarch)
   267  				failuresMu.Unlock()
   268  			}
   269  		}
   270  		compiled++
   271  	}
   272  	close(run)
   273  	wg.Wait()
   274  	log.Printf("Compiled %d arches in %v", compiled, time.Since(start))
   275  	if len(failures) > 0 {
   276  		sort.Strings(failures)
   277  		log.Printf("%d compile failures:\n  %s\n", len(failures), strings.Join(failures, "\n  "))
   278  		os.Exit(1)
   279  	}
   280  }
   281  
   282  func main() {
   283  	flag.Parse()
   284  	args := flag.Args()
   285  	if len(args) != 1 {
   286  		log.Fatalf("Syntax: %s <version>", os.Args[0])
   287  	}
   288  	version := args[0]
   289  	if !*noClean {
   290  		run("rm", "-rf", "build")
   291  		run("mkdir", "build")
   292  	}
   293  	chdir("build")
   294  	err := ioutil.WriteFile("version.txt", []byte(fmt.Sprintf("rclone %s\n", version)), 0666)
   295  	if err != nil {
   296  		log.Fatalf("Couldn't write version.txt: %v", err)
   297  	}
   298  	compile(version)
   299  }