| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //+build ignore |
| |
| // Release is a tool for building the NDK tarballs hosted on dl.google.com. |
| // |
| // The Go toolchain only needs the gcc compiler and headers, which are ~10MB. |
| // The entire NDK is ~400MB. Building smaller toolchain binaries reduces the |
| // run time of gomobile init significantly. |
| package main |
| |
| import ( |
| "archive/tar" |
| "bufio" |
| "compress/gzip" |
| "crypto/sha256" |
| "encoding/hex" |
| "fmt" |
| "hash" |
| "io" |
| "io/ioutil" |
| "log" |
| "net/http" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| ) |
| |
| const ndkVersion = "ndk-r10e" |
| |
| type version struct { |
| os string |
| arch string |
| } |
| |
| var hosts = []version{ |
| {"darwin", "x86_64"}, |
| {"linux", "x86"}, |
| {"linux", "x86_64"}, |
| {"windows", "x86"}, |
| {"windows", "x86_64"}, |
| } |
| |
| var tmpdir string |
| |
| func main() { |
| var err error |
| tmpdir, err = ioutil.TempDir("", "gomobile-release-") |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer os.RemoveAll(tmpdir) |
| |
| fmt.Println("var fetchHashes = map[string]string{") |
| |
| for _, host := range hosts { |
| if err := mkpkg(host); err != nil { |
| log.Fatal(err) |
| } |
| } |
| |
| if err := mkALPkg(); err != nil { |
| log.Fatal(err) |
| } |
| |
| fmt.Println("}") |
| } |
| |
| func run(dir, path string, args ...string) error { |
| cmd := exec.Command(path, args...) |
| cmd.Dir = dir |
| cmd.Stdout = os.Stdout |
| cmd.Stderr = os.Stderr |
| return cmd.Run() |
| } |
| |
| func mkALPkg() (err error) { |
| alTmpDir, err := ioutil.TempDir("", "openal-release-") |
| if err != nil { |
| return err |
| } |
| defer os.RemoveAll(alTmpDir) |
| |
| if err := run(alTmpDir, "git", "clone", "-v", "git://repo.or.cz/openal-soft.git", alTmpDir); err != nil { |
| return err |
| } |
| if err := run(alTmpDir, "git", "checkout", "19f79be57b8e768f44710b6d26017bc1f8c8fbda"); err != nil { |
| return err |
| } |
| if err := run(filepath.Join(alTmpDir, "cmake"), "cmake", "..", "-DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt", "-DHOST=arm-linux-androideabi"); err != nil { |
| return err |
| } |
| if err := run(filepath.Join(alTmpDir, "cmake"), "make"); err != nil { |
| return err |
| } |
| |
| // Build the tarball. |
| aw := newArchiveWriter("gomobile-openal-soft-1.16.0.1.tar.gz") |
| defer func() { |
| err2 := aw.Close() |
| if err == nil { |
| err = err2 |
| } |
| }() |
| |
| files := map[string]string{ |
| "cmake/libopenal.so": "lib/armeabi/libopenal.so", |
| "include/AL/al.h": "include/AL/al.h", |
| "include/AL/alc.h": "include/AL/alc.h", |
| "COPYING": "include/AL/COPYING", |
| } |
| for src, dst := range files { |
| f, err := os.Open(filepath.Join(alTmpDir, src)) |
| if err != nil { |
| return err |
| } |
| fi, err := f.Stat() |
| if err != nil { |
| return err |
| } |
| aw.WriteHeader(&tar.Header{ |
| Name: dst, |
| Mode: int64(fi.Mode()), |
| Size: fi.Size(), |
| }) |
| io.Copy(aw, f) |
| f.Close() |
| } |
| return nil |
| } |
| |
| func mkpkg(host version) (err error) { |
| ndkName := "android-" + ndkVersion + "-" + host.os + "-" + host.arch + "." |
| if host.os == "windows" { |
| ndkName += "exe" |
| } else { |
| ndkName += "bin" |
| } |
| url := "http://dl.google.com/android/ndk/" + ndkName |
| log.Printf("%s\n", url) |
| binPath := tmpdir + "/" + ndkName |
| binHash, err := fetch(binPath, url) |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| fmt.Printf("\t%q: %q,\n", ndkName, binHash) |
| |
| src := tmpdir + "/" + host.os + "-" + host.arch + "-src" |
| dst := tmpdir + "/" + host.os + "-" + host.arch + "-dst" |
| if err := os.Mkdir(src, 0755); err != nil { |
| return err |
| } |
| if err := inflate(src, binPath); err != nil { |
| return err |
| } |
| |
| // The NDK is unpacked into tmpdir/linux-x86_64-src/android-{{ndkVersion}}. |
| // Move the files we want into tmpdir/linux-x86_64-dst/android-{{ndkVersion}}. |
| // We preserve the same file layout to make the full NDK interchangable |
| // with the cut down file. |
| usr := "android-" + ndkVersion + "/platforms/android-15/arch-arm/usr" |
| gcc := "android-" + ndkVersion + "/toolchains/arm-linux-androideabi-4.8/prebuilt/" |
| if host.os == "windows" && host.arch == "x86" { |
| gcc += "windows" |
| } else { |
| gcc += host.os + "-" + host.arch |
| } |
| |
| if err := os.MkdirAll(dst+"/"+usr, 0755); err != nil { |
| return err |
| } |
| if err := os.MkdirAll(dst+"/"+gcc, 0755); err != nil { |
| return err |
| } |
| if err := move(dst+"/"+usr, src+"/"+usr, "include", "lib"); err != nil { |
| return err |
| } |
| if err := move(dst+"/"+gcc, src+"/"+gcc, "bin", "lib", "libexec", "COPYING", "COPYING.LIB"); err != nil { |
| return err |
| } |
| |
| // Build the tarball. |
| aw := newArchiveWriter("gomobile-" + ndkVersion + "-" + host.os + "-" + host.arch + ".tar.gz") |
| defer func() { |
| err2 := aw.Close() |
| if err == nil { |
| err = err2 |
| } |
| }() |
| |
| readme := "Stripped down copy of:\n\n\t" + url + "\n\nGenerated by golang.org/x/mobile/cmd/gomobile/release.go." |
| aw.WriteHeader(&tar.Header{ |
| Name: "README", |
| Mode: 0644, |
| Size: int64(len(readme)), |
| }) |
| io.WriteString(aw, readme) |
| |
| return filepath.Walk(dst, func(path string, fi os.FileInfo, err error) error { |
| defer func() { |
| if err != nil { |
| err = fmt.Errorf("%s: %v", path, err) |
| } |
| }() |
| if err != nil { |
| return err |
| } |
| if path == dst { |
| return nil |
| } |
| name := path[len(dst)+1:] |
| if fi.IsDir() { |
| return nil |
| } |
| if fi.Mode()&os.ModeSymlink != 0 { |
| dst, err := os.Readlink(path) |
| if err != nil { |
| log.Printf("bad symlink: %s", name) |
| return nil |
| } |
| aw.WriteHeader(&tar.Header{ |
| Name: name, |
| Linkname: dst, |
| Typeflag: tar.TypeSymlink, |
| }) |
| return nil |
| } |
| aw.WriteHeader(&tar.Header{ |
| Name: name, |
| Mode: int64(fi.Mode()), |
| Size: fi.Size(), |
| }) |
| f, err := os.Open(path) |
| if err != nil { |
| return err |
| } |
| io.Copy(aw, f) |
| f.Close() |
| return nil |
| }) |
| } |
| |
| func fetch(dst, url string) (string, error) { |
| f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0755) |
| if err != nil { |
| return "", err |
| } |
| resp, err := http.Get(url) |
| if err != nil { |
| return "", err |
| } |
| hashw := sha256.New() |
| _, err = io.Copy(io.MultiWriter(hashw, f), resp.Body) |
| err2 := resp.Body.Close() |
| err3 := f.Close() |
| if err != nil { |
| return "", err |
| } |
| if err2 != nil { |
| return "", err2 |
| } |
| if err3 != nil { |
| return "", err3 |
| } |
| return hex.EncodeToString(hashw.Sum(nil)), nil |
| } |
| |
| func inflate(dst, path string) error { |
| p7zip := "7z" |
| if runtime.GOOS == "darwin" { |
| p7zip = "/Applications/Keka.app/Contents/Resources/keka7z" |
| } |
| cmd := exec.Command(p7zip, "x", path) |
| cmd.Dir = dst |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| os.Stderr.Write(out) |
| return err |
| } |
| return nil |
| } |
| |
| func move(dst, src string, names ...string) error { |
| for _, name := range names { |
| if err := os.Rename(src+"/"+name, dst+"/"+name); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // archiveWriter writes a .tar.gz archive and prints its SHA256 to stdout. |
| // If any error occurs, it continues as a no-op until Close, when it is reported. |
| type archiveWriter struct { |
| name string |
| hashw hash.Hash |
| f *os.File |
| zw *gzip.Writer |
| bw *bufio.Writer |
| tw *tar.Writer |
| err error |
| } |
| |
| func (aw *archiveWriter) WriteHeader(h *tar.Header) { |
| if aw.err != nil { |
| return |
| } |
| aw.err = aw.tw.WriteHeader(h) |
| } |
| |
| func (aw *archiveWriter) Write(b []byte) (n int, err error) { |
| if aw.err != nil { |
| return len(b), nil |
| } |
| n, aw.err = aw.tw.Write(b) |
| return n, nil |
| } |
| |
| func (aw *archiveWriter) Close() (err error) { |
| err = aw.tw.Close() |
| if aw.err == nil { |
| aw.err = err |
| } |
| err = aw.zw.Close() |
| if aw.err == nil { |
| aw.err = err |
| } |
| err = aw.bw.Flush() |
| if aw.err == nil { |
| aw.err = err |
| } |
| err = aw.f.Close() |
| if aw.err == nil { |
| aw.err = err |
| } |
| if aw.err != nil { |
| return aw.err |
| } |
| hash := hex.EncodeToString(aw.hashw.Sum(nil)) |
| fmt.Printf("\t%q: %q,\n", aw.name, hash) |
| return nil |
| } |
| |
| func newArchiveWriter(name string) *archiveWriter { |
| aw := &archiveWriter{name: name} |
| aw.f, aw.err = os.Create(name) |
| if aw.err != nil { |
| return aw |
| } |
| aw.hashw = sha256.New() |
| aw.bw = bufio.NewWriter(io.MultiWriter(aw.f, aw.hashw)) |
| aw.zw, aw.err = gzip.NewWriterLevel(aw.bw, gzip.BestCompression) |
| if aw.err != nil { |
| return aw |
| } |
| aw.tw = tar.NewWriter(aw.zw) |
| return aw |
| } |