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 }