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  }