| // 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_ios_$GOARCH_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 | |
| // | |
| // This script supports an extra flag, -lldb, that pauses execution | |
| // just before the main program begins and allows the user to control | |
| // the remote lldb session. This flag is appended to the end of the | |
| // script's arguments and is not passed through to the underlying | |
| // binary. | |
| // | |
| // This script requires that three environment variables be set: | |
| // GOIOS_DEV_ID: The codesigning developer id or certificate identifier | |
| // GOIOS_APP_ID: The provisioning app id prefix. Must support wildcard app ids. | |
| // GOIOS_TEAM_ID: The team id that owns the app id prefix. | |
| // $GOROOT/misc/ios contains a script, detect.go, that attempts to autodetect these. | |
| package main | |
| import ( | |
| "bytes" | |
| "encoding/xml" | |
| "errors" | |
| "fmt" | |
| "go/build" | |
| "io" | |
| "io/ioutil" | |
| "log" | |
| "net" | |
| "os" | |
| "os/exec" | |
| "os/signal" | |
| "path/filepath" | |
| "runtime" | |
| "strconv" | |
| "strings" | |
| "syscall" | |
| "time" | |
| ) | |
| const debug = false | |
| var tmpdir string | |
| var ( | |
| devID string | |
| appID string | |
| teamID string | |
| bundleID string | |
| deviceID string | |
| ) | |
| // lock is a file lock to serialize iOS runs. It is global to avoid the | |
| // garbage collector finalizing it, closing the file and releasing the | |
| // lock prematurely. | |
| var lock *os.File | |
| func main() { | |
| log.SetFlags(0) | |
| log.SetPrefix("go_ios_exec: ") | |
| if debug { | |
| log.Println(strings.Join(os.Args, " ")) | |
| } | |
| if len(os.Args) < 2 { | |
| log.Fatal("usage: go_ios_exec a.out") | |
| } | |
| // For compatibility with the old builders, use a fallback bundle ID | |
| bundleID = "golang.gotest" | |
| exitCode, err := runMain() | |
| if err != nil { | |
| log.Fatalf("%v\n", err) | |
| } | |
| os.Exit(exitCode) | |
| } | |
| func runMain() (int, error) { | |
| var err error | |
| tmpdir, err = ioutil.TempDir("", "go_ios_exec_") | |
| if err != nil { | |
| return 1, err | |
| } | |
| if !debug { | |
| defer os.RemoveAll(tmpdir) | |
| } | |
| appdir := filepath.Join(tmpdir, "gotest.app") | |
| os.RemoveAll(appdir) | |
| if err := assembleApp(appdir, os.Args[1]); err != nil { | |
| return 1, err | |
| } | |
| // This wrapper uses complicated machinery to run iOS binaries. It | |
| // works, but only when running one binary at a time. | |
| // Use a file lock to make sure only one wrapper is running at a time. | |
| // | |
| // The lock file is never deleted, to avoid concurrent locks on distinct | |
| // files with the same path. | |
| lockName := filepath.Join(os.TempDir(), "go_ios_exec-"+deviceID+".lock") | |
| lock, err = os.OpenFile(lockName, os.O_CREATE|os.O_RDONLY, 0666) | |
| if err != nil { | |
| return 1, err | |
| } | |
| if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil { | |
| return 1, err | |
| } | |
| if goarch := os.Getenv("GOARCH"); goarch == "arm64" { | |
| err = runOnDevice(appdir) | |
| } else { | |
| err = runOnSimulator(appdir) | |
| } | |
| if err != nil { | |
| // If the lldb driver completed with an exit code, use that. | |
| if err, ok := err.(*exec.ExitError); ok { | |
| if ws, ok := err.Sys().(interface{ ExitStatus() int }); ok { | |
| return ws.ExitStatus(), nil | |
| } | |
| } | |
| return 1, err | |
| } | |
| return 0, nil | |
| } | |
| func runOnSimulator(appdir string) error { | |
| if err := installSimulator(appdir); err != nil { | |
| return err | |
| } | |
| return runSimulator(appdir, bundleID, os.Args[2:]) | |
| } | |
| func runOnDevice(appdir string) error { | |
| // e.g. B393DDEB490947F5A463FD074299B6C0AXXXXXXX | |
| devID = getenv("GOIOS_DEV_ID") | |
| // e.g. Z8B3JBXXXX.org.golang.sample, Z8B3JBXXXX prefix is available at | |
| // https://developer.apple.com/membercenter/index.action#accountSummary as Team ID. | |
| appID = getenv("GOIOS_APP_ID") | |
| // e.g. Z8B3JBXXXX, available at | |
| // https://developer.apple.com/membercenter/index.action#accountSummary as Team ID. | |
| teamID = getenv("GOIOS_TEAM_ID") | |
| // Device IDs as listed with ios-deploy -c. | |
| deviceID = os.Getenv("GOIOS_DEVICE_ID") | |
| parts := strings.SplitN(appID, ".", 2) | |
| if len(parts) == 2 { | |
| bundleID = parts[1] | |
| } | |
| if err := signApp(appdir); err != nil { | |
| return err | |
| } | |
| if err := uninstallDevice(bundleID); err != nil { | |
| return err | |
| } | |
| if err := installDevice(appdir); err != nil { | |
| return err | |
| } | |
| if err := mountDevImage(); err != nil { | |
| return err | |
| } | |
| // Kill any hanging debug bridges that might take up port 3222. | |
| exec.Command("killall", "idevicedebugserverproxy").Run() | |
| closer, err := startDebugBridge() | |
| if err != nil { | |
| return err | |
| } | |
| defer closer() | |
| return runDevice(appdir, bundleID, os.Args[2:]) | |
| } | |
| func getenv(envvar string) string { | |
| s := os.Getenv(envvar) | |
| if s == "" { | |
| log.Fatalf("%s not set\nrun $GOROOT/misc/ios/detect.go to attempt to autodetect", envvar) | |
| } | |
| return s | |
| } | |
| func assembleApp(appdir, bin string) error { | |
| if err := os.MkdirAll(appdir, 0755); err != nil { | |
| return err | |
| } | |
| if err := cp(filepath.Join(appdir, "gotest"), bin); err != nil { | |
| return err | |
| } | |
| pkgpath, err := copyLocalData(appdir) | |
| if 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(pkgpath)), 0744); err != nil { | |
| return err | |
| } | |
| if err := ioutil.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil { | |
| return err | |
| } | |
| return nil | |
| } | |
| func signApp(appdir string) error { | |
| entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist") | |
| cmd := exec.Command( | |
| "codesign", | |
| "-f", | |
| "-s", devID, | |
| "--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) | |
| } | |
| return nil | |
| } | |
| // mountDevImage ensures a developer image is mounted on the device. | |
| // The image contains the device lldb server for idevicedebugserverproxy | |
| // to connect to. | |
| func mountDevImage() error { | |
| // Check for existing mount. | |
| cmd := idevCmd(exec.Command("ideviceimagemounter", "-l", "-x")) | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| os.Stderr.Write(out) | |
| return fmt.Errorf("ideviceimagemounter: %v", err) | |
| } | |
| var info struct { | |
| Dict struct { | |
| Data []byte `xml:",innerxml"` | |
| } `xml:"dict"` | |
| } | |
| if err := xml.Unmarshal(out, &info); err != nil { | |
| return fmt.Errorf("mountDevImage: failed to decode mount information: %v", err) | |
| } | |
| dict, err := parsePlistDict(info.Dict.Data) | |
| if err != nil { | |
| return fmt.Errorf("mountDevImage: failed to parse mount information: %v", err) | |
| } | |
| if dict["ImagePresent"] == "true" && dict["Status"] == "Complete" { | |
| return nil | |
| } | |
| // Some devices only give us an ImageSignature key. | |
| if _, exists := dict["ImageSignature"]; exists { | |
| return nil | |
| } | |
| // No image is mounted. Find a suitable image. | |
| imgPath, err := findDevImage() | |
| if err != nil { | |
| return err | |
| } | |
| sigPath := imgPath + ".signature" | |
| cmd = idevCmd(exec.Command("ideviceimagemounter", imgPath, sigPath)) | |
| if out, err := cmd.CombinedOutput(); err != nil { | |
| os.Stderr.Write(out) | |
| return fmt.Errorf("ideviceimagemounter: %v", err) | |
| } | |
| return nil | |
| } | |
| // findDevImage use the device iOS version and build to locate a suitable | |
| // developer image. | |
| func findDevImage() (string, error) { | |
| cmd := idevCmd(exec.Command("ideviceinfo")) | |
| out, err := cmd.Output() | |
| if err != nil { | |
| return "", fmt.Errorf("ideviceinfo: %v", err) | |
| } | |
| var iosVer, buildVer string | |
| lines := bytes.Split(out, []byte("\n")) | |
| for _, line := range lines { | |
| spl := bytes.SplitN(line, []byte(": "), 2) | |
| if len(spl) != 2 { | |
| continue | |
| } | |
| key, val := string(spl[0]), string(spl[1]) | |
| switch key { | |
| case "ProductVersion": | |
| iosVer = val | |
| case "BuildVersion": | |
| buildVer = val | |
| } | |
| } | |
| if iosVer == "" || buildVer == "" { | |
| return "", errors.New("failed to parse ideviceinfo output") | |
| } | |
| verSplit := strings.Split(iosVer, ".") | |
| if len(verSplit) > 2 { | |
| // Developer images are specific to major.minor ios version. | |
| // Cut off the patch version. | |
| iosVer = strings.Join(verSplit[:2], ".") | |
| } | |
| sdkBase := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport" | |
| patterns := []string{fmt.Sprintf("%s (%s)", iosVer, buildVer), fmt.Sprintf("%s (*)", iosVer), fmt.Sprintf("%s*", iosVer)} | |
| for _, pattern := range patterns { | |
| matches, err := filepath.Glob(filepath.Join(sdkBase, pattern, "DeveloperDiskImage.dmg")) | |
| if err != nil { | |
| return "", fmt.Errorf("findDevImage: %v", err) | |
| } | |
| if len(matches) > 0 { | |
| return matches[0], nil | |
| } | |
| } | |
| return "", fmt.Errorf("failed to find matching developer image for iOS version %s build %s", iosVer, buildVer) | |
| } | |
| // startDebugBridge ensures that the idevicedebugserverproxy runs on | |
| // port 3222. | |
| func startDebugBridge() (func(), error) { | |
| errChan := make(chan error, 1) | |
| cmd := idevCmd(exec.Command("idevicedebugserverproxy", "3222")) | |
| var stderr bytes.Buffer | |
| cmd.Stderr = &stderr | |
| if err := cmd.Start(); err != nil { | |
| return nil, fmt.Errorf("idevicedebugserverproxy: %v", err) | |
| } | |
| go func() { | |
| if err := cmd.Wait(); err != nil { | |
| if _, ok := err.(*exec.ExitError); ok { | |
| errChan <- fmt.Errorf("idevicedebugserverproxy: %s", stderr.Bytes()) | |
| } else { | |
| errChan <- fmt.Errorf("idevicedebugserverproxy: %v", err) | |
| } | |
| } | |
| errChan <- nil | |
| }() | |
| closer := func() { | |
| cmd.Process.Kill() | |
| <-errChan | |
| } | |
| // Dial localhost:3222 to ensure the proxy is ready. | |
| delay := time.Second / 4 | |
| for attempt := 0; attempt < 5; attempt++ { | |
| conn, err := net.DialTimeout("tcp", "localhost:3222", 5*time.Second) | |
| if err == nil { | |
| conn.Close() | |
| return closer, nil | |
| } | |
| select { | |
| case <-time.After(delay): | |
| delay *= 2 | |
| case err := <-errChan: | |
| return nil, err | |
| } | |
| } | |
| closer() | |
| return nil, errors.New("failed to set up idevicedebugserverproxy") | |
| } | |
| // findDeviceAppPath returns the device path to the app with the | |
| // given bundle ID. It parses the output of ideviceinstaller -l -o xml, | |
| // looking for the bundle ID and the corresponding Path value. | |
| func findDeviceAppPath(bundleID string) (string, error) { | |
| cmd := idevCmd(exec.Command("ideviceinstaller", "-l", "-o", "xml")) | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| os.Stderr.Write(out) | |
| return "", fmt.Errorf("ideviceinstaller: -l -o xml %v", err) | |
| } | |
| var list struct { | |
| Apps []struct { | |
| Data []byte `xml:",innerxml"` | |
| } `xml:"array>dict"` | |
| } | |
| if err := xml.Unmarshal(out, &list); err != nil { | |
| return "", fmt.Errorf("failed to parse ideviceinstaller output: %v", err) | |
| } | |
| for _, app := range list.Apps { | |
| values, err := parsePlistDict(app.Data) | |
| if err != nil { | |
| return "", fmt.Errorf("findDeviceAppPath: failed to parse app dict: %v", err) | |
| } | |
| if values["CFBundleIdentifier"] == bundleID { | |
| if path, ok := values["Path"]; ok { | |
| return path, nil | |
| } | |
| } | |
| } | |
| return "", fmt.Errorf("failed to find device path for bundle: %s", bundleID) | |
| } | |
| // Parse an xml encoded plist. Plist values are mapped to string. | |
| func parsePlistDict(dict []byte) (map[string]string, error) { | |
| d := xml.NewDecoder(bytes.NewReader(dict)) | |
| values := make(map[string]string) | |
| var key string | |
| var hasKey bool | |
| for { | |
| tok, err := d.Token() | |
| if err == io.EOF { | |
| break | |
| } | |
| if err != nil { | |
| return nil, err | |
| } | |
| if tok, ok := tok.(xml.StartElement); ok { | |
| if tok.Name.Local == "key" { | |
| if err := d.DecodeElement(&key, &tok); err != nil { | |
| return nil, err | |
| } | |
| hasKey = true | |
| } else if hasKey { | |
| var val string | |
| var err error | |
| switch n := tok.Name.Local; n { | |
| case "true", "false": | |
| // Bools are represented as <true/> and <false/>. | |
| val = n | |
| err = d.Skip() | |
| default: | |
| err = d.DecodeElement(&val, &tok) | |
| } | |
| if err != nil { | |
| return nil, err | |
| } | |
| values[key] = val | |
| hasKey = false | |
| } else { | |
| if err := d.Skip(); err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| } | |
| return values, nil | |
| } | |
| func installSimulator(appdir string) error { | |
| cmd := exec.Command( | |
| "xcrun", "simctl", "install", | |
| "booted", // Install to the booted simulator. | |
| appdir, | |
| ) | |
| if out, err := cmd.CombinedOutput(); err != nil { | |
| os.Stderr.Write(out) | |
| return fmt.Errorf("xcrun simctl install booted %q: %v", appdir, err) | |
| } | |
| return nil | |
| } | |
| func uninstallDevice(bundleID string) error { | |
| cmd := idevCmd(exec.Command( | |
| "ideviceinstaller", | |
| "-U", bundleID, | |
| )) | |
| if out, err := cmd.CombinedOutput(); err != nil { | |
| os.Stderr.Write(out) | |
| return fmt.Errorf("ideviceinstaller -U %q: %s", bundleID, err) | |
| } | |
| return nil | |
| } | |
| func installDevice(appdir string) error { | |
| attempt := 0 | |
| for { | |
| cmd := idevCmd(exec.Command( | |
| "ideviceinstaller", | |
| "-i", appdir, | |
| )) | |
| if out, err := cmd.CombinedOutput(); err != nil { | |
| // Sometimes, installing the app fails for some reason. | |
| // Give the device a few seconds and try again. | |
| if attempt < 5 { | |
| time.Sleep(5 * time.Second) | |
| attempt++ | |
| continue | |
| } | |
| os.Stderr.Write(out) | |
| return fmt.Errorf("ideviceinstaller -i %q: %v (%d attempts)", appdir, err, attempt) | |
| } | |
| return nil | |
| } | |
| } | |
| func idevCmd(cmd *exec.Cmd) *exec.Cmd { | |
| if deviceID != "" { | |
| // Inject -u device_id after the executable, but before the arguments. | |
| args := []string{cmd.Args[0], "-u", deviceID} | |
| cmd.Args = append(args, cmd.Args[1:]...) | |
| } | |
| return cmd | |
| } | |
| func runSimulator(appdir, bundleID string, args []string) error { | |
| cmd := exec.Command( | |
| "xcrun", "simctl", "launch", | |
| "--wait-for-debugger", | |
| "booted", | |
| bundleID, | |
| ) | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| os.Stderr.Write(out) | |
| return fmt.Errorf("xcrun simctl launch booted %q: %v", bundleID, err) | |
| } | |
| var processID int | |
| var ignore string | |
| if _, err := fmt.Sscanf(string(out), "%s %d", &ignore, &processID); err != nil { | |
| return fmt.Errorf("runSimulator: couldn't find processID from `simctl launch`: %v (%q)", err, out) | |
| } | |
| _, err = runLLDB("ios-simulator", appdir, strconv.Itoa(processID), args) | |
| return err | |
| } | |
| func runDevice(appdir, bundleID string, args []string) error { | |
| attempt := 0 | |
| for { | |
| // The device app path reported by the device might be stale, so retry | |
| // the lookup of the device path along with the lldb launching below. | |
| deviceapp, err := findDeviceAppPath(bundleID) | |
| if err != nil { | |
| // The device app path might not yet exist for a newly installed app. | |
| if attempt == 5 { | |
| return err | |
| } | |
| attempt++ | |
| time.Sleep(5 * time.Second) | |
| continue | |
| } | |
| out, err := runLLDB("remote-ios", appdir, deviceapp, args) | |
| // If the program was not started it can be retried without papering over | |
| // real test failures. | |
| started := bytes.HasPrefix(out, []byte("lldb: running program")) | |
| if started || err == nil || attempt == 5 { | |
| return err | |
| } | |
| // Sometimes, the app was not yet ready to launch or the device path was | |
| // stale. Retry. | |
| attempt++ | |
| time.Sleep(5 * time.Second) | |
| } | |
| } | |
| func runLLDB(target, appdir, deviceapp string, args []string) ([]byte, error) { | |
| var env []string | |
| for _, e := range os.Environ() { | |
| // Don't override TMPDIR, HOME, GOCACHE on the device. | |
| if strings.HasPrefix(e, "TMPDIR=") || strings.HasPrefix(e, "HOME=") || strings.HasPrefix(e, "GOCACHE=") { | |
| continue | |
| } | |
| env = append(env, e) | |
| } | |
| lldb := exec.Command( | |
| "python", | |
| "-", // Read script from stdin. | |
| target, | |
| appdir, | |
| deviceapp, | |
| ) | |
| lldb.Args = append(lldb.Args, args...) | |
| lldb.Env = env | |
| lldb.Stdin = strings.NewReader(lldbDriver) | |
| lldb.Stdout = os.Stdout | |
| var out bytes.Buffer | |
| lldb.Stderr = io.MultiWriter(&out, os.Stderr) | |
| err := lldb.Start() | |
| if err == nil { | |
| // Forward SIGQUIT to the lldb driver which in turn will forward | |
| // to the running program. | |
| sigs := make(chan os.Signal, 1) | |
| signal.Notify(sigs, syscall.SIGQUIT) | |
| proc := lldb.Process | |
| go func() { | |
| for sig := range sigs { | |
| proc.Signal(sig) | |
| } | |
| }() | |
| err = lldb.Wait() | |
| signal.Stop(sigs) | |
| close(sigs) | |
| } | |
| return out.Bytes(), err | |
| } | |
| 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 | |
| } | |
| } | |
| if underGoRoot { | |
| // Copy timezone file. | |
| // | |
| // Typical apps have the zoneinfo.zip in the root of their app bundle, | |
| // read by the time package as the working directory at initialization. | |
| // As we move the working directory to the GOROOT pkg directory, we | |
| // install the zoneinfo.zip file in the pkgpath. | |
| err := cp( | |
| filepath.Join(dstbase, pkgpath), | |
| filepath.Join(cwd, "lib", "time", "zoneinfo.zip"), | |
| ) | |
| if err != nil { | |
| return "", err | |
| } | |
| // Copy src/runtime/textflag.h for (at least) Test386EndToEnd in | |
| // cmd/asm/internal/asm. | |
| runtimePath := filepath.Join(dstbase, "src", "runtime") | |
| if err := os.MkdirAll(runtimePath, 0755); err != nil { | |
| return "", err | |
| } | |
| err = cp( | |
| filepath.Join(runtimePath, "textflag.h"), | |
| filepath.Join(cwd, "src", "runtime", "textflag.h"), | |
| ) | |
| if 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 | |
| } | |
| cwd, err = filepath.EvalSymlinks(cwd) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| goroot, err := filepath.EvalSymlinks(runtime.GOROOT()) | |
| if err != nil { | |
| return "", false, err | |
| } | |
| if strings.HasPrefix(cwd, goroot) { | |
| subdir, err := filepath.Rel(goroot, cwd) | |
| if err != nil { | |
| return "", false, err | |
| } | |
| return subdir, true, nil | |
| } | |
| for _, p := range filepath.SplitList(build.Default.GOPATH) { | |
| pabs, err := filepath.EvalSymlinks(p) | |
| if err != nil { | |
| return "", false, err | |
| } | |
| if !strings.HasPrefix(cwd, pabs) { | |
| continue | |
| } | |
| subdir, err := filepath.Rel(pabs, 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, | |
| ) | |
| } | |
| func infoPlist(pkgpath string) string { | |
| return `<?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>CFBundleShortVersionString</key><string>1.0</string> | |
| <key>CFBundleIdentifier</key><string>` + bundleID + `</string> | |
| <key>CFBundleResourceSpecification</key><string>ResourceRules.plist</string> | |
| <key>LSRequiresIPhoneOS</key><true/> | |
| <key>CFBundleDisplayName</key><string>gotest</string> | |
| <key>GoExecWrapperWorkingDirectory</key><string>` + pkgpath + `</string> | |
| </dict> | |
| </plist> | |
| ` | |
| } | |
| func entitlementsPlist() string { | |
| return `<?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>` + appID + `</string></array> | |
| <key>get-task-allow</key> | |
| <true/> | |
| <key>application-identifier</key> | |
| <string>` + appID + `</string> | |
| <key>com.apple.developer.team-identifier</key> | |
| <string>` + teamID + `</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> | |
| <integer>10</integer> | |
| </dict> | |
| <key>ResourceRules.plist</key> | |
| <dict> | |
| <key>omit</key> | |
| <true/> | |
| <key>weight</key> | |
| <integer>100</integer> | |
| </dict> | |
| </dict> | |
| </dict> | |
| </plist> | |
| ` | |
| const lldbDriver = ` | |
| import sys | |
| import os | |
| import signal | |
| platform, exe, device_exe_or_pid, args = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4:] | |
| env = [] | |
| for k, v in os.environ.items(): | |
| env.append(k + "=" + v) | |
| sys.path.append('/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python') | |
| import lldb | |
| debugger = lldb.SBDebugger.Create() | |
| debugger.SetAsync(True) | |
| debugger.SkipLLDBInitFiles(True) | |
| err = lldb.SBError() | |
| target = debugger.CreateTarget(exe, None, platform, True, err) | |
| if not target.IsValid() or not err.Success(): | |
| sys.stderr.write("lldb: failed to setup up target: %s\n" % (err)) | |
| sys.exit(1) | |
| listener = debugger.GetListener() | |
| if platform == 'remote-ios': | |
| target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_exe_or_pid)) | |
| process = target.ConnectRemote(listener, 'connect://localhost:3222', None, err) | |
| else: | |
| process = target.AttachToProcessWithID(listener, int(device_exe_or_pid), err) | |
| if not err.Success(): | |
| sys.stderr.write("lldb: failed to connect to remote target %s: %s\n" % (device_exe_or_pid, err)) | |
| sys.exit(1) | |
| # Don't stop on signals. | |
| sigs = process.GetUnixSignals() | |
| for i in range(0, sigs.GetNumSignals()): | |
| sig = sigs.GetSignalAtIndex(i) | |
| sigs.SetShouldStop(sig, False) | |
| sigs.SetShouldNotify(sig, False) | |
| event = lldb.SBEvent() | |
| running = False | |
| prev_handler = None | |
| def signal_handler(signal, frame): | |
| process.Signal(signal) | |
| def run_program(): | |
| # Forward SIGQUIT to the program. | |
| prev_handler = signal.signal(signal.SIGQUIT, signal_handler) | |
| # Tell the Go driver that the program is running and should not be retried. | |
| sys.stderr.write("lldb: running program\n") | |
| running = True | |
| # Process is stopped at attach/launch. Let it run. | |
| process.Continue() | |
| if platform != 'remote-ios': | |
| # For the local emulator the program is ready to run. | |
| # For remote device runs, we need to wait for eStateConnected, | |
| # below. | |
| run_program() | |
| while True: | |
| if not listener.WaitForEvent(1, event): | |
| continue | |
| if not lldb.SBProcess.EventIsProcessEvent(event): | |
| continue | |
| if running: | |
| # Pass through stdout and stderr. | |
| while True: | |
| out = process.GetSTDOUT(8192) | |
| if not out: | |
| break | |
| sys.stdout.write(out) | |
| while True: | |
| out = process.GetSTDERR(8192) | |
| if not out: | |
| break | |
| sys.stderr.write(out) | |
| state = process.GetStateFromEvent(event) | |
| if state in [lldb.eStateCrashed, lldb.eStateDetached, lldb.eStateUnloaded, lldb.eStateExited]: | |
| if running: | |
| signal.signal(signal.SIGQUIT, prev_handler) | |
| break | |
| elif state == lldb.eStateConnected: | |
| if platform == 'remote-ios': | |
| process.RemoteLaunch(args, env, None, None, None, None, 0, False, err) | |
| if not err.Success(): | |
| sys.stderr.write("lldb: failed to launch remote process: %s\n" % (err)) | |
| process.Kill() | |
| debugger.Terminate() | |
| sys.exit(1) | |
| run_program() | |
| exitStatus = process.GetExitStatus() | |
| exitDesc = process.GetExitDescription() | |
| process.Kill() | |
| debugger.Terminate() | |
| if exitStatus == 0 and exitDesc is not None: | |
| # Ensure tests fail when killed by a signal. | |
| exitStatus = 123 | |
| sys.exit(exitStatus) | |
| ` |