mirror of
https://github.com/golang/go.git
synced 2025-10-19 19:13:18 +00:00

In Go 1.25+, strings.SplitSeq offers better
performance. Here are the benchmark results comparing
strings.Split and strings.SplitSeq in a for-loop, with the
benchmark code located in src/strings/iter_test.go:
goos: darwin
goarch: amd64
pkg: cmd/go/internal/auth
cpu: Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
ParseGitAuth/standard-8 281.4n ± 1% 218.0n ± 11% -22.54% (p=0.000 n=10)
ParseGitAuth/with_url-8 549.1n ± 1% 480.5n ± 13% -12.48% (p=0.002 n=10)
ParseGitAuth/minimal-8 235.4n ± 1% 197.3n ± 7% -16.20% (p=0.000 n=10)
ParseGitAuth/complex-8 797.6n ± 2% 805.2n ± 4% ~ (p=0.481 n=10)
ParseGitAuth/empty-8 87.48n ± 3% 63.25n ± 6% -27.71% (p=0.000 n=10)
ParseGitAuth/malformed-8 228.8n ± 1% 171.2n ± 3% -25.17% (p=0.000 n=10)
geomean 288.9n 237.7n -17.72%
│ old.txt │ new.txt │
│ B/op │ B/op vs base │
ParseGitAuth/standard-8 192.00 ± 0% 96.00 ± 0% -50.00% (p=0.000 n=10)
ParseGitAuth/with_url-8 400.0 ± 0% 288.0 ± 0% -28.00% (p=0.000 n=10)
ParseGitAuth/minimal-8 144.00 ± 0% 80.00 ± 0% -44.44% (p=0.000 n=10)
ParseGitAuth/complex-8 528.0 ± 0% 400.0 ± 0% -24.24% (p=0.000 n=10)
ParseGitAuth/empty-8 32.00 ± 0% 16.00 ± 0% -50.00% (p=0.000 n=10)
ParseGitAuth/malformed-8 176.00 ± 0% 80.00 ± 0% -54.55% (p=0.000 n=10)
geomean 179.0 102.1 -42.96%
│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
ParseGitAuth/standard-8 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.000 n=10)
ParseGitAuth/with_url-8 4.000 ± 0% 3.000 ± 0% -25.00% (p=0.000 n=10)
ParseGitAuth/minimal-8 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.000 n=10)
ParseGitAuth/complex-8 4.000 ± 0% 3.000 ± 0% -25.00% (p=0.000 n=10)
ParseGitAuth/empty-8 2.000 ± 0% 1.000 ± 0% -50.00% (p=0.000 n=10)
ParseGitAuth/malformed-8 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.000 n=10)
geomean 3.086 2.040 -33.91%
Updates #69315.
Change-Id: Id0219edea45d9658d527b863162ebe917e7821d9
GitHub-Last-Rev: 392b315e12
GitHub-Pull-Request: golang/go#75259
Reviewed-on: https://go-review.googlesource.com/c/go/+/701015
Reviewed-by: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
Reviewed-by: Keith Randall <khr@google.com>
Auto-Submit: Emmanuel Odeke <emmanuel@orijtech.com>
366 lines
8.7 KiB
Go
366 lines
8.7 KiB
Go
// 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.SplitSeq(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>
|
|
`
|