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

View as plain text