2
0
mirror of synced 2025-02-24 15:28:28 +00:00
mobile/cmd/gomobile/release.go
Elias Naur efd7eed289 mobile/cmd/gomobile: upgrade to NDK r12b and add nm
gomobile build uses the nm tool which was missing from the NDK r12
upgrade. I missed it because gomobile bind works without it.

Make release.go include nm and upgrade to NDK r12b to make avoid
name clashes with the already released, but inadequate, NDK r12
files.

Finally, update the cmd/gomobile tests to match the new clang
reality.

Fixes golang/go#16268

Change-Id: Ic0cbf75785baace1fe6e88c8dc72d83ce2e13b35
Reviewed-on: https://go-review.googlesource.com/24724
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
2016-07-11 14:34:23 +00:00

490 lines
11 KiB
Go

// 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"
"flag"
"fmt"
"hash"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
)
const ndkVersion = "ndk-r12b"
type version struct {
os string
arch string
}
var hosts = []version{
{"darwin", "x86_64"},
{"linux", "x86_64"},
{"windows", "x86"},
{"windows", "x86_64"},
}
type target struct {
arch string
platform int
gcc string
toolPrefix string
}
var targets = []target{
{"arm", 15, "arm-linux-androideabi-4.9", "arm-linux-androideabi"},
{"arm64", 21, "aarch64-linux-android-4.9", "aarch64-linux-android"},
{"x86", 15, "x86-4.9", "i686-linux-android"},
{"x86_64", 21, "x86_64-4.9", "x86_64-linux-android"},
}
var (
ndkdir = flag.String("ndkdir", "", "Directory for the downloaded NDKs for caching")
tmpdir string
)
func main() {
flag.Parse()
var err error
tmpdir, err = ioutil.TempDir("", "gomobile-release-")
if err != nil {
log.Panic(err)
}
defer os.RemoveAll(tmpdir)
fmt.Println("var fetchHashes = map[string]string{")
for _, host := range hosts {
if err := mkpkg(host); err != nil {
log.Panic(err)
}
}
if err := mkALPkg(); err != nil {
log.Panic(err)
}
fmt.Println("}")
}
func run(dir, path string, args ...string) error {
cmd := exec.Command(path, args...)
cmd.Dir = dir
buf, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("%s\n", buf)
}
return err
}
func mkALPkg() (err error) {
ndkPath, _, err := fetchNDK(version{os: hostOS, arch: hostArch})
if err != nil {
return err
}
ndkRoot := tmpdir + "/android-" + ndkVersion
if err := inflate(tmpdir, ndkPath); err != nil {
return err
}
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
}
// TODO: use more recent revision?
if err := run(alTmpDir, "git", "checkout", "19f79be57b8e768f44710b6d26017bc1f8c8fbda"); err != nil {
return err
}
files := map[string]string{
"include/AL/al.h": "include/AL/al.h",
"include/AL/alc.h": "include/AL/alc.h",
"COPYING": "include/AL/COPYING",
}
for _, t := range targets {
abi := t.arch
if abi == "arm" {
abi = "armeabi"
}
buildDir := alTmpDir + "/build/" + abi
toolchain := buildDir + "/toolchain"
// standalone ndk toolchains make openal-soft's build config easier.
if err := run(ndkRoot, "env",
"build/tools/make_standalone_toolchain.py",
"--arch="+t.arch,
"--api="+strconv.Itoa(t.platform),
"--install-dir="+toolchain); err != nil {
return fmt.Errorf("make_standalone_toolchain.py failed: %v", err)
}
orgPath := os.Getenv("PATH")
os.Setenv("PATH", toolchain+"/bin"+string(os.PathListSeparator)+orgPath)
if err := run(buildDir, "cmake",
"../../",
"-DCMAKE_TOOLCHAIN_FILE=../../XCompile-Android.txt",
"-DHOST="+t.toolPrefix); err != nil {
return fmt.Errorf("cmake failed: %v", err)
}
os.Setenv("PATH", orgPath)
if err := run(buildDir, "make"); err != nil {
return fmt.Errorf("make failed: %v", err)
}
files["build/"+abi+"/libopenal.so"] = "lib/" + abi + "/libopenal.so"
}
// Build the tarball.
aw := newArchiveWriter("gomobile-openal-soft-1.16.0.1-" + ndkVersion + ".tar.gz")
defer func() {
err2 := aw.Close()
if err == nil {
err = err2
}
}()
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 fetchNDK(host version) (binPath, url string, err error) {
ndkName := "android-" + ndkVersion + "-" + host.os + "-" + host.arch + ".zip"
url = "https://dl.google.com/android/repository/" + ndkName
binPath = *ndkdir
if binPath == "" {
binPath = tmpdir
}
binPath += "/" + ndkName
if _, err := os.Stat(binPath); err == nil {
log.Printf("\t%q: using cached NDK\n", ndkName)
return binPath, url, nil
}
log.Printf("%s\n", url)
binHash, err := fetch(binPath, url)
if err != nil {
return "", "", err
}
fmt.Printf("\t%q: %q,\n", ndkName, binHash)
return binPath, url, nil
}
func mkpkg(host version) error {
binPath, url, err := fetchNDK(host)
if err != nil {
return err
}
src := tmpdir + "/" + host.os + "-" + host.arch + "-src"
dst := tmpdir + "/" + host.os + "-" + host.arch + "-dst"
defer os.RemoveAll(src)
defer os.RemoveAll(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.
for _, t := range targets {
usr := fmt.Sprintf("android-%s/platforms/android-%d/arch-%s/usr/", ndkVersion, t.platform, t.arch)
gcc := fmt.Sprintf("android-%s/toolchains/%s/prebuilt/", ndkVersion, t.gcc)
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+"/bin", 0755); err != nil {
return err
}
subdirs := []string{"include", "lib"}
switch t.arch {
case "x86_64":
subdirs = append(subdirs, "lib64", "libx32")
}
if err := move(dst+"/"+usr, src+"/"+usr, subdirs...); err != nil {
return err
}
if err := move(dst+"/"+gcc, src+"/"+gcc, "lib", "COPYING", "COPYING.LIB"); err != nil {
return err
}
for _, exe := range []string{"as", "ld", "nm"} {
if host.os == "windows" {
exe += ".exe"
}
if err := move(dst+"/"+gcc+"/bin", src+"/"+gcc+"/bin", t.toolPrefix+"-"+exe); err != nil {
return err
}
}
}
// Copy the LLVM clang and clang++ compilers
llvm := fmt.Sprintf("android-%s/toolchains/llvm/prebuilt/", ndkVersion)
if host.os == "windows" && host.arch == "x86" {
llvm += "windows"
} else {
llvm += host.os + "-" + host.arch
}
if err := os.MkdirAll(dst+"/"+llvm, 0755); err != nil {
return err
}
if err := move(dst+"/"+llvm, src+"/"+llvm, "bin", "lib64", "NOTICE"); 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
}
if sc := resp.StatusCode; sc != http.StatusOK {
return "", fmt.Errorf("invalid HTTP status %d", sc)
}
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 {
unzip := "unzip"
cmd := exec.Command(unzip, 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
}
var hostOS, hostArch string
func init() {
switch runtime.GOOS {
case "linux", "darwin":
hostOS = runtime.GOOS
}
switch runtime.GOARCH {
case "386":
hostArch = "x86"
case "amd64":
hostArch = "x86_64"
}
if hostOS == "" || hostArch == "" {
panic(fmt.Sprintf("cannot run release from OS/Arch: %s/%s", hostOS, hostArch))
}
}