github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/misc/ios/go_darwin_arm_exec.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  // This program can be used as go_darwin_arm_exec by the Go tool.
     6  // It executes binaries on an iOS device using the XCode toolchain
     7  // and the ios-deploy program: https://github.com/phonegap/ios-deploy
     8  //
     9  // This script supports an extra flag, -lldb, that pauses execution
    10  // just before the main program begins and allows the user to control
    11  // the remote lldb session. This flag is appended to the end of the
    12  // script's arguments and is not passed through to the underlying
    13  // binary.
    14  //
    15  // This script requires that three environment variables be set:
    16  // 	GOIOS_DEV_ID: The codesigning developer id or certificate identifier
    17  // 	GOIOS_APP_ID: The provisioning app id prefix. Must support wildcard app ids.
    18  // 	GOIOS_TEAM_ID: The team id that owns the app id prefix.
    19  // $GOROOT/misc/ios contains a script, detect.go, that attempts to autodetect these.
    20  package main
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/xml"
    25  	"errors"
    26  	"fmt"
    27  	"go/build"
    28  	"io"
    29  	"io/ioutil"
    30  	"log"
    31  	"net"
    32  	"os"
    33  	"os/exec"
    34  	"os/signal"
    35  	"path/filepath"
    36  	"runtime"
    37  	"strings"
    38  	"syscall"
    39  	"time"
    40  )
    41  
    42  const debug = false
    43  
    44  var tmpdir string
    45  
    46  var (
    47  	devID    string
    48  	appID    string
    49  	teamID   string
    50  	bundleID string
    51  	deviceID string
    52  )
    53  
    54  // lock is a file lock to serialize iOS runs. It is global to avoid the
    55  // garbage collector finalizing it, closing the file and releasing the
    56  // lock prematurely.
    57  var lock *os.File
    58  
    59  func main() {
    60  	log.SetFlags(0)
    61  	log.SetPrefix("go_darwin_arm_exec: ")
    62  	if debug {
    63  		log.Println(strings.Join(os.Args, " "))
    64  	}
    65  	if len(os.Args) < 2 {
    66  		log.Fatal("usage: go_darwin_arm_exec a.out")
    67  	}
    68  
    69  	// e.g. B393DDEB490947F5A463FD074299B6C0AXXXXXXX
    70  	devID = getenv("GOIOS_DEV_ID")
    71  
    72  	// e.g. Z8B3JBXXXX.org.golang.sample, Z8B3JBXXXX prefix is available at
    73  	// https://developer.apple.com/membercenter/index.action#accountSummary as Team ID.
    74  	appID = getenv("GOIOS_APP_ID")
    75  
    76  	// e.g. Z8B3JBXXXX, available at
    77  	// https://developer.apple.com/membercenter/index.action#accountSummary as Team ID.
    78  	teamID = getenv("GOIOS_TEAM_ID")
    79  
    80  	// Device IDs as listed with ios-deploy -c.
    81  	deviceID = os.Getenv("GOIOS_DEVICE_ID")
    82  
    83  	parts := strings.SplitN(appID, ".", 2)
    84  	// For compatibility with the old builders, use a fallback bundle ID
    85  	bundleID = "golang.gotest"
    86  	if len(parts) == 2 {
    87  		bundleID = parts[1]
    88  	}
    89  
    90  	exitCode, err := runMain()
    91  	if err != nil {
    92  		log.Fatalf("%v\n", err)
    93  	}
    94  	os.Exit(exitCode)
    95  }
    96  
    97  func runMain() (int, error) {
    98  	var err error
    99  	tmpdir, err = ioutil.TempDir("", "go_darwin_arm_exec_")
   100  	if err != nil {
   101  		return 1, err
   102  	}
   103  	if !debug {
   104  		defer os.RemoveAll(tmpdir)
   105  	}
   106  
   107  	appdir := filepath.Join(tmpdir, "gotest.app")
   108  	os.RemoveAll(appdir)
   109  
   110  	if err := assembleApp(appdir, os.Args[1]); err != nil {
   111  		return 1, err
   112  	}
   113  
   114  	// This wrapper uses complicated machinery to run iOS binaries. It
   115  	// works, but only when running one binary at a time.
   116  	// Use a file lock to make sure only one wrapper is running at a time.
   117  	//
   118  	// The lock file is never deleted, to avoid concurrent locks on distinct
   119  	// files with the same path.
   120  	lockName := filepath.Join(os.TempDir(), "go_darwin_arm_exec-"+deviceID+".lock")
   121  	lock, err = os.OpenFile(lockName, os.O_CREATE|os.O_RDONLY, 0666)
   122  	if err != nil {
   123  		return 1, err
   124  	}
   125  	if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
   126  		return 1, err
   127  	}
   128  
   129  	if err := uninstall(bundleID); err != nil {
   130  		return 1, err
   131  	}
   132  
   133  	if err := install(appdir); err != nil {
   134  		return 1, err
   135  	}
   136  
   137  	if err := mountDevImage(); err != nil {
   138  		return 1, err
   139  	}
   140  
   141  	// Kill any hanging debug bridges that might take up port 3222.
   142  	exec.Command("killall", "idevicedebugserverproxy").Run()
   143  
   144  	closer, err := startDebugBridge()
   145  	if err != nil {
   146  		return 1, err
   147  	}
   148  	defer closer()
   149  
   150  	if err := run(appdir, bundleID, os.Args[2:]); err != nil {
   151  		// If the lldb driver completed with an exit code, use that.
   152  		if err, ok := err.(*exec.ExitError); ok {
   153  			if ws, ok := err.Sys().(interface{ ExitStatus() int }); ok {
   154  				return ws.ExitStatus(), nil
   155  			}
   156  		}
   157  		return 1, err
   158  	}
   159  	return 0, nil
   160  }
   161  
   162  func getenv(envvar string) string {
   163  	s := os.Getenv(envvar)
   164  	if s == "" {
   165  		log.Fatalf("%s not set\nrun $GOROOT/misc/ios/detect.go to attempt to autodetect", envvar)
   166  	}
   167  	return s
   168  }
   169  
   170  func assembleApp(appdir, bin string) error {
   171  	if err := os.MkdirAll(appdir, 0755); err != nil {
   172  		return err
   173  	}
   174  
   175  	if err := cp(filepath.Join(appdir, "gotest"), bin); err != nil {
   176  		return err
   177  	}
   178  
   179  	pkgpath, err := copyLocalData(appdir)
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist")
   185  	if err := ioutil.WriteFile(entitlementsPath, []byte(entitlementsPlist()), 0744); err != nil {
   186  		return err
   187  	}
   188  	if err := ioutil.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist(pkgpath)), 0744); err != nil {
   189  		return err
   190  	}
   191  	if err := ioutil.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil {
   192  		return err
   193  	}
   194  
   195  	cmd := exec.Command(
   196  		"codesign",
   197  		"-f",
   198  		"-s", devID,
   199  		"--entitlements", entitlementsPath,
   200  		appdir,
   201  	)
   202  	if debug {
   203  		log.Println(strings.Join(cmd.Args, " "))
   204  	}
   205  	cmd.Stdout = os.Stdout
   206  	cmd.Stderr = os.Stderr
   207  	if err := cmd.Run(); err != nil {
   208  		return fmt.Errorf("codesign: %v", err)
   209  	}
   210  	return nil
   211  }
   212  
   213  // mountDevImage ensures a developer image is mounted on the device.
   214  // The image contains the device lldb server for idevicedebugserverproxy
   215  // to connect to.
   216  func mountDevImage() error {
   217  	// Check for existing mount.
   218  	cmd := idevCmd(exec.Command("ideviceimagemounter", "-l", "-x"))
   219  	out, err := cmd.CombinedOutput()
   220  	if err != nil {
   221  		os.Stderr.Write(out)
   222  		return fmt.Errorf("ideviceimagemounter: %v", err)
   223  	}
   224  	var info struct {
   225  		Dict struct {
   226  			Data []byte `xml:",innerxml"`
   227  		} `xml:"dict"`
   228  	}
   229  	if err := xml.Unmarshal(out, &info); err != nil {
   230  		return fmt.Errorf("mountDevImage: failed to decode mount information: %v", err)
   231  	}
   232  	dict, err := parsePlistDict(info.Dict.Data)
   233  	if err != nil {
   234  		return fmt.Errorf("mountDevImage: failed to parse mount information: %v", err)
   235  	}
   236  	if dict["ImagePresent"] == "true" && dict["Status"] == "Complete" {
   237  		return nil
   238  	}
   239  	// Some devices only give us an ImageSignature key.
   240  	if _, exists := dict["ImageSignature"]; exists {
   241  		return nil
   242  	}
   243  	// No image is mounted. Find a suitable image.
   244  	imgPath, err := findDevImage()
   245  	if err != nil {
   246  		return err
   247  	}
   248  	sigPath := imgPath + ".signature"
   249  	cmd = idevCmd(exec.Command("ideviceimagemounter", imgPath, sigPath))
   250  	if out, err := cmd.CombinedOutput(); err != nil {
   251  		os.Stderr.Write(out)
   252  		return fmt.Errorf("ideviceimagemounter: %v", err)
   253  	}
   254  	return nil
   255  }
   256  
   257  // findDevImage use the device iOS version and build to locate a suitable
   258  // developer image.
   259  func findDevImage() (string, error) {
   260  	cmd := idevCmd(exec.Command("ideviceinfo"))
   261  	out, err := cmd.Output()
   262  	if err != nil {
   263  		return "", fmt.Errorf("ideviceinfo: %v", err)
   264  	}
   265  	var iosVer, buildVer string
   266  	lines := bytes.Split(out, []byte("\n"))
   267  	for _, line := range lines {
   268  		spl := bytes.SplitN(line, []byte(": "), 2)
   269  		if len(spl) != 2 {
   270  			continue
   271  		}
   272  		key, val := string(spl[0]), string(spl[1])
   273  		switch key {
   274  		case "ProductVersion":
   275  			iosVer = val
   276  		case "BuildVersion":
   277  			buildVer = val
   278  		}
   279  	}
   280  	if iosVer == "" || buildVer == "" {
   281  		return "", errors.New("failed to parse ideviceinfo output")
   282  	}
   283  	verSplit := strings.Split(iosVer, ".")
   284  	if len(verSplit) > 2 {
   285  		// Developer images are specific to major.minor ios version.
   286  		// Cut off the patch version.
   287  		iosVer = strings.Join(verSplit[:2], ".")
   288  	}
   289  	sdkBase := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport"
   290  	patterns := []string{fmt.Sprintf("%s (%s)", iosVer, buildVer), fmt.Sprintf("%s (*)", iosVer), fmt.Sprintf("%s*", iosVer)}
   291  	for _, pattern := range patterns {
   292  		matches, err := filepath.Glob(filepath.Join(sdkBase, pattern, "DeveloperDiskImage.dmg"))
   293  		if err != nil {
   294  			return "", fmt.Errorf("findDevImage: %v", err)
   295  		}
   296  		if len(matches) > 0 {
   297  			return matches[0], nil
   298  		}
   299  	}
   300  	return "", fmt.Errorf("failed to find matching developer image for iOS version %s build %s", iosVer, buildVer)
   301  }
   302  
   303  // startDebugBridge ensures that the idevicedebugserverproxy runs on
   304  // port 3222.
   305  func startDebugBridge() (func(), error) {
   306  	errChan := make(chan error, 1)
   307  	cmd := idevCmd(exec.Command("idevicedebugserverproxy", "3222"))
   308  	var stderr bytes.Buffer
   309  	cmd.Stderr = &stderr
   310  	if err := cmd.Start(); err != nil {
   311  		return nil, fmt.Errorf("idevicedebugserverproxy: %v", err)
   312  	}
   313  	go func() {
   314  		if err := cmd.Wait(); err != nil {
   315  			if _, ok := err.(*exec.ExitError); ok {
   316  				errChan <- fmt.Errorf("idevicedebugserverproxy: %s", stderr.Bytes())
   317  			} else {
   318  				errChan <- fmt.Errorf("idevicedebugserverproxy: %v", err)
   319  			}
   320  		}
   321  		errChan <- nil
   322  	}()
   323  	closer := func() {
   324  		cmd.Process.Kill()
   325  		<-errChan
   326  	}
   327  	// Dial localhost:3222 to ensure the proxy is ready.
   328  	delay := time.Second / 4
   329  	for attempt := 0; attempt < 5; attempt++ {
   330  		conn, err := net.DialTimeout("tcp", "localhost:3222", 5*time.Second)
   331  		if err == nil {
   332  			conn.Close()
   333  			return closer, nil
   334  		}
   335  		select {
   336  		case <-time.After(delay):
   337  			delay *= 2
   338  		case err := <-errChan:
   339  			return nil, err
   340  		}
   341  	}
   342  	closer()
   343  	return nil, errors.New("failed to set up idevicedebugserverproxy")
   344  }
   345  
   346  // findDeviceAppPath returns the device path to the app with the
   347  // given bundle ID. It parses the output of ideviceinstaller -l -o xml,
   348  // looking for the bundle ID and the corresponding Path value.
   349  func findDeviceAppPath(bundleID string) (string, error) {
   350  	cmd := idevCmd(exec.Command("ideviceinstaller", "-l", "-o", "xml"))
   351  	out, err := cmd.CombinedOutput()
   352  	if err != nil {
   353  		os.Stderr.Write(out)
   354  		return "", fmt.Errorf("ideviceinstaller: -l -o xml %v", err)
   355  	}
   356  	var list struct {
   357  		Apps []struct {
   358  			Data []byte `xml:",innerxml"`
   359  		} `xml:"array>dict"`
   360  	}
   361  	if err := xml.Unmarshal(out, &list); err != nil {
   362  		return "", fmt.Errorf("failed to parse ideviceinstaller output: %v", err)
   363  	}
   364  	for _, app := range list.Apps {
   365  		values, err := parsePlistDict(app.Data)
   366  		if err != nil {
   367  			return "", fmt.Errorf("findDeviceAppPath: failed to parse app dict: %v", err)
   368  		}
   369  		if values["CFBundleIdentifier"] == bundleID {
   370  			if path, ok := values["Path"]; ok {
   371  				return path, nil
   372  			}
   373  		}
   374  	}
   375  	return "", fmt.Errorf("failed to find device path for bundle: %s", bundleID)
   376  }
   377  
   378  // Parse an xml encoded plist. Plist values are mapped to string.
   379  func parsePlistDict(dict []byte) (map[string]string, error) {
   380  	d := xml.NewDecoder(bytes.NewReader(dict))
   381  	values := make(map[string]string)
   382  	var key string
   383  	var hasKey bool
   384  	for {
   385  		tok, err := d.Token()
   386  		if err == io.EOF {
   387  			break
   388  		}
   389  		if err != nil {
   390  			return nil, err
   391  		}
   392  		if tok, ok := tok.(xml.StartElement); ok {
   393  			if tok.Name.Local == "key" {
   394  				if err := d.DecodeElement(&key, &tok); err != nil {
   395  					return nil, err
   396  				}
   397  				hasKey = true
   398  			} else if hasKey {
   399  				var val string
   400  				var err error
   401  				switch n := tok.Name.Local; n {
   402  				case "true", "false":
   403  					// Bools are represented as <true/> and <false/>.
   404  					val = n
   405  					err = d.Skip()
   406  				default:
   407  					err = d.DecodeElement(&val, &tok)
   408  				}
   409  				if err != nil {
   410  					return nil, err
   411  				}
   412  				values[key] = val
   413  				hasKey = false
   414  			} else {
   415  				if err := d.Skip(); err != nil {
   416  					return nil, err
   417  				}
   418  			}
   419  		}
   420  	}
   421  	return values, nil
   422  }
   423  
   424  func uninstall(bundleID string) error {
   425  	cmd := idevCmd(exec.Command(
   426  		"ideviceinstaller",
   427  		"-U", bundleID,
   428  	))
   429  	if out, err := cmd.CombinedOutput(); err != nil {
   430  		os.Stderr.Write(out)
   431  		return fmt.Errorf("ideviceinstaller -U %q: %s", bundleID, err)
   432  	}
   433  	return nil
   434  }
   435  
   436  func install(appdir string) error {
   437  	attempt := 0
   438  	for {
   439  		cmd := idevCmd(exec.Command(
   440  			"ideviceinstaller",
   441  			"-i", appdir,
   442  		))
   443  		if out, err := cmd.CombinedOutput(); err != nil {
   444  			// Sometimes, installing the app fails for some reason.
   445  			// Give the device a few seconds and try again.
   446  			if attempt < 5 {
   447  				time.Sleep(5 * time.Second)
   448  				attempt++
   449  				continue
   450  			}
   451  			os.Stderr.Write(out)
   452  			return fmt.Errorf("ideviceinstaller -i %q: %v (%d attempts)", appdir, err, attempt)
   453  		}
   454  		return nil
   455  	}
   456  }
   457  
   458  func idevCmd(cmd *exec.Cmd) *exec.Cmd {
   459  	if deviceID != "" {
   460  		// Inject -u device_id after the executable, but before the arguments.
   461  		args := []string{cmd.Args[0], "-u", deviceID}
   462  		cmd.Args = append(args, cmd.Args[1:]...)
   463  	}
   464  	return cmd
   465  }
   466  
   467  func run(appdir, bundleID string, args []string) error {
   468  	var env []string
   469  	for _, e := range os.Environ() {
   470  		// Don't override TMPDIR on the device.
   471  		if strings.HasPrefix(e, "TMPDIR=") {
   472  			continue
   473  		}
   474  		env = append(env, e)
   475  	}
   476  	attempt := 0
   477  	for {
   478  		// The device app path reported by the device might be stale, so retry
   479  		// the lookup of the device path along with the lldb launching below.
   480  		deviceapp, err := findDeviceAppPath(bundleID)
   481  		if err != nil {
   482  			// The device app path might not yet exist for a newly installed app.
   483  			if attempt == 5 {
   484  				return err
   485  			}
   486  			attempt++
   487  			time.Sleep(5 * time.Second)
   488  			continue
   489  		}
   490  		lldb := exec.Command(
   491  			"python",
   492  			"-", // Read script from stdin.
   493  			appdir,
   494  			deviceapp,
   495  		)
   496  		lldb.Args = append(lldb.Args, args...)
   497  		lldb.Env = env
   498  		lldb.Stdin = strings.NewReader(lldbDriver)
   499  		lldb.Stdout = os.Stdout
   500  		var out bytes.Buffer
   501  		lldb.Stderr = io.MultiWriter(&out, os.Stderr)
   502  		err = lldb.Start()
   503  		if err == nil {
   504  			// Forward SIGQUIT to the lldb driver which in turn will forward
   505  			// to the running program.
   506  			sigs := make(chan os.Signal, 1)
   507  			signal.Notify(sigs, syscall.SIGQUIT)
   508  			proc := lldb.Process
   509  			go func() {
   510  				for sig := range sigs {
   511  					proc.Signal(sig)
   512  				}
   513  			}()
   514  			err = lldb.Wait()
   515  			signal.Stop(sigs)
   516  			close(sigs)
   517  		}
   518  		// If the program was not started it can be retried without papering over
   519  		// real test failures.
   520  		started := bytes.HasPrefix(out.Bytes(), []byte("lldb: running program"))
   521  		if started || err == nil || attempt == 5 {
   522  			return err
   523  		}
   524  		// Sometimes, the app was not yet ready to launch or the device path was
   525  		// stale. Retry.
   526  		attempt++
   527  		time.Sleep(5 * time.Second)
   528  	}
   529  }
   530  
   531  func copyLocalDir(dst, src string) error {
   532  	if err := os.Mkdir(dst, 0755); err != nil {
   533  		return err
   534  	}
   535  
   536  	d, err := os.Open(src)
   537  	if err != nil {
   538  		return err
   539  	}
   540  	defer d.Close()
   541  	fi, err := d.Readdir(-1)
   542  	if err != nil {
   543  		return err
   544  	}
   545  
   546  	for _, f := range fi {
   547  		if f.IsDir() {
   548  			if f.Name() == "testdata" {
   549  				if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
   550  					return err
   551  				}
   552  			}
   553  			continue
   554  		}
   555  		if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
   556  			return err
   557  		}
   558  	}
   559  	return nil
   560  }
   561  
   562  func cp(dst, src string) error {
   563  	out, err := exec.Command("cp", "-a", src, dst).CombinedOutput()
   564  	if err != nil {
   565  		os.Stderr.Write(out)
   566  	}
   567  	return err
   568  }
   569  
   570  func copyLocalData(dstbase string) (pkgpath string, err error) {
   571  	cwd, err := os.Getwd()
   572  	if err != nil {
   573  		return "", err
   574  	}
   575  
   576  	finalPkgpath, underGoRoot, err := subdir()
   577  	if err != nil {
   578  		return "", err
   579  	}
   580  	cwd = strings.TrimSuffix(cwd, finalPkgpath)
   581  
   582  	// Copy all immediate files and testdata directories between
   583  	// the package being tested and the source root.
   584  	pkgpath = ""
   585  	for _, element := range strings.Split(finalPkgpath, string(filepath.Separator)) {
   586  		if debug {
   587  			log.Printf("copying %s", pkgpath)
   588  		}
   589  		pkgpath = filepath.Join(pkgpath, element)
   590  		dst := filepath.Join(dstbase, pkgpath)
   591  		src := filepath.Join(cwd, pkgpath)
   592  		if err := copyLocalDir(dst, src); err != nil {
   593  			return "", err
   594  		}
   595  	}
   596  
   597  	if underGoRoot {
   598  		// Copy timezone file.
   599  		//
   600  		// Typical apps have the zoneinfo.zip in the root of their app bundle,
   601  		// read by the time package as the working directory at initialization.
   602  		// As we move the working directory to the GOROOT pkg directory, we
   603  		// install the zoneinfo.zip file in the pkgpath.
   604  		err := cp(
   605  			filepath.Join(dstbase, pkgpath),
   606  			filepath.Join(cwd, "lib", "time", "zoneinfo.zip"),
   607  		)
   608  		if err != nil {
   609  			return "", err
   610  		}
   611  		// Copy src/runtime/textflag.h for (at least) Test386EndToEnd in
   612  		// cmd/asm/internal/asm.
   613  		runtimePath := filepath.Join(dstbase, "src", "runtime")
   614  		if err := os.MkdirAll(runtimePath, 0755); err != nil {
   615  			return "", err
   616  		}
   617  		err = cp(
   618  			filepath.Join(runtimePath, "textflag.h"),
   619  			filepath.Join(cwd, "src", "runtime", "textflag.h"),
   620  		)
   621  		if err != nil {
   622  			return "", err
   623  		}
   624  	}
   625  
   626  	return finalPkgpath, nil
   627  }
   628  
   629  // subdir determines the package based on the current working directory,
   630  // and returns the path to the package source relative to $GOROOT (or $GOPATH).
   631  func subdir() (pkgpath string, underGoRoot bool, err error) {
   632  	cwd, err := os.Getwd()
   633  	if err != nil {
   634  		return "", false, err
   635  	}
   636  	if root := runtime.GOROOT(); strings.HasPrefix(cwd, root) {
   637  		subdir, err := filepath.Rel(root, cwd)
   638  		if err != nil {
   639  			return "", false, err
   640  		}
   641  		return subdir, true, nil
   642  	}
   643  
   644  	for _, p := range filepath.SplitList(build.Default.GOPATH) {
   645  		if !strings.HasPrefix(cwd, p) {
   646  			continue
   647  		}
   648  		subdir, err := filepath.Rel(p, cwd)
   649  		if err == nil {
   650  			return subdir, false, nil
   651  		}
   652  	}
   653  	return "", false, fmt.Errorf(
   654  		"working directory %q is not in either GOROOT(%q) or GOPATH(%q)",
   655  		cwd,
   656  		runtime.GOROOT(),
   657  		build.Default.GOPATH,
   658  	)
   659  }
   660  
   661  func infoPlist(pkgpath string) string {
   662  	return `<?xml version="1.0" encoding="UTF-8"?>
   663  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   664  <plist version="1.0">
   665  <dict>
   666  <key>CFBundleName</key><string>golang.gotest</string>
   667  <key>CFBundleSupportedPlatforms</key><array><string>iPhoneOS</string></array>
   668  <key>CFBundleExecutable</key><string>gotest</string>
   669  <key>CFBundleVersion</key><string>1.0</string>
   670  <key>CFBundleIdentifier</key><string>` + bundleID + `</string>
   671  <key>CFBundleResourceSpecification</key><string>ResourceRules.plist</string>
   672  <key>LSRequiresIPhoneOS</key><true/>
   673  <key>CFBundleDisplayName</key><string>gotest</string>
   674  <key>GoExecWrapperWorkingDirectory</key><string>` + pkgpath + `</string>
   675  </dict>
   676  </plist>
   677  `
   678  }
   679  
   680  func entitlementsPlist() string {
   681  	return `<?xml version="1.0" encoding="UTF-8"?>
   682  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   683  <plist version="1.0">
   684  <dict>
   685  	<key>keychain-access-groups</key>
   686  	<array><string>` + appID + `</string></array>
   687  	<key>get-task-allow</key>
   688  	<true/>
   689  	<key>application-identifier</key>
   690  	<string>` + appID + `</string>
   691  	<key>com.apple.developer.team-identifier</key>
   692  	<string>` + teamID + `</string>
   693  </dict>
   694  </plist>
   695  `
   696  }
   697  
   698  const resourceRules = `<?xml version="1.0" encoding="UTF-8"?>
   699  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   700  <plist version="1.0">
   701  <dict>
   702  	<key>rules</key>
   703  	<dict>
   704  		<key>.*</key>
   705  		<true/>
   706  		<key>Info.plist</key>
   707  		<dict>
   708  			<key>omit</key>
   709  			<true/>
   710  			<key>weight</key>
   711  			<integer>10</integer>
   712  		</dict>
   713  		<key>ResourceRules.plist</key>
   714  		<dict>
   715  			<key>omit</key>
   716  			<true/>
   717  			<key>weight</key>
   718  			<integer>100</integer>
   719  		</dict>
   720  	</dict>
   721  </dict>
   722  </plist>
   723  `
   724  
   725  const lldbDriver = `
   726  import sys
   727  import os
   728  import signal
   729  
   730  exe, device_exe, args = sys.argv[1], sys.argv[2], sys.argv[3:]
   731  
   732  env = []
   733  for k, v in os.environ.items():
   734  	env.append(k + "=" + v)
   735  
   736  sys.path.append('/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python')
   737  
   738  import lldb
   739  
   740  debugger = lldb.SBDebugger.Create()
   741  debugger.SetAsync(True)
   742  debugger.SkipLLDBInitFiles(True)
   743  
   744  err = lldb.SBError()
   745  target = debugger.CreateTarget(exe, None, 'remote-ios', True, err)
   746  if not target.IsValid() or not err.Success():
   747  	sys.stderr.write("lldb: failed to setup up target: %s\n" % (err))
   748  	sys.exit(1)
   749  
   750  target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_exe))
   751  
   752  listener = debugger.GetListener()
   753  process = target.ConnectRemote(listener, 'connect://localhost:3222', None, err)
   754  if not err.Success():
   755  	sys.stderr.write("lldb: failed to connect to remote target: %s\n" % (err))
   756  	sys.exit(1)
   757  
   758  # Don't stop on signals.
   759  sigs = process.GetUnixSignals()
   760  for i in range(0, sigs.GetNumSignals()):
   761  	sig = sigs.GetSignalAtIndex(i)
   762  	sigs.SetShouldStop(sig, False)
   763  	sigs.SetShouldNotify(sig, False)
   764  
   765  event = lldb.SBEvent()
   766  running = False
   767  prev_handler = None
   768  while True:
   769  	if not listener.WaitForEvent(1, event):
   770  		continue
   771  	if not lldb.SBProcess.EventIsProcessEvent(event):
   772  		continue
   773  	if running:
   774  		# Pass through stdout and stderr.
   775  		while True:
   776  			out = process.GetSTDOUT(8192)
   777  			if not out:
   778  				break
   779  			sys.stdout.write(out)
   780  		while True:
   781  			out = process.GetSTDERR(8192)
   782  			if not out:
   783  				break
   784  			sys.stderr.write(out)
   785  	state = process.GetStateFromEvent(event)
   786  	if state in [lldb.eStateCrashed, lldb.eStateDetached, lldb.eStateUnloaded, lldb.eStateExited]:
   787  		if running:
   788  			signal.signal(signal.SIGQUIT, prev_handler)
   789  		break
   790  	elif state == lldb.eStateConnected:
   791  		process.RemoteLaunch(args, env, None, None, None, None, 0, False, err)
   792  		if not err.Success():
   793  			sys.stderr.write("lldb: failed to launch remote process: %s\n" % (err))
   794  			process.Kill()
   795  			debugger.Terminate()
   796  			sys.exit(1)
   797  		# Forward SIGQUIT to the program.
   798  		def signal_handler(signal, frame):
   799  			process.Signal(signal)
   800  		prev_handler = signal.signal(signal.SIGQUIT, signal_handler)
   801  		# Tell the Go driver that the program is running and should not be retried.
   802  		sys.stderr.write("lldb: running program\n")
   803  		running = True
   804  		# Process stops once at the beginning. Continue.
   805  		process.Continue()
   806  
   807  exitStatus = process.GetExitStatus()
   808  process.Kill()
   809  debugger.Terminate()
   810  sys.exit(exitStatus)
   811  `