mirror of
https://github.com/golang/go
synced 2024-11-11 21:10:21 -07:00
misc/ios: add go_darwin_arm_exec script
This script is getting very close to complete, and is complex enough that I'd like to get what's there so far reviewed. With it the builder is left failing on eight packages. Two of those involve correcting GOROOT which may need modifications to this script, the others are either a unix sockets bug I have to hunt down or are caused by lldb getting stuck on SIGSEGV, a TODO. Change-Id: I5ff933800167b6764b51ad195da7dcda61d59ff8 Reviewed-on: https://go-review.googlesource.com/6404 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
parent
8974fb9ba5
commit
d460b6e5c6
505
misc/ios/go_darwin_arm_exec.go
Normal file
505
misc/ios/go_darwin_arm_exec.go
Normal file
@ -0,0 +1,505 @@
|
||||
// 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.
|
||||
|
||||
// This program can be used as go_darwin_arm_exec by the Go tool.
|
||||
// It executes binaries on an iOS device using the XCode toolchain
|
||||
// and the ios-deploy program: https://github.com/phonegap/ios-deploy
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("go_darwin_arm_exec: ")
|
||||
if debug {
|
||||
log.Println(strings.Join(os.Args, " "))
|
||||
}
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatal("usage: go_darwin_arm_exec a.out")
|
||||
}
|
||||
|
||||
if err := run(os.Args[1], os.Args[2:]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "go_darwin_arm_exec: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(bin string, args []string) error {
|
||||
defer exec.Command("killall", "ios-deploy").Run() // cleanup
|
||||
|
||||
exec.Command("killall", "ios-deploy").Run()
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "go_darwin_arm_exec_")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
appdir := filepath.Join(tmpdir, "gotest.app")
|
||||
if err := os.MkdirAll(appdir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cp(filepath.Join(appdir, "gotest"), bin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist")
|
||||
if err := ioutil.WriteFile(entitlementsPath, []byte(entitlementsPlist), 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist), 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkgpath, err := copyLocalData(appdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command(
|
||||
"codesign",
|
||||
"-f",
|
||||
"-s", "E8BMC3FE2Z", // certificate associated with golang.org
|
||||
"--entitlements", entitlementsPath,
|
||||
appdir,
|
||||
)
|
||||
if debug {
|
||||
log.Println(strings.Join(cmd.Args, " "))
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("codesign: %v", err)
|
||||
}
|
||||
|
||||
if err := os.Chdir(tmpdir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ios-deploy invokes lldb to give us a shell session with the app.
|
||||
cmd = exec.Command(
|
||||
// lldb tries to be clever with terminals.
|
||||
// So we wrap it in script(1) and be clever
|
||||
// right back at it.
|
||||
"script",
|
||||
"-q", "-t", "0",
|
||||
"/dev/null",
|
||||
|
||||
"ios-deploy",
|
||||
"--debug",
|
||||
"-u",
|
||||
"-r",
|
||||
"-n",
|
||||
`--args=`+strings.Join(args, " ")+``,
|
||||
"--bundle", appdir,
|
||||
)
|
||||
if debug {
|
||||
log.Println(strings.Join(cmd.Args, " "))
|
||||
}
|
||||
|
||||
lldbr, lldb, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := new(bufWriter)
|
||||
cmd.Stdout = w
|
||||
cmd.Stderr = w // everything of interest is on stderr
|
||||
cmd.Stdin = lldbr
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("ios-deploy failed to start: %v", err)
|
||||
}
|
||||
|
||||
// Manage the -test.timeout here, outside of the test. There is a lot
|
||||
// of moving parts in an iOS test harness (notably lldb) that can
|
||||
// swallow useful stdio or cause its own ruckus.
|
||||
var timedout chan struct{}
|
||||
if t := parseTimeout(args); t > 1*time.Second {
|
||||
timedout = make(chan struct{})
|
||||
time.AfterFunc(t-1*time.Second, func() {
|
||||
close(timedout)
|
||||
})
|
||||
}
|
||||
|
||||
exited := make(chan error)
|
||||
go func() {
|
||||
exited <- cmd.Wait()
|
||||
}()
|
||||
|
||||
waitFor := func(stage, str string) error {
|
||||
select {
|
||||
case <-timedout:
|
||||
w.printBuf()
|
||||
if p := cmd.Process; p != nil {
|
||||
p.Kill()
|
||||
}
|
||||
return fmt.Errorf("timeout (stage %s)", stage)
|
||||
case err := <-exited:
|
||||
w.printBuf()
|
||||
return fmt.Errorf("failed (stage %s): %v", stage, err)
|
||||
case i := <-w.find(str):
|
||||
w.clearTo(i + len(str))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
do := func(cmd string) {
|
||||
fmt.Fprintln(lldb, cmd)
|
||||
}
|
||||
|
||||
// Wait for installation and connection.
|
||||
if err := waitFor("ios-deploy before run", "(lldb) connect\r\nProcess 0 connected\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Script LLDB. Oh dear.
|
||||
do(`process handle SIGHUP --stop false --pass true --notify false`)
|
||||
do(`process handle SIGPIPE --stop false --pass true --notify false`)
|
||||
do(`process handle SIGUSR1 --stop false --pass true --notify false`)
|
||||
do(`process handle SIGSEGV --stop false --pass true --notify false`) // does not work
|
||||
do(`process handle SIGBUS --stop false --pass true --notify false`) // does not work
|
||||
|
||||
do(`breakpoint set -n getwd`) // in runtime/cgo/gcc_darwin_arm.go
|
||||
do(`run`)
|
||||
if err := waitFor("br getwd", "stop reason = breakpoint"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := waitFor("br getwd prompt", "(lldb)"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move the current working directory into the faux gopath.
|
||||
do(`breakpoint delete 1`)
|
||||
do(`expr char* $mem = (char*)malloc(512)`)
|
||||
do(`expr $mem = (char*)getwd($mem, 512)`)
|
||||
do(`expr $mem = (char*)strcat($mem, "/` + pkgpath + `")`)
|
||||
do(`expr int $res = (int)chdir($mem)`)
|
||||
do(`print $res`)
|
||||
if err := waitFor("move working dir", "(int) $res = 0"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Watch for SIGSEGV. Ideally lldb would never break on SIGSEGV.
|
||||
// http://golang.org/issue/10043
|
||||
go func() {
|
||||
<-w.find("stop reason = EXC_BAD_ACCESS")
|
||||
do(`bt`)
|
||||
// The backtrace has no obvious end, so we invent one.
|
||||
do(`expr int $dummy = 1`)
|
||||
do(`print $dummy`)
|
||||
<-w.find(`(int) $dummy = 1`)
|
||||
w.printBuf()
|
||||
if p := cmd.Process; p != nil {
|
||||
p.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
// Run the tests.
|
||||
w.trimSuffix("(lldb) ")
|
||||
do(`process continue`)
|
||||
|
||||
// Wait for the test to complete.
|
||||
select {
|
||||
case <-timedout:
|
||||
w.printBuf()
|
||||
if p := cmd.Process; p != nil {
|
||||
p.Kill()
|
||||
}
|
||||
return errors.New("timeout running tests")
|
||||
case err := <-exited:
|
||||
// The returned lldb error code is usually non-zero.
|
||||
// We check for test success by scanning for the final
|
||||
// PASS returned by the test harness, assuming the worst
|
||||
// in its absence.
|
||||
if w.isPass() {
|
||||
err = nil
|
||||
} else if err == nil {
|
||||
err = errors.New("test failure")
|
||||
}
|
||||
w.printBuf()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
type bufWriter struct {
|
||||
mu sync.Mutex
|
||||
buf []byte
|
||||
suffix []byte // remove from each Write
|
||||
|
||||
findTxt []byte // search buffer on each Write
|
||||
findCh chan int // report find position
|
||||
}
|
||||
|
||||
func (w *bufWriter) Write(in []byte) (n int, err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
n = len(in)
|
||||
in = bytes.TrimSuffix(in, w.suffix)
|
||||
|
||||
w.buf = append(w.buf, in...)
|
||||
|
||||
if len(w.findTxt) > 0 {
|
||||
if i := bytes.Index(w.buf, w.findTxt); i >= 0 {
|
||||
w.findCh <- i
|
||||
close(w.findCh)
|
||||
w.findTxt = nil
|
||||
w.findCh = nil
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *bufWriter) trimSuffix(p string) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.suffix = []byte(p)
|
||||
}
|
||||
|
||||
func (w *bufWriter) printBuf() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
fmt.Fprintf(os.Stderr, "%s", w.buf)
|
||||
w.buf = nil
|
||||
}
|
||||
|
||||
func (w *bufWriter) clearTo(i int) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "--- go_darwin_arm_exec clear ---\n%s\n--- go_darwin_arm_exec clear ---\n", w.buf[:i])
|
||||
}
|
||||
w.buf = w.buf[i:]
|
||||
}
|
||||
|
||||
func (w *bufWriter) find(str string) <-chan int {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if len(w.findTxt) > 0 {
|
||||
panic(fmt.Sprintf("find(%s): already trying to find %s", str, w.findTxt))
|
||||
}
|
||||
txt := []byte(str)
|
||||
ch := make(chan int, 1)
|
||||
if i := bytes.Index(w.buf, txt); i >= 0 {
|
||||
ch <- i
|
||||
close(ch)
|
||||
} else {
|
||||
w.findTxt = txt
|
||||
w.findCh = ch
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func (w *bufWriter) isPass() bool {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
// The final stdio of lldb is non-deterministic, so we
|
||||
// scan the whole buffer.
|
||||
//
|
||||
// Just to make things fun, lldb sometimes translates \n
|
||||
// into \r\n.
|
||||
return bytes.Contains(w.buf, []byte("\nPASS\n")) || bytes.Contains(w.buf, []byte("\nPASS\r"))
|
||||
}
|
||||
|
||||
func parseTimeout(testArgs []string) (timeout time.Duration) {
|
||||
var args []string
|
||||
for _, arg := range testArgs {
|
||||
if strings.Contains(arg, "test.timeout") {
|
||||
args = append(args, arg)
|
||||
}
|
||||
}
|
||||
f := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
f.DurationVar(&timeout, "test.timeout", 0, "")
|
||||
f.Parse(args)
|
||||
if debug {
|
||||
log.Printf("parseTimeout of %s, got %s", args, timeout)
|
||||
}
|
||||
return timeout
|
||||
}
|
||||
|
||||
func copyLocalDir(dst, src string) error {
|
||||
if err := os.Mkdir(dst, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
fi, err := d.Readdir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range fi {
|
||||
if f.IsDir() {
|
||||
if f.Name() == "testdata" {
|
||||
if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cp(dst, src string) error {
|
||||
out, err := exec.Command("cp", "-a", src, dst).CombinedOutput()
|
||||
if err != nil {
|
||||
os.Stderr.Write(out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func copyLocalData(dstbase string) (pkgpath string, err error) {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
finalPkgpath, underGoRoot, err := subdir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cwd = strings.TrimSuffix(cwd, finalPkgpath)
|
||||
|
||||
// Copy all immediate files and testdata directories between
|
||||
// the package being tested and the source root.
|
||||
pkgpath = ""
|
||||
for _, element := range strings.Split(finalPkgpath, string(filepath.Separator)) {
|
||||
if debug {
|
||||
log.Printf("copying %s", pkgpath)
|
||||
}
|
||||
pkgpath = filepath.Join(pkgpath, element)
|
||||
dst := filepath.Join(dstbase, pkgpath)
|
||||
src := filepath.Join(cwd, pkgpath)
|
||||
if err := copyLocalDir(dst, src); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Copy timezone file.
|
||||
if underGoRoot {
|
||||
dst := filepath.Join(dstbase, "lib", "time")
|
||||
os.MkdirAll(dst, 0755)
|
||||
if err := cp(dst, filepath.Join(cwd, "lib", "time", "zoneinfo.zip")); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return finalPkgpath, nil
|
||||
}
|
||||
|
||||
// subdir determines the package based on the current working directory,
|
||||
// and returns the path to the package source relative to $GOROOT (or $GOPATH).
|
||||
func subdir() (pkgpath string, underGoRoot bool, err error) {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if root := runtime.GOROOT(); strings.HasPrefix(cwd, root) {
|
||||
subdir, err := filepath.Rel(root, cwd)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return subdir, true, nil
|
||||
}
|
||||
|
||||
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
||||
if !strings.HasPrefix(cwd, p) {
|
||||
continue
|
||||
}
|
||||
subdir, err := filepath.Rel(p, cwd)
|
||||
if err == nil {
|
||||
return subdir, false, nil
|
||||
}
|
||||
}
|
||||
return "", false, fmt.Errorf(
|
||||
"working directory %q is not in either GOROOT(%q) or GOPATH(%q)",
|
||||
cwd,
|
||||
runtime.GOROOT(),
|
||||
build.Default.GOPATH,
|
||||
)
|
||||
}
|
||||
|
||||
const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key><string>golang.gotest</string>
|
||||
<key>CFBundleSupportedPlatforms</key><array><string>iPhoneOS</string></array>
|
||||
<key>CFBundleExecutable</key><string>gotest</string>
|
||||
<key>CFBundleVersion</key><string>1.0</string>
|
||||
<key>CFBundleIdentifier</key><string>golang.gotest</string>
|
||||
<key>CFBundleResourceSpecification</key><string>ResourceRules.plist</string>
|
||||
<key>LSRequiresIPhoneOS</key><true/>
|
||||
<key>CFBundleDisplayName</key><string>gotest</string>
|
||||
</dict>
|
||||
</plist>
|
||||
`
|
||||
|
||||
const devID = `YE84DJ86AZ`
|
||||
|
||||
const entitlementsPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>keychain-access-groups</key>
|
||||
<array><string>` + devID + `.golang.gotest</string></array>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
<key>application-identifier</key>
|
||||
<string>` + devID + `.golang.gotest</string>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>` + devID + `</string>
|
||||
</dict>
|
||||
</plist>`
|
||||
|
||||
const resourceRules = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>.*</key><true/>
|
||||
<key>Info.plist</key>
|
||||
<dict>
|
||||
<key>omit</key> <true/>
|
||||
<key>weight</key> <real>10</real>
|
||||
</dict>
|
||||
<key>ResourceRules.plist</key>
|
||||
<dict>
|
||||
<key>omit</key> <true/>
|
||||
<key>weight</key> <real>100</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
`
|
Loading…
Reference in New Issue
Block a user