github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/mobile/cmd/gomobile/release.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //+build ignore 6 7 // Release is a tool for building the NDK tarballs hosted on dl.google.com. 8 // 9 // The Go toolchain only needs the gcc compiler and headers, which are ~10MB. 10 // The entire NDK is ~400MB. Building smaller toolchain binaries reduces the 11 // run time of gomobile init significantly. 12 package main 13 14 import ( 15 "archive/tar" 16 "bufio" 17 "compress/gzip" 18 "crypto/sha256" 19 "encoding/hex" 20 "fmt" 21 "hash" 22 "io" 23 "io/ioutil" 24 "log" 25 "net/http" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "runtime" 30 ) 31 32 const ndkVersion = "ndk-r10e" 33 34 type version struct { 35 os string 36 arch string 37 } 38 39 var hosts = []version{ 40 {"darwin", "x86_64"}, 41 {"linux", "x86"}, 42 {"linux", "x86_64"}, 43 {"windows", "x86"}, 44 {"windows", "x86_64"}, 45 } 46 47 type target struct { 48 arch string 49 platform string 50 gcc string 51 toolPrefix string 52 } 53 54 var targets = []target{ 55 {"arm", "android-15", "arm-linux-androideabi-4.8", "arm-linux-androideabi"}, 56 {"x86", "android-15", "x86-4.8", "i686-linux-android"}, 57 {"x86_64", "android-21", "x86_64-4.9", "x86_64-linux-android"}, 58 } 59 60 var tmpdir string 61 62 func main() { 63 var err error 64 tmpdir, err = ioutil.TempDir("", "gomobile-release-") 65 if err != nil { 66 log.Panic(err) 67 } 68 defer os.RemoveAll(tmpdir) 69 70 fmt.Println("var fetchHashes = map[string]string{") 71 for _, host := range hosts { 72 if err := mkpkg(host); err != nil { 73 log.Panic(err) 74 } 75 } 76 if err := mkALPkg(); err != nil { 77 log.Panic(err) 78 } 79 80 fmt.Println("}") 81 } 82 83 func run(dir, path string, args ...string) error { 84 cmd := exec.Command(path, args...) 85 cmd.Dir = dir 86 buf, err := cmd.CombinedOutput() 87 if err != nil { 88 fmt.Printf("%s\n", buf) 89 } 90 return err 91 } 92 93 func mkALPkg() (err error) { 94 ndkPath, _, err := fetchNDK(version{os: hostOS, arch: hostArch}) 95 if err != nil { 96 return err 97 } 98 ndkRoot := tmpdir + "/android-" + ndkVersion 99 if err := inflate(tmpdir, ndkPath); err != nil { 100 return err 101 } 102 103 alTmpDir, err := ioutil.TempDir("", "openal-release-") 104 if err != nil { 105 return err 106 } 107 defer os.RemoveAll(alTmpDir) 108 109 if err := run(alTmpDir, "git", "clone", "-v", "git://repo.or.cz/openal-soft.git", alTmpDir); err != nil { 110 return err 111 } 112 // TODO: use more recent revision? 113 if err := run(alTmpDir, "git", "checkout", "19f79be57b8e768f44710b6d26017bc1f8c8fbda"); err != nil { 114 return err 115 } 116 117 files := map[string]string{ 118 "include/AL/al.h": "include/AL/al.h", 119 "include/AL/alc.h": "include/AL/alc.h", 120 "COPYING": "include/AL/COPYING", 121 } 122 123 for _, t := range targets { 124 abi := t.arch 125 if abi == "arm" { 126 abi = "armeabi" 127 } 128 buildDir := alTmpDir + "/build/" + abi 129 toolchain := buildDir + "/toolchain" 130 if err := os.MkdirAll(toolchain, 0755); err != nil { 131 return err 132 } 133 // standalone ndk toolchains make openal-soft's build config easier. 134 if err := run(ndkRoot, "env", 135 "build/tools/make-standalone-toolchain.sh", 136 "--arch="+t.arch, 137 "--platform="+t.platform, 138 "--install-dir="+toolchain); err != nil { 139 return fmt.Errorf("make-standalone-toolchain.sh failed: %v", err) 140 } 141 142 orgPath := os.Getenv("PATH") 143 os.Setenv("PATH", toolchain+"/bin"+string(os.PathListSeparator)+orgPath) 144 if err := run(buildDir, "cmake", 145 "../../", 146 "-DCMAKE_TOOLCHAIN_FILE=../../XCompile-Android.txt", 147 "-DHOST="+t.toolPrefix); err != nil { 148 return fmt.Errorf("cmake failed: %v", err) 149 } 150 os.Setenv("PATH", orgPath) 151 152 if err := run(buildDir, "make"); err != nil { 153 return fmt.Errorf("make failed: %v", err) 154 } 155 156 files["build/"+abi+"/libopenal.so"] = "lib/" + abi + "/libopenal.so" 157 } 158 159 // Build the tarball. 160 aw := newArchiveWriter("gomobile-openal-soft-1.16.0.1.tar.gz") 161 defer func() { 162 err2 := aw.Close() 163 if err == nil { 164 err = err2 165 } 166 }() 167 168 for src, dst := range files { 169 f, err := os.Open(filepath.Join(alTmpDir, src)) 170 if err != nil { 171 return err 172 } 173 fi, err := f.Stat() 174 if err != nil { 175 return err 176 } 177 aw.WriteHeader(&tar.Header{ 178 Name: dst, 179 Mode: int64(fi.Mode()), 180 Size: fi.Size(), 181 }) 182 io.Copy(aw, f) 183 f.Close() 184 } 185 return nil 186 } 187 188 func fetchNDK(host version) (binPath, url string, err error) { 189 ndkName := "android-" + ndkVersion + "-" + host.os + "-" + host.arch + "." 190 if host.os == "windows" { 191 ndkName += "exe" 192 } else { 193 ndkName += "bin" 194 } 195 196 url = "http://dl.google.com/android/ndk/" + ndkName 197 binPath = tmpdir + "/" + ndkName 198 199 if _, err := os.Stat(binPath); err == nil { 200 log.Printf("\t%q: using cached NDK\n", ndkName) 201 return binPath, url, nil 202 } 203 204 log.Printf("%s\n", url) 205 binHash, err := fetch(binPath, url) 206 if err != nil { 207 return "", "", err 208 } 209 210 fmt.Printf("\t%q: %q,\n", ndkName, binHash) 211 return binPath, url, nil 212 } 213 214 func mkpkg(host version) error { 215 binPath, url, err := fetchNDK(host) 216 if err != nil { 217 return err 218 } 219 220 src := tmpdir + "/" + host.os + "-" + host.arch + "-src" 221 dst := tmpdir + "/" + host.os + "-" + host.arch + "-dst" 222 defer os.RemoveAll(src) 223 defer os.RemoveAll(dst) 224 225 if err := os.Mkdir(src, 0755); err != nil { 226 return err 227 } 228 229 if err := inflate(src, binPath); err != nil { 230 return err 231 } 232 233 // The NDK is unpacked into tmpdir/linux-x86_64-src/android-{{ndkVersion}}. 234 // Move the files we want into tmpdir/linux-x86_64-dst/android-{{ndkVersion}}. 235 // We preserve the same file layout to make the full NDK interchangable 236 // with the cut down file. 237 for _, t := range targets { 238 usr := fmt.Sprintf("android-%s/platforms/%s/arch-%s/usr/", ndkVersion, t.platform, t.arch) 239 gcc := fmt.Sprintf("android-%s/toolchains/%s/prebuilt/", ndkVersion, t.gcc) 240 241 if host.os == "windows" && host.arch == "x86" { 242 gcc += "windows" 243 } else { 244 gcc += host.os + "-" + host.arch 245 } 246 247 if err := os.MkdirAll(dst+"/"+usr, 0755); err != nil { 248 return err 249 } 250 if err := os.MkdirAll(dst+"/"+gcc, 0755); err != nil { 251 return err 252 } 253 254 subdirs := []string{"include", "lib"} 255 switch t.arch { 256 case "x86_64": 257 subdirs = append(subdirs, "lib64", "libx32") 258 } 259 if err := move(dst+"/"+usr, src+"/"+usr, subdirs...); err != nil { 260 return err 261 } 262 263 if err := move(dst+"/"+gcc, src+"/"+gcc, "bin", "lib", "libexec", "COPYING", "COPYING.LIB"); err != nil { 264 return err 265 } 266 } 267 268 // Build the tarball. 269 aw := newArchiveWriter("gomobile-" + ndkVersion + "-" + host.os + "-" + host.arch + ".tar.gz") 270 defer func() { 271 err2 := aw.Close() 272 if err == nil { 273 err = err2 274 } 275 }() 276 277 readme := "Stripped down copy of:\n\n\t" + url + "\n\nGenerated by golang.org/x/mobile/cmd/gomobile/release.go." 278 aw.WriteHeader(&tar.Header{ 279 Name: "README", 280 Mode: 0644, 281 Size: int64(len(readme)), 282 }) 283 io.WriteString(aw, readme) 284 285 return filepath.Walk(dst, func(path string, fi os.FileInfo, err error) error { 286 defer func() { 287 if err != nil { 288 err = fmt.Errorf("%s: %v", path, err) 289 } 290 }() 291 if err != nil { 292 return err 293 } 294 if path == dst { 295 return nil 296 } 297 name := path[len(dst)+1:] 298 if fi.IsDir() { 299 return nil 300 } 301 if fi.Mode()&os.ModeSymlink != 0 { 302 dst, err := os.Readlink(path) 303 if err != nil { 304 log.Printf("bad symlink: %s", name) 305 return nil 306 } 307 aw.WriteHeader(&tar.Header{ 308 Name: name, 309 Linkname: dst, 310 Typeflag: tar.TypeSymlink, 311 }) 312 return nil 313 } 314 aw.WriteHeader(&tar.Header{ 315 Name: name, 316 Mode: int64(fi.Mode()), 317 Size: fi.Size(), 318 }) 319 f, err := os.Open(path) 320 if err != nil { 321 return err 322 } 323 io.Copy(aw, f) 324 f.Close() 325 return nil 326 }) 327 } 328 329 func fetch(dst, url string) (string, error) { 330 f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0755) 331 if err != nil { 332 return "", err 333 } 334 resp, err := http.Get(url) 335 if err != nil { 336 return "", err 337 } 338 hashw := sha256.New() 339 _, err = io.Copy(io.MultiWriter(hashw, f), resp.Body) 340 err2 := resp.Body.Close() 341 err3 := f.Close() 342 if err != nil { 343 return "", err 344 } 345 if err2 != nil { 346 return "", err2 347 } 348 if err3 != nil { 349 return "", err3 350 } 351 return hex.EncodeToString(hashw.Sum(nil)), nil 352 } 353 354 func inflate(dst, path string) error { 355 p7zip := "7z" 356 if runtime.GOOS == "darwin" { 357 p7zip = "/Applications/Keka.app/Contents/Resources/keka7z" 358 } 359 cmd := exec.Command(p7zip, "x", path) 360 cmd.Dir = dst 361 out, err := cmd.CombinedOutput() 362 if err != nil { 363 os.Stderr.Write(out) 364 return err 365 } 366 return nil 367 } 368 369 func move(dst, src string, names ...string) error { 370 for _, name := range names { 371 if err := os.Rename(src+"/"+name, dst+"/"+name); err != nil { 372 return err 373 } 374 } 375 return nil 376 } 377 378 // archiveWriter writes a .tar.gz archive and prints its SHA256 to stdout. 379 // If any error occurs, it continues as a no-op until Close, when it is reported. 380 type archiveWriter struct { 381 name string 382 hashw hash.Hash 383 f *os.File 384 zw *gzip.Writer 385 bw *bufio.Writer 386 tw *tar.Writer 387 err error 388 } 389 390 func (aw *archiveWriter) WriteHeader(h *tar.Header) { 391 if aw.err != nil { 392 return 393 } 394 aw.err = aw.tw.WriteHeader(h) 395 } 396 397 func (aw *archiveWriter) Write(b []byte) (n int, err error) { 398 if aw.err != nil { 399 return len(b), nil 400 } 401 n, aw.err = aw.tw.Write(b) 402 return n, nil 403 } 404 405 func (aw *archiveWriter) Close() (err error) { 406 err = aw.tw.Close() 407 if aw.err == nil { 408 aw.err = err 409 } 410 err = aw.zw.Close() 411 if aw.err == nil { 412 aw.err = err 413 } 414 err = aw.bw.Flush() 415 if aw.err == nil { 416 aw.err = err 417 } 418 err = aw.f.Close() 419 if aw.err == nil { 420 aw.err = err 421 } 422 if aw.err != nil { 423 return aw.err 424 } 425 hash := hex.EncodeToString(aw.hashw.Sum(nil)) 426 fmt.Printf("\t%q: %q,\n", aw.name, hash) 427 return nil 428 } 429 430 func newArchiveWriter(name string) *archiveWriter { 431 aw := &archiveWriter{name: name} 432 aw.f, aw.err = os.Create(name) 433 if aw.err != nil { 434 return aw 435 } 436 aw.hashw = sha256.New() 437 aw.bw = bufio.NewWriter(io.MultiWriter(aw.f, aw.hashw)) 438 aw.zw, aw.err = gzip.NewWriterLevel(aw.bw, gzip.BestCompression) 439 if aw.err != nil { 440 return aw 441 } 442 aw.tw = tar.NewWriter(aw.zw) 443 return aw 444 } 445 446 var hostOS, hostArch string 447 448 func init() { 449 switch runtime.GOOS { 450 case "linux", "darwin": 451 hostOS = runtime.GOOS 452 } 453 switch runtime.GOARCH { 454 case "386": 455 hostArch = "x86" 456 case "amd64": 457 hostArch = "x86_64" 458 } 459 if hostOS == "" || hostArch == "" { 460 panic(fmt.Sprintf("cannot run release from OS/Arch: %s/%s", hostOS, hostArch)) 461 } 462 }