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 }