misc/ios/go_ios_exec.go GO 367 lines View on github.com → Search inside
1// Copyright 2024 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45// This program can be used as go_ios_$GOARCH_exec by the Go tool. It executes6// binaries on the iOS Simulator using the XCode toolchain.7package main89import (10	"fmt"11	"go/build"12	"log"13	"os"14	"os/exec"15	"path/filepath"16	"runtime"17	"strings"18	"syscall"19)2021const debug = false2223var tmpdir string2425var (26	devID    string27	appID    string28	teamID   string29	bundleID string30	deviceID string31)3233// lock is a file lock to serialize iOS runs. It is global to avoid the34// garbage collector finalizing it, closing the file and releasing the35// lock prematurely.36var lock *os.File3738func main() {39	log.SetFlags(0)40	log.SetPrefix("go_ios_exec: ")41	if debug {42		log.Println(strings.Join(os.Args, " "))43	}44	if len(os.Args) < 2 {45		log.Fatal("usage: go_ios_exec a.out")46	}4748	// For compatibility with the old builders, use a fallback bundle ID49	bundleID = "golang.gotest"5051	exitCode, err := runMain()52	if err != nil {53		log.Fatalf("%v\n", err)54	}55	os.Exit(exitCode)56}5758func runMain() (int, error) {59	var err error60	tmpdir, err = os.MkdirTemp("", "go_ios_exec_")61	if err != nil {62		return 1, err63	}64	if !debug {65		defer os.RemoveAll(tmpdir)66	}6768	appdir := filepath.Join(tmpdir, "gotest.app")69	os.RemoveAll(appdir)7071	if err := assembleApp(appdir, os.Args[1]); err != nil {72		return 1, err73	}7475	// This wrapper uses complicated machinery to run iOS binaries. It76	// works, but only when running one binary at a time.77	// Use a file lock to make sure only one wrapper is running at a time.78	//79	// The lock file is never deleted, to avoid concurrent locks on distinct80	// files with the same path.81	lockName := filepath.Join(os.TempDir(), "go_ios_exec-"+deviceID+".lock")82	lock, err = os.OpenFile(lockName, os.O_CREATE|os.O_RDONLY, 0666)83	if err != nil {84		return 1, err85	}86	if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {87		return 1, err88	}8990	err = runOnSimulator(appdir)91	if err != nil {92		return 1, err93	}94	return 0, nil95}9697func runOnSimulator(appdir string) error {98	if err := installSimulator(appdir); err != nil {99		return err100	}101102	return runSimulator(appdir, bundleID, os.Args[2:])103}104105func assembleApp(appdir, bin string) error {106	if err := os.MkdirAll(appdir, 0755); err != nil {107		return err108	}109110	if err := cp(filepath.Join(appdir, "gotest"), bin); err != nil {111		return err112	}113114	pkgpath, err := copyLocalData(appdir)115	if err != nil {116		return err117	}118119	entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist")120	if err := os.WriteFile(entitlementsPath, []byte(entitlementsPlist()), 0744); err != nil {121		return err122	}123	if err := os.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist(pkgpath)), 0744); err != nil {124		return err125	}126	if err := os.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil {127		return err128	}129	return nil130}131132func installSimulator(appdir string) error {133	cmd := exec.Command(134		"xcrun", "simctl", "install",135		"booted", // Install to the booted simulator.136		appdir,137	)138	if out, err := cmd.CombinedOutput(); err != nil {139		os.Stderr.Write(out)140		return fmt.Errorf("xcrun simctl install booted %q: %v", appdir, err)141	}142	return nil143}144145func runSimulator(appdir, bundleID string, args []string) error {146	xcrunArgs := []string{"simctl", "spawn",147		"booted",148		appdir + "/gotest",149	}150	xcrunArgs = append(xcrunArgs, args...)151	cmd := exec.Command("xcrun", xcrunArgs...)152	cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr153	err := cmd.Run()154	if err != nil {155		return fmt.Errorf("xcrun simctl launch booted %q: %v", bundleID, err)156	}157158	return nil159}160161func copyLocalDir(dst, src string) error {162	if err := os.Mkdir(dst, 0755); err != nil {163		return err164	}165166	d, err := os.Open(src)167	if err != nil {168		return err169	}170	defer d.Close()171	fi, err := d.Readdir(-1)172	if err != nil {173		return err174	}175176	for _, f := range fi {177		if f.IsDir() {178			if f.Name() == "testdata" {179				if err := cp(dst, filepath.Join(src, f.Name())); err != nil {180					return err181				}182			}183			continue184		}185		if err := cp(dst, filepath.Join(src, f.Name())); err != nil {186			return err187		}188	}189	return nil190}191192func cp(dst, src string) error {193	out, err := exec.Command("cp", "-a", src, dst).CombinedOutput()194	if err != nil {195		os.Stderr.Write(out)196	}197	return err198}199200func copyLocalData(dstbase string) (pkgpath string, err error) {201	cwd, err := os.Getwd()202	if err != nil {203		return "", err204	}205206	finalPkgpath, underGoRoot, err := subdir()207	if err != nil {208		return "", err209	}210	cwd = strings.TrimSuffix(cwd, finalPkgpath)211212	// Copy all immediate files and testdata directories between213	// the package being tested and the source root.214	pkgpath = ""215	for element := range strings.SplitSeq(finalPkgpath, string(filepath.Separator)) {216		if debug {217			log.Printf("copying %s", pkgpath)218		}219		pkgpath = filepath.Join(pkgpath, element)220		dst := filepath.Join(dstbase, pkgpath)221		src := filepath.Join(cwd, pkgpath)222		if err := copyLocalDir(dst, src); err != nil {223			return "", err224		}225	}226227	if underGoRoot {228		// Copy timezone file.229		//230		// Typical apps have the zoneinfo.zip in the root of their app bundle,231		// read by the time package as the working directory at initialization.232		// As we move the working directory to the GOROOT pkg directory, we233		// install the zoneinfo.zip file in the pkgpath.234		err := cp(235			filepath.Join(dstbase, pkgpath),236			filepath.Join(cwd, "lib", "time", "zoneinfo.zip"),237		)238		if err != nil {239			return "", err240		}241		// Copy src/runtime/textflag.h for (at least) Test386EndToEnd in242		// cmd/asm/internal/asm.243		runtimePath := filepath.Join(dstbase, "src", "runtime")244		if err := os.MkdirAll(runtimePath, 0755); err != nil {245			return "", err246		}247		err = cp(248			filepath.Join(runtimePath, "textflag.h"),249			filepath.Join(cwd, "src", "runtime", "textflag.h"),250		)251		if err != nil {252			return "", err253		}254	}255256	return finalPkgpath, nil257}258259// subdir determines the package based on the current working directory,260// and returns the path to the package source relative to $GOROOT (or $GOPATH).261func subdir() (pkgpath string, underGoRoot bool, err error) {262	cwd, err := os.Getwd()263	if err != nil {264		return "", false, err265	}266	cwd, err = filepath.EvalSymlinks(cwd)267	if err != nil {268		log.Fatal(err)269	}270	goroot, err := filepath.EvalSymlinks(runtime.GOROOT())271	if err != nil {272		return "", false, err273	}274	if strings.HasPrefix(cwd, goroot) {275		subdir, err := filepath.Rel(goroot, cwd)276		if err != nil {277			return "", false, err278		}279		return subdir, true, nil280	}281282	for _, p := range filepath.SplitList(build.Default.GOPATH) {283		pabs, err := filepath.EvalSymlinks(p)284		if err != nil {285			return "", false, err286		}287		if !strings.HasPrefix(cwd, pabs) {288			continue289		}290		subdir, err := filepath.Rel(pabs, cwd)291		if err == nil {292			return subdir, false, nil293		}294	}295	return "", false, fmt.Errorf(296		"working directory %q is not in either GOROOT(%q) or GOPATH(%q)",297		cwd,298		runtime.GOROOT(),299		build.Default.GOPATH,300	)301}302303func infoPlist(pkgpath string) string {304	return `<?xml version="1.0" encoding="UTF-8"?>305<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">306<plist version="1.0">307<dict>308<key>CFBundleName</key><string>golang.gotest</string>309<key>CFBundleSupportedPlatforms</key><array><string>iPhoneOS</string></array>310<key>CFBundleExecutable</key><string>gotest</string>311<key>CFBundleVersion</key><string>1.0</string>312<key>CFBundleShortVersionString</key><string>1.0</string>313<key>CFBundleIdentifier</key><string>` + bundleID + `</string>314<key>CFBundleResourceSpecification</key><string>ResourceRules.plist</string>315<key>LSRequiresIPhoneOS</key><true/>316<key>CFBundleDisplayName</key><string>gotest</string>317<key>GoExecWrapperWorkingDirectory</key><string>` + pkgpath + `</string>318</dict>319</plist>320`321}322323func entitlementsPlist() string {324	return `<?xml version="1.0" encoding="UTF-8"?>325<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">326<plist version="1.0">327<dict>328	<key>keychain-access-groups</key>329	<array><string>` + appID + `</string></array>330	<key>get-task-allow</key>331	<true/>332	<key>application-identifier</key>333	<string>` + appID + `</string>334	<key>com.apple.developer.team-identifier</key>335	<string>` + teamID + `</string>336</dict>337</plist>338`339}340341const resourceRules = `<?xml version="1.0" encoding="UTF-8"?>342<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">343<plist version="1.0">344<dict>345	<key>rules</key>346	<dict>347		<key>.*</key>348		<true/>349		<key>Info.plist</key>350		<dict>351			<key>omit</key>352			<true/>353			<key>weight</key>354			<integer>10</integer>355		</dict>356		<key>ResourceRules.plist</key>357		<dict>358			<key>omit</key>359			<true/>360			<key>weight</key>361			<integer>100</integer>362		</dict>363	</dict>364</dict>365</plist>366`

Code quality findings 18

Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer d.Close()
Basic logging; upgrade to structured logging with levels and fields
info correctness log-println
log.Println(strings.Join(os.Args, " "))
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
appdir := filepath.Join(tmpdir, "gotest.app")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
lockName := filepath.Join(os.TempDir(), "go_ios_exec-"+deviceID+".lock")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := cp(filepath.Join(appdir, "gotest"), bin); err != nil {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := os.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist(pkgpath)), 0744); err != nil {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := os.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
pkgpath = filepath.Join(pkgpath, element)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
dst := filepath.Join(dstbase, pkgpath)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
src := filepath.Join(cwd, pkgpath)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
filepath.Join(dstbase, pkgpath),
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
filepath.Join(cwd, "lib", "time", "zoneinfo.zip"),
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
runtimePath := filepath.Join(dstbase, "src", "runtime")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
filepath.Join(runtimePath, "textflag.h"),
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
filepath.Join(cwd, "src", "runtime", "textflag.h"),

Security findings 1

Ensure restrictive umask values
security permissive-file-mode
lock, err = os.OpenFile(lockName, os.O_CREATE|os.O_RDONLY, 0666)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.