Source file misc/ios/go_ios_exec.go

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

View as plain text