github.com/c-darwin/mobile@v0.0.0-20160313183840-ff625c46f7c9/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  var tmpdir string
    48  
    49  func main() {
    50  	var err error
    51  	tmpdir, err = ioutil.TempDir("", "gomobile-release-")
    52  	if err != nil {
    53  		log.Fatal(err)
    54  	}
    55  	defer os.RemoveAll(tmpdir)
    56  
    57  	fmt.Println("var fetchHashes = map[string]string{")
    58  
    59  	for _, host := range hosts {
    60  		if err := mkpkg(host); err != nil {
    61  			log.Fatal(err)
    62  		}
    63  	}
    64  
    65  	if err := mkALPkg(); err != nil {
    66  		log.Fatal(err)
    67  	}
    68  
    69  	fmt.Println("}")
    70  }
    71  
    72  func run(dir, path string, args ...string) error {
    73  	cmd := exec.Command(path, args...)
    74  	cmd.Dir = dir
    75  	cmd.Stdout = os.Stdout
    76  	cmd.Stderr = os.Stderr
    77  	return cmd.Run()
    78  }
    79  
    80  func mkALPkg() (err error) {
    81  	alTmpDir, err := ioutil.TempDir("", "openal-release-")
    82  	if err != nil {
    83  		return err
    84  	}
    85  	defer os.RemoveAll(alTmpDir)
    86  
    87  	if err := run(alTmpDir, "git", "clone", "-v", "git://repo.or.cz/openal-soft.git", alTmpDir); err != nil {
    88  		return err
    89  	}
    90  	if err := run(alTmpDir, "git", "checkout", "19f79be57b8e768f44710b6d26017bc1f8c8fbda"); err != nil {
    91  		return err
    92  	}
    93  	if err := run(filepath.Join(alTmpDir, "cmake"), "cmake", "..", "-DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt", "-DHOST=arm-linux-androideabi"); err != nil {
    94  		return err
    95  	}
    96  	if err := run(filepath.Join(alTmpDir, "cmake"), "make"); err != nil {
    97  		return err
    98  	}
    99  
   100  	// Build the tarball.
   101  	aw := newArchiveWriter("gomobile-openal-soft-1.16.0.1.tar.gz")
   102  	defer func() {
   103  		err2 := aw.Close()
   104  		if err == nil {
   105  			err = err2
   106  		}
   107  	}()
   108  
   109  	files := map[string]string{
   110  		"cmake/libopenal.so": "lib/armeabi/libopenal.so",
   111  		"include/AL/al.h":    "include/AL/al.h",
   112  		"include/AL/alc.h":   "include/AL/alc.h",
   113  		"COPYING":            "include/AL/COPYING",
   114  	}
   115  	for src, dst := range files {
   116  		f, err := os.Open(filepath.Join(alTmpDir, src))
   117  		if err != nil {
   118  			return err
   119  		}
   120  		fi, err := f.Stat()
   121  		if err != nil {
   122  			return err
   123  		}
   124  		aw.WriteHeader(&tar.Header{
   125  			Name: dst,
   126  			Mode: int64(fi.Mode()),
   127  			Size: fi.Size(),
   128  		})
   129  		io.Copy(aw, f)
   130  		f.Close()
   131  	}
   132  	return nil
   133  }
   134  
   135  func mkpkg(host version) (err error) {
   136  	ndkName := "android-" + ndkVersion + "-" + host.os + "-" + host.arch + "."
   137  	if host.os == "windows" {
   138  		ndkName += "exe"
   139  	} else {
   140  		ndkName += "bin"
   141  	}
   142  	url := "http://dl.google.com/android/ndk/" + ndkName
   143  	log.Printf("%s\n", url)
   144  	binPath := tmpdir + "/" + ndkName
   145  	binHash, err := fetch(binPath, url)
   146  	if err != nil {
   147  		log.Fatal(err)
   148  	}
   149  
   150  	fmt.Printf("\t%q: %q,\n", ndkName, binHash)
   151  
   152  	src := tmpdir + "/" + host.os + "-" + host.arch + "-src"
   153  	dst := tmpdir + "/" + host.os + "-" + host.arch + "-dst"
   154  	if err := os.Mkdir(src, 0755); err != nil {
   155  		return err
   156  	}
   157  	if err := inflate(src, binPath); err != nil {
   158  		return err
   159  	}
   160  
   161  	// The NDK is unpacked into tmpdir/linux-x86_64-src/android-{{ndkVersion}}.
   162  	// Move the files we want into tmpdir/linux-x86_64-dst/android-{{ndkVersion}}.
   163  	// We preserve the same file layout to make the full NDK interchangable
   164  	// with the cut down file.
   165  	usr := "android-" + ndkVersion + "/platforms/android-15/arch-arm/usr"
   166  	gcc := "android-" + ndkVersion + "/toolchains/arm-linux-androideabi-4.8/prebuilt/"
   167  	if host.os == "windows" && host.arch == "x86" {
   168  		gcc += "windows"
   169  	} else {
   170  		gcc += host.os + "-" + host.arch
   171  	}
   172  
   173  	if err := os.MkdirAll(dst+"/"+usr, 0755); err != nil {
   174  		return err
   175  	}
   176  	if err := os.MkdirAll(dst+"/"+gcc, 0755); err != nil {
   177  		return err
   178  	}
   179  	if err := move(dst+"/"+usr, src+"/"+usr, "include", "lib"); err != nil {
   180  		return err
   181  	}
   182  	if err := move(dst+"/"+gcc, src+"/"+gcc, "bin", "lib", "libexec", "COPYING", "COPYING.LIB"); err != nil {
   183  		return err
   184  	}
   185  
   186  	// Build the tarball.
   187  	aw := newArchiveWriter("gomobile-" + ndkVersion + "-" + host.os + "-" + host.arch + ".tar.gz")
   188  	defer func() {
   189  		err2 := aw.Close()
   190  		if err == nil {
   191  			err = err2
   192  		}
   193  	}()
   194  
   195  	readme := "Stripped down copy of:\n\n\t" + url + "\n\nGenerated by github.com/c-darwin/mobile/cmd/gomobile/release.go."
   196  	aw.WriteHeader(&tar.Header{
   197  		Name: "README",
   198  		Mode: 0644,
   199  		Size: int64(len(readme)),
   200  	})
   201  	io.WriteString(aw, readme)
   202  
   203  	return filepath.Walk(dst, func(path string, fi os.FileInfo, err error) error {
   204  		defer func() {
   205  			if err != nil {
   206  				err = fmt.Errorf("%s: %v", path, err)
   207  			}
   208  		}()
   209  		if err != nil {
   210  			return err
   211  		}
   212  		if path == dst {
   213  			return nil
   214  		}
   215  		name := path[len(dst)+1:]
   216  		if fi.IsDir() {
   217  			return nil
   218  		}
   219  		if fi.Mode()&os.ModeSymlink != 0 {
   220  			dst, err := os.Readlink(path)
   221  			if err != nil {
   222  				log.Printf("bad symlink: %s", name)
   223  				return nil
   224  			}
   225  			aw.WriteHeader(&tar.Header{
   226  				Name:     name,
   227  				Linkname: dst,
   228  				Typeflag: tar.TypeSymlink,
   229  			})
   230  			return nil
   231  		}
   232  		aw.WriteHeader(&tar.Header{
   233  			Name: name,
   234  			Mode: int64(fi.Mode()),
   235  			Size: fi.Size(),
   236  		})
   237  		f, err := os.Open(path)
   238  		if err != nil {
   239  			return err
   240  		}
   241  		io.Copy(aw, f)
   242  		f.Close()
   243  		return nil
   244  	})
   245  }
   246  
   247  func fetch(dst, url string) (string, error) {
   248  	f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0755)
   249  	if err != nil {
   250  		return "", err
   251  	}
   252  	resp, err := http.Get(url)
   253  	if err != nil {
   254  		return "", err
   255  	}
   256  	hashw := sha256.New()
   257  	_, err = io.Copy(io.MultiWriter(hashw, f), resp.Body)
   258  	err2 := resp.Body.Close()
   259  	err3 := f.Close()
   260  	if err != nil {
   261  		return "", err
   262  	}
   263  	if err2 != nil {
   264  		return "", err2
   265  	}
   266  	if err3 != nil {
   267  		return "", err3
   268  	}
   269  	return hex.EncodeToString(hashw.Sum(nil)), nil
   270  }
   271  
   272  func inflate(dst, path string) error {
   273  	p7zip := "7z"
   274  	if runtime.GOOS == "darwin" {
   275  		p7zip = "/Applications/Keka.app/Contents/Resources/keka7z"
   276  	}
   277  	cmd := exec.Command(p7zip, "x", path)
   278  	cmd.Dir = dst
   279  	out, err := cmd.CombinedOutput()
   280  	if err != nil {
   281  		os.Stderr.Write(out)
   282  		return err
   283  	}
   284  	return nil
   285  }
   286  
   287  func move(dst, src string, names ...string) error {
   288  	for _, name := range names {
   289  		if err := os.Rename(src+"/"+name, dst+"/"+name); err != nil {
   290  			return err
   291  		}
   292  	}
   293  	return nil
   294  }
   295  
   296  // archiveWriter writes a .tar.gz archive and prints its SHA256 to stdout.
   297  // If any error occurs, it continues as a no-op until Close, when it is reported.
   298  type archiveWriter struct {
   299  	name  string
   300  	hashw hash.Hash
   301  	f     *os.File
   302  	zw    *gzip.Writer
   303  	bw    *bufio.Writer
   304  	tw    *tar.Writer
   305  	err   error
   306  }
   307  
   308  func (aw *archiveWriter) WriteHeader(h *tar.Header) {
   309  	if aw.err != nil {
   310  		return
   311  	}
   312  	aw.err = aw.tw.WriteHeader(h)
   313  }
   314  
   315  func (aw *archiveWriter) Write(b []byte) (n int, err error) {
   316  	if aw.err != nil {
   317  		return len(b), nil
   318  	}
   319  	n, aw.err = aw.tw.Write(b)
   320  	return n, nil
   321  }
   322  
   323  func (aw *archiveWriter) Close() (err error) {
   324  	err = aw.tw.Close()
   325  	if aw.err == nil {
   326  		aw.err = err
   327  	}
   328  	err = aw.zw.Close()
   329  	if aw.err == nil {
   330  		aw.err = err
   331  	}
   332  	err = aw.bw.Flush()
   333  	if aw.err == nil {
   334  		aw.err = err
   335  	}
   336  	err = aw.f.Close()
   337  	if aw.err == nil {
   338  		aw.err = err
   339  	}
   340  	if aw.err != nil {
   341  		return aw.err
   342  	}
   343  	hash := hex.EncodeToString(aw.hashw.Sum(nil))
   344  	fmt.Printf("\t%q: %q,\n", aw.name, hash)
   345  	return nil
   346  }
   347  
   348  func newArchiveWriter(name string) *archiveWriter {
   349  	aw := &archiveWriter{name: name}
   350  	aw.f, aw.err = os.Create(name)
   351  	if aw.err != nil {
   352  		return aw
   353  	}
   354  	aw.hashw = sha256.New()
   355  	aw.bw = bufio.NewWriter(io.MultiWriter(aw.f, aw.hashw))
   356  	aw.zw, aw.err = gzip.NewWriterLevel(aw.bw, gzip.BestCompression)
   357  	if aw.err != nil {
   358  		return aw
   359  	}
   360  	aw.tw = tar.NewWriter(aw.zw)
   361  	return aw
   362  }