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 }