Initial commit: Go 1.23 release state
This commit is contained in:
57
misc/ios/README
Normal file
57
misc/ios/README
Normal file
@@ -0,0 +1,57 @@
|
||||
Go on iOS
|
||||
=========
|
||||
|
||||
To run the standard library tests, run all.bash as usual, but with the compiler
|
||||
set to the clang wrapper that invokes clang for iOS. For example, this command runs
|
||||
all.bash on the iOS emulator:
|
||||
|
||||
GOOS=ios GOARCH=amd64 CGO_ENABLED=1 CC_FOR_TARGET=$(pwd)/../misc/ios/clangwrap.sh ./all.bash
|
||||
|
||||
If CC_FOR_TARGET is not set when the toolchain is built (make.bash or all.bash), CC
|
||||
can be set on the command line. For example,
|
||||
|
||||
GOOS=ios GOARCH=amd64 CGO_ENABLED=1 CC=$(go env GOROOT)/misc/ios/clangwrap.sh go build
|
||||
|
||||
Setting CC is not necessary if the toolchain is built with CC_FOR_TARGET set.
|
||||
|
||||
To use the go tool to run individual programs and tests, put $GOROOT/bin into PATH to ensure
|
||||
the go_ios_$GOARCH_exec wrapper is found. For example, to run the archive/tar tests:
|
||||
|
||||
export PATH=$GOROOT/bin:$PATH
|
||||
GOOS=ios GOARCH=amd64 CGO_ENABLED=1 go test archive/tar
|
||||
|
||||
The go_ios_exec wrapper uses GOARCH to select the emulator (amd64) or the device (arm64).
|
||||
However, further setup is required to run tests or programs directly on a device.
|
||||
|
||||
First make sure you have a valid developer certificate and have setup your device properly
|
||||
to run apps signed by your developer certificate. Then install the libimobiledevice and
|
||||
ideviceinstaller tools from https://www.libimobiledevice.org/. Use the HEAD versions from
|
||||
source; the stable versions have bugs that prevents the Go exec wrapper to install and run
|
||||
apps.
|
||||
|
||||
Second, the Go exec wrapper must be told the developer account signing identity, the team
|
||||
id and a provisioned bundle id to use. They're specified with the environment variables
|
||||
GOIOS_DEV_ID, GOIOS_TEAM_ID and GOIOS_APP_ID. The detect.go program in this directory will
|
||||
attempt to auto-detect suitable values. Run it as
|
||||
|
||||
go run detect.go
|
||||
|
||||
which will output something similar to
|
||||
|
||||
export GOIOS_DEV_ID="iPhone Developer: xxx@yyy.zzz (XXXXXXXX)"
|
||||
export GOIOS_APP_ID=YYYYYYYY.some.bundle.id
|
||||
export GOIOS_TEAM_ID=ZZZZZZZZ
|
||||
|
||||
If you have multiple devices connected, specify the device UDID with the GOIOS_DEVICE_ID
|
||||
variable. Use `idevice_id -l` to list all available UDIDs. Then, setting GOARCH to arm64
|
||||
will select the device:
|
||||
|
||||
GOOS=ios GOARCH=arm64 CGO_ENABLED=1 CC_FOR_TARGET=$(pwd)/../misc/ios/clangwrap.sh ./all.bash
|
||||
|
||||
Note that the go_darwin_$GOARCH_exec wrapper uninstalls any existing app identified by
|
||||
the bundle id before installing a new app. If the uninstalled app is the last app by
|
||||
the developer identity, the device might also remove the permission to run apps from
|
||||
that developer, and the exec wrapper will fail to install the new app. To avoid that,
|
||||
install another app with the same developer identity but with a different bundle id.
|
||||
That way, the permission to install apps is held on to while the primary app is
|
||||
uninstalled.
|
||||
23
misc/ios/clangwrap.sh
Executable file
23
misc/ios/clangwrap.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script configures clang to target the iOS simulator. If you'd like to
|
||||
# build for real iOS devices, change SDK to "iphoneos" and PLATFORM to "ios".
|
||||
# This uses the latest available iOS SDK, which is recommended. To select a
|
||||
# specific SDK, run 'xcodebuild -showsdks' to see the available SDKs and replace
|
||||
# iphonesimulator with one of them.
|
||||
|
||||
SDK=iphonesimulator
|
||||
PLATFORM=ios-simulator
|
||||
|
||||
if [ "$GOARCH" == "arm64" ]; then
|
||||
CLANGARCH="arm64"
|
||||
else
|
||||
CLANGARCH="x86_64"
|
||||
fi
|
||||
|
||||
SDK_PATH=`xcrun --sdk $SDK --show-sdk-path`
|
||||
|
||||
# cmd/cgo doesn't support llvm-gcc-4.2, so we have to use clang.
|
||||
CLANG=`xcrun --sdk $SDK --find clang`
|
||||
|
||||
exec "$CLANG" -arch $CLANGARCH -isysroot "$SDK_PATH" -m${PLATFORM}-version-min=12.0 "$@"
|
||||
133
misc/ios/detect.go
Normal file
133
misc/ios/detect.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
// detect attempts to autodetect the correct
|
||||
// values of the environment variables
|
||||
// used by go_ios_exec.
|
||||
// detect shells out to ideviceinfo, a third party program that can
|
||||
// be obtained by following the instructions at
|
||||
// https://github.com/libimobiledevice/libimobiledevice.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
udids := getLines(exec.Command("idevice_id", "-l"))
|
||||
if len(udids) == 0 {
|
||||
fail("no udid found; is a device connected?")
|
||||
}
|
||||
|
||||
mps := detectMobileProvisionFiles(udids)
|
||||
if len(mps) == 0 {
|
||||
fail("did not find mobile provision matching device udids %q", udids)
|
||||
}
|
||||
|
||||
fmt.Println("# Available provisioning profiles below.")
|
||||
fmt.Println("# NOTE: Any existing app on the device with the app id specified by GOIOS_APP_ID")
|
||||
fmt.Println("# will be overwritten when running Go programs.")
|
||||
for _, mp := range mps {
|
||||
fmt.Println()
|
||||
f, err := os.CreateTemp("", "go_ios_detect_")
|
||||
check(err)
|
||||
fname := f.Name()
|
||||
defer os.Remove(fname)
|
||||
|
||||
out := output(parseMobileProvision(mp))
|
||||
_, err = f.Write(out)
|
||||
check(err)
|
||||
check(f.Close())
|
||||
|
||||
cert, err := plistExtract(fname, "DeveloperCertificates:0")
|
||||
check(err)
|
||||
pcert, err := x509.ParseCertificate(cert)
|
||||
check(err)
|
||||
fmt.Printf("export GOIOS_DEV_ID=\"%s\"\n", pcert.Subject.CommonName)
|
||||
|
||||
appID, err := plistExtract(fname, "Entitlements:application-identifier")
|
||||
check(err)
|
||||
fmt.Printf("export GOIOS_APP_ID=%s\n", appID)
|
||||
|
||||
teamID, err := plistExtract(fname, "Entitlements:com.apple.developer.team-identifier")
|
||||
check(err)
|
||||
fmt.Printf("export GOIOS_TEAM_ID=%s\n", teamID)
|
||||
}
|
||||
}
|
||||
|
||||
func detectMobileProvisionFiles(udids [][]byte) []string {
|
||||
cmd := exec.Command("mdfind", "-name", ".mobileprovision")
|
||||
lines := getLines(cmd)
|
||||
|
||||
var files []string
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
xmlLines := getLines(parseMobileProvision(string(line)))
|
||||
matches := 0
|
||||
for _, udid := range udids {
|
||||
for _, xmlLine := range xmlLines {
|
||||
if bytes.Contains(xmlLine, udid) {
|
||||
matches++
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches == len(udids) {
|
||||
files = append(files, string(line))
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func parseMobileProvision(fname string) *exec.Cmd {
|
||||
return exec.Command("security", "cms", "-D", "-i", string(fname))
|
||||
}
|
||||
|
||||
func plistExtract(fname string, path string) ([]byte, error) {
|
||||
out, err := exec.Command("/usr/libexec/PlistBuddy", "-c", "Print "+path, fname).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.TrimSpace(out), nil
|
||||
}
|
||||
|
||||
func getLines(cmd *exec.Cmd) [][]byte {
|
||||
out := output(cmd)
|
||||
lines := bytes.Split(out, []byte("\n"))
|
||||
// Skip the empty line at the end.
|
||||
if len(lines[len(lines)-1]) == 0 {
|
||||
lines = lines[:len(lines)-1]
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func output(cmd *exec.Cmd) []byte {
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Println(strings.Join(cmd.Args, "\n"))
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func fail(msg string, v ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, msg, v...)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
os.Exit(1)
|
||||
}
|
||||
366
misc/ios/go_ios_exec.go
Normal file
366
misc/ios/go_ios_exec.go
Normal file
@@ -0,0 +1,366 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This program can be used as go_ios_$GOARCH_exec by the Go tool. It executes
|
||||
// binaries on the iOS Simulator using the XCode toolchain.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
var tmpdir string
|
||||
|
||||
var (
|
||||
devID string
|
||||
appID string
|
||||
teamID string
|
||||
bundleID string
|
||||
deviceID string
|
||||
)
|
||||
|
||||
// lock is a file lock to serialize iOS runs. It is global to avoid the
|
||||
// garbage collector finalizing it, closing the file and releasing the
|
||||
// lock prematurely.
|
||||
var lock *os.File
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("go_ios_exec: ")
|
||||
if debug {
|
||||
log.Println(strings.Join(os.Args, " "))
|
||||
}
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatal("usage: go_ios_exec a.out")
|
||||
}
|
||||
|
||||
// For compatibility with the old builders, use a fallback bundle ID
|
||||
bundleID = "golang.gotest"
|
||||
|
||||
exitCode, err := runMain()
|
||||
if err != nil {
|
||||
log.Fatalf("%v\n", err)
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func runMain() (int, error) {
|
||||
var err error
|
||||
tmpdir, err = os.MkdirTemp("", "go_ios_exec_")
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
if !debug {
|
||||
defer os.RemoveAll(tmpdir)
|
||||
}
|
||||
|
||||
appdir := filepath.Join(tmpdir, "gotest.app")
|
||||
os.RemoveAll(appdir)
|
||||
|
||||
if err := assembleApp(appdir, os.Args[1]); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
// This wrapper uses complicated machinery to run iOS binaries. It
|
||||
// works, but only when running one binary at a time.
|
||||
// Use a file lock to make sure only one wrapper is running at a time.
|
||||
//
|
||||
// The lock file is never deleted, to avoid concurrent locks on distinct
|
||||
// files with the same path.
|
||||
lockName := filepath.Join(os.TempDir(), "go_ios_exec-"+deviceID+".lock")
|
||||
lock, err = os.OpenFile(lockName, os.O_CREATE|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
err = runOnSimulator(appdir)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func runOnSimulator(appdir string) error {
|
||||
if err := installSimulator(appdir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runSimulator(appdir, bundleID, os.Args[2:])
|
||||
}
|
||||
|
||||
func assembleApp(appdir, bin string) error {
|
||||
if err := os.MkdirAll(appdir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cp(filepath.Join(appdir, "gotest"), bin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkgpath, err := copyLocalData(appdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist")
|
||||
if err := os.WriteFile(entitlementsPath, []byte(entitlementsPlist()), 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist(pkgpath)), 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func installSimulator(appdir string) error {
|
||||
cmd := exec.Command(
|
||||
"xcrun", "simctl", "install",
|
||||
"booted", // Install to the booted simulator.
|
||||
appdir,
|
||||
)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
os.Stderr.Write(out)
|
||||
return fmt.Errorf("xcrun simctl install booted %q: %v", appdir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSimulator(appdir, bundleID string, args []string) error {
|
||||
xcrunArgs := []string{"simctl", "spawn",
|
||||
"booted",
|
||||
appdir + "/gotest",
|
||||
}
|
||||
xcrunArgs = append(xcrunArgs, args...)
|
||||
cmd := exec.Command("xcrun", xcrunArgs...)
|
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("xcrun simctl launch booted %q: %v", bundleID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyLocalDir(dst, src string) error {
|
||||
if err := os.Mkdir(dst, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
fi, err := d.Readdir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range fi {
|
||||
if f.IsDir() {
|
||||
if f.Name() == "testdata" {
|
||||
if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cp(dst, src string) error {
|
||||
out, err := exec.Command("cp", "-a", src, dst).CombinedOutput()
|
||||
if err != nil {
|
||||
os.Stderr.Write(out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func copyLocalData(dstbase string) (pkgpath string, err error) {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
finalPkgpath, underGoRoot, err := subdir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cwd = strings.TrimSuffix(cwd, finalPkgpath)
|
||||
|
||||
// Copy all immediate files and testdata directories between
|
||||
// the package being tested and the source root.
|
||||
pkgpath = ""
|
||||
for _, element := range strings.Split(finalPkgpath, string(filepath.Separator)) {
|
||||
if debug {
|
||||
log.Printf("copying %s", pkgpath)
|
||||
}
|
||||
pkgpath = filepath.Join(pkgpath, element)
|
||||
dst := filepath.Join(dstbase, pkgpath)
|
||||
src := filepath.Join(cwd, pkgpath)
|
||||
if err := copyLocalDir(dst, src); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if underGoRoot {
|
||||
// Copy timezone file.
|
||||
//
|
||||
// Typical apps have the zoneinfo.zip in the root of their app bundle,
|
||||
// read by the time package as the working directory at initialization.
|
||||
// As we move the working directory to the GOROOT pkg directory, we
|
||||
// install the zoneinfo.zip file in the pkgpath.
|
||||
err := cp(
|
||||
filepath.Join(dstbase, pkgpath),
|
||||
filepath.Join(cwd, "lib", "time", "zoneinfo.zip"),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Copy src/runtime/textflag.h for (at least) Test386EndToEnd in
|
||||
// cmd/asm/internal/asm.
|
||||
runtimePath := filepath.Join(dstbase, "src", "runtime")
|
||||
if err := os.MkdirAll(runtimePath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = cp(
|
||||
filepath.Join(runtimePath, "textflag.h"),
|
||||
filepath.Join(cwd, "src", "runtime", "textflag.h"),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return finalPkgpath, nil
|
||||
}
|
||||
|
||||
// subdir determines the package based on the current working directory,
|
||||
// and returns the path to the package source relative to $GOROOT (or $GOPATH).
|
||||
func subdir() (pkgpath string, underGoRoot bool, err error) {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
cwd, err = filepath.EvalSymlinks(cwd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
goroot, err := filepath.EvalSymlinks(runtime.GOROOT())
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if strings.HasPrefix(cwd, goroot) {
|
||||
subdir, err := filepath.Rel(goroot, cwd)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return subdir, true, nil
|
||||
}
|
||||
|
||||
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
||||
pabs, err := filepath.EvalSymlinks(p)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if !strings.HasPrefix(cwd, pabs) {
|
||||
continue
|
||||
}
|
||||
subdir, err := filepath.Rel(pabs, cwd)
|
||||
if err == nil {
|
||||
return subdir, false, nil
|
||||
}
|
||||
}
|
||||
return "", false, fmt.Errorf(
|
||||
"working directory %q is not in either GOROOT(%q) or GOPATH(%q)",
|
||||
cwd,
|
||||
runtime.GOROOT(),
|
||||
build.Default.GOPATH,
|
||||
)
|
||||
}
|
||||
|
||||
func infoPlist(pkgpath string) string {
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key><string>golang.gotest</string>
|
||||
<key>CFBundleSupportedPlatforms</key><array><string>iPhoneOS</string></array>
|
||||
<key>CFBundleExecutable</key><string>gotest</string>
|
||||
<key>CFBundleVersion</key><string>1.0</string>
|
||||
<key>CFBundleShortVersionString</key><string>1.0</string>
|
||||
<key>CFBundleIdentifier</key><string>` + bundleID + `</string>
|
||||
<key>CFBundleResourceSpecification</key><string>ResourceRules.plist</string>
|
||||
<key>LSRequiresIPhoneOS</key><true/>
|
||||
<key>CFBundleDisplayName</key><string>gotest</string>
|
||||
<key>GoExecWrapperWorkingDirectory</key><string>` + pkgpath + `</string>
|
||||
</dict>
|
||||
</plist>
|
||||
`
|
||||
}
|
||||
|
||||
func entitlementsPlist() string {
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>keychain-access-groups</key>
|
||||
<array><string>` + appID + `</string></array>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
<key>application-identifier</key>
|
||||
<string>` + appID + `</string>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>` + teamID + `</string>
|
||||
</dict>
|
||||
</plist>
|
||||
`
|
||||
}
|
||||
|
||||
const resourceRules = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>.*</key>
|
||||
<true/>
|
||||
<key>Info.plist</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
<key>ResourceRules.plist</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<integer>100</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
`
|
||||
Reference in New Issue
Block a user