Initial commit: Go 1.23 release state
This commit is contained in:
45
misc/cgo/gmp/fib.go
Normal file
45
misc/cgo/gmp/fib.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2009 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
|
||||
|
||||
// Compute Fibonacci numbers with two goroutines
|
||||
// that pass integers back and forth. No actual
|
||||
// concurrency, just threads and synchronization
|
||||
// and foreign code on multiple pthreads.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
big "."
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func fibber(c chan *big.Int, out chan string, n int64) {
|
||||
// Keep the fibbers in dedicated operating system
|
||||
// threads, so that this program tests coordination
|
||||
// between pthreads and not just goroutines.
|
||||
runtime.LockOSThread()
|
||||
|
||||
i := big.NewInt(n)
|
||||
if n == 0 {
|
||||
c <- i
|
||||
}
|
||||
for {
|
||||
j := <-c
|
||||
out <- j.String()
|
||||
i.Add(i, j)
|
||||
c <- i
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan *big.Int)
|
||||
out := make(chan string)
|
||||
go fibber(c, out, 0)
|
||||
go fibber(c, out, 1)
|
||||
for i := 0; i < 200; i++ {
|
||||
println(<-out)
|
||||
}
|
||||
}
|
||||
379
misc/cgo/gmp/gmp.go
Normal file
379
misc/cgo/gmp/gmp.go
Normal file
@@ -0,0 +1,379 @@
|
||||
// Copyright 2009 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.
|
||||
|
||||
/*
|
||||
An example of wrapping a C library in Go. This is the GNU
|
||||
multiprecision library gmp's integer type mpz_t wrapped to look like
|
||||
the Go package big's integer type Int.
|
||||
|
||||
This is a syntactically valid Go program—it can be parsed with the Go
|
||||
parser and processed by godoc—but it is not compiled directly by gc.
|
||||
Instead, a separate tool, cgo, processes it to produce three output
|
||||
files. The first two, 6g.go and 6c.c, are a Go source file for 6g and
|
||||
a C source file for 6c; both compile as part of the named package
|
||||
(gmp, in this example). The third, gcc.c, is a C source file for gcc;
|
||||
it compiles into a shared object (.so) that is dynamically linked into
|
||||
any 6.out that imports the first two files.
|
||||
|
||||
The stanza
|
||||
|
||||
// #include <gmp.h>
|
||||
import "C"
|
||||
|
||||
is a signal to cgo. The doc comment on the import of "C" provides
|
||||
additional context for the C file. Here it is just a single #include
|
||||
but it could contain arbitrary C definitions to be imported and used.
|
||||
|
||||
Cgo recognizes any use of a qualified identifier C.xxx and uses gcc to
|
||||
find the definition of xxx. If xxx is a type, cgo replaces C.xxx with
|
||||
a Go translation. C arithmetic types translate to precisely-sized Go
|
||||
arithmetic types. A C struct translates to a Go struct, field by
|
||||
field; unrepresentable fields are replaced with opaque byte arrays. A
|
||||
C union translates into a struct containing the first union member and
|
||||
perhaps additional padding. C arrays become Go arrays. C pointers
|
||||
become Go pointers. C function pointers become Go's uintptr.
|
||||
C void pointers become Go's unsafe.Pointer.
|
||||
|
||||
For example, mpz_t is defined in <gmp.h> as:
|
||||
|
||||
typedef unsigned long int mp_limb_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int _mp_alloc;
|
||||
int _mp_size;
|
||||
mp_limb_t *_mp_d;
|
||||
} __mpz_struct;
|
||||
|
||||
typedef __mpz_struct mpz_t[1];
|
||||
|
||||
Cgo generates:
|
||||
|
||||
type _C_int int32
|
||||
type _C_mp_limb_t uint64
|
||||
type _C___mpz_struct struct {
|
||||
_mp_alloc _C_int;
|
||||
_mp_size _C_int;
|
||||
_mp_d *_C_mp_limb_t;
|
||||
}
|
||||
type _C_mpz_t [1]_C___mpz_struct
|
||||
|
||||
and then replaces each occurrence of a type C.xxx with _C_xxx.
|
||||
|
||||
If xxx is data, cgo arranges for C.xxx to refer to the C variable,
|
||||
with the type translated as described above. To do this, cgo must
|
||||
introduce a Go variable that points at the C variable (the linker can
|
||||
be told to initialize this pointer). For example, if the gmp library
|
||||
provided
|
||||
|
||||
mpz_t zero;
|
||||
|
||||
then cgo would rewrite a reference to C.zero by introducing
|
||||
|
||||
var _C_zero *C.mpz_t
|
||||
|
||||
and then replacing all instances of C.zero with (*_C_zero).
|
||||
|
||||
Cgo's most interesting translation is for functions. If xxx is a C
|
||||
function, then cgo rewrites C.xxx into a new function _C_xxx that
|
||||
calls the C xxx in a standard pthread. The new function translates
|
||||
its arguments, calls xxx, and translates the return value.
|
||||
|
||||
Translation of parameters and the return value follows the type
|
||||
translation above except that arrays passed as parameters translate
|
||||
explicitly in Go to pointers to arrays, as they do (implicitly) in C.
|
||||
|
||||
Garbage collection is the big problem. It is fine for the Go world to
|
||||
have pointers into the C world and to free those pointers when they
|
||||
are no longer needed. To help, the Go code can define Go objects
|
||||
holding the C pointers and use runtime.SetFinalizer on those Go objects.
|
||||
|
||||
It is much more difficult for the C world to have pointers into the Go
|
||||
world, because the Go garbage collector is unaware of the memory
|
||||
allocated by C. The most important consideration is not to
|
||||
constrain future implementations, so the rule is that Go code can
|
||||
hand a Go pointer to C code but must separately arrange for
|
||||
Go to hang on to a reference to the pointer until C is done with it.
|
||||
*/
|
||||
package gmp
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lgmp
|
||||
#include <gmp.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// gmp 5.0.0+ changed the type of the 3rd argument to mp_bitcnt_t,
|
||||
// so, to support older versions, we wrap these two functions.
|
||||
void _mpz_mul_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) {
|
||||
mpz_mul_2exp(a, b, n);
|
||||
}
|
||||
void _mpz_div_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) {
|
||||
mpz_div_2exp(a, b, n);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
* one of a kind
|
||||
*/
|
||||
|
||||
// An Int represents a signed multi-precision integer.
|
||||
// The zero value for an Int represents the value 0.
|
||||
type Int struct {
|
||||
i C.mpz_t
|
||||
init bool
|
||||
}
|
||||
|
||||
// NewInt returns a new Int initialized to x.
|
||||
func NewInt(x int64) *Int { return new(Int).SetInt64(x) }
|
||||
|
||||
// Int promises that the zero value is a 0, but in gmp
|
||||
// the zero value is a crash. To bridge the gap, the
|
||||
// init bool says whether this is a valid gmp value.
|
||||
// doinit initializes z.i if it needs it. This is not inherent
|
||||
// to FFI, just a mismatch between Go's convention of
|
||||
// making zero values useful and gmp's decision not to.
|
||||
func (z *Int) doinit() {
|
||||
if z.init {
|
||||
return
|
||||
}
|
||||
z.init = true
|
||||
C.mpz_init(&z.i[0])
|
||||
}
|
||||
|
||||
// Bytes returns z's representation as a big-endian byte array.
|
||||
func (z *Int) Bytes() []byte {
|
||||
b := make([]byte, (z.Len()+7)/8)
|
||||
n := C.size_t(len(b))
|
||||
C.mpz_export(unsafe.Pointer(&b[0]), &n, 1, 1, 1, 0, &z.i[0])
|
||||
return b[0:n]
|
||||
}
|
||||
|
||||
// Len returns the length of z in bits. 0 is considered to have length 1.
|
||||
func (z *Int) Len() int {
|
||||
z.doinit()
|
||||
return int(C.mpz_sizeinbase(&z.i[0], 2))
|
||||
}
|
||||
|
||||
// Set sets z = x and returns z.
|
||||
func (z *Int) Set(x *Int) *Int {
|
||||
z.doinit()
|
||||
C.mpz_set(&z.i[0], &x.i[0])
|
||||
return z
|
||||
}
|
||||
|
||||
// SetBytes interprets b as the bytes of a big-endian integer
|
||||
// and sets z to that value.
|
||||
func (z *Int) SetBytes(b []byte) *Int {
|
||||
z.doinit()
|
||||
if len(b) == 0 {
|
||||
z.SetInt64(0)
|
||||
} else {
|
||||
C.mpz_import(&z.i[0], C.size_t(len(b)), 1, 1, 1, 0, unsafe.Pointer(&b[0]))
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
// SetInt64 sets z = x and returns z.
|
||||
func (z *Int) SetInt64(x int64) *Int {
|
||||
z.doinit()
|
||||
// TODO(rsc): more work on 32-bit platforms
|
||||
C.mpz_set_si(&z.i[0], C.long(x))
|
||||
return z
|
||||
}
|
||||
|
||||
// SetString interprets s as a number in the given base
|
||||
// and sets z to that value. The base must be in the range [2,36].
|
||||
// SetString returns an error if s cannot be parsed or the base is invalid.
|
||||
func (z *Int) SetString(s string, base int) error {
|
||||
z.doinit()
|
||||
if base < 2 || base > 36 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
p := C.CString(s)
|
||||
defer C.free(unsafe.Pointer(p))
|
||||
if C.mpz_set_str(&z.i[0], p, C.int(base)) < 0 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the decimal representation of z.
|
||||
func (z *Int) String() string {
|
||||
if z == nil {
|
||||
return "nil"
|
||||
}
|
||||
z.doinit()
|
||||
p := C.mpz_get_str(nil, 10, &z.i[0])
|
||||
s := C.GoString(p)
|
||||
C.free(unsafe.Pointer(p))
|
||||
return s
|
||||
}
|
||||
|
||||
func (z *Int) destroy() {
|
||||
if z.init {
|
||||
C.mpz_clear(&z.i[0])
|
||||
}
|
||||
z.init = false
|
||||
}
|
||||
|
||||
/*
|
||||
* arithmetic
|
||||
*/
|
||||
|
||||
// Add sets z = x + y and returns z.
|
||||
func (z *Int) Add(x, y *Int) *Int {
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
z.doinit()
|
||||
C.mpz_add(&z.i[0], &x.i[0], &y.i[0])
|
||||
return z
|
||||
}
|
||||
|
||||
// Sub sets z = x - y and returns z.
|
||||
func (z *Int) Sub(x, y *Int) *Int {
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
z.doinit()
|
||||
C.mpz_sub(&z.i[0], &x.i[0], &y.i[0])
|
||||
return z
|
||||
}
|
||||
|
||||
// Mul sets z = x * y and returns z.
|
||||
func (z *Int) Mul(x, y *Int) *Int {
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
z.doinit()
|
||||
C.mpz_mul(&z.i[0], &x.i[0], &y.i[0])
|
||||
return z
|
||||
}
|
||||
|
||||
// Div sets z = x / y, rounding toward zero, and returns z.
|
||||
func (z *Int) Div(x, y *Int) *Int {
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
z.doinit()
|
||||
C.mpz_tdiv_q(&z.i[0], &x.i[0], &y.i[0])
|
||||
return z
|
||||
}
|
||||
|
||||
// Mod sets z = x % y and returns z.
|
||||
// Like the result of the Go % operator, z has the same sign as x.
|
||||
func (z *Int) Mod(x, y *Int) *Int {
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
z.doinit()
|
||||
C.mpz_tdiv_r(&z.i[0], &x.i[0], &y.i[0])
|
||||
return z
|
||||
}
|
||||
|
||||
// Lsh sets z = x << s and returns z.
|
||||
func (z *Int) Lsh(x *Int, s uint) *Int {
|
||||
x.doinit()
|
||||
z.doinit()
|
||||
C._mpz_mul_2exp(&z.i[0], &x.i[0], C.ulong(s))
|
||||
return z
|
||||
}
|
||||
|
||||
// Rsh sets z = x >> s and returns z.
|
||||
func (z *Int) Rsh(x *Int, s uint) *Int {
|
||||
x.doinit()
|
||||
z.doinit()
|
||||
C._mpz_div_2exp(&z.i[0], &x.i[0], C.ulong(s))
|
||||
return z
|
||||
}
|
||||
|
||||
// Exp sets z = x^y % m and returns z.
|
||||
// If m == nil, Exp sets z = x^y.
|
||||
func (z *Int) Exp(x, y, m *Int) *Int {
|
||||
m.doinit()
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
z.doinit()
|
||||
if m == nil {
|
||||
C.mpz_pow_ui(&z.i[0], &x.i[0], C.mpz_get_ui(&y.i[0]))
|
||||
} else {
|
||||
C.mpz_powm(&z.i[0], &x.i[0], &y.i[0], &m.i[0])
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
func (z *Int) Int64() int64 {
|
||||
if !z.init {
|
||||
return 0
|
||||
}
|
||||
return int64(C.mpz_get_si(&z.i[0]))
|
||||
}
|
||||
|
||||
// Neg sets z = -x and returns z.
|
||||
func (z *Int) Neg(x *Int) *Int {
|
||||
x.doinit()
|
||||
z.doinit()
|
||||
C.mpz_neg(&z.i[0], &x.i[0])
|
||||
return z
|
||||
}
|
||||
|
||||
// Abs sets z to the absolute value of x and returns z.
|
||||
func (z *Int) Abs(x *Int) *Int {
|
||||
x.doinit()
|
||||
z.doinit()
|
||||
C.mpz_abs(&z.i[0], &x.i[0])
|
||||
return z
|
||||
}
|
||||
|
||||
/*
|
||||
* functions without a clear receiver
|
||||
*/
|
||||
|
||||
// CmpInt compares x and y. The result is
|
||||
//
|
||||
// -1 if x < y
|
||||
// 0 if x == y
|
||||
// +1 if x > y
|
||||
func CmpInt(x, y *Int) int {
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
switch cmp := C.mpz_cmp(&x.i[0], &y.i[0]); {
|
||||
case cmp < 0:
|
||||
return -1
|
||||
case cmp == 0:
|
||||
return 0
|
||||
}
|
||||
return +1
|
||||
}
|
||||
|
||||
// DivModInt sets q = x / y and r = x % y.
|
||||
func DivModInt(q, r, x, y *Int) {
|
||||
q.doinit()
|
||||
r.doinit()
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
C.mpz_tdiv_qr(&q.i[0], &r.i[0], &x.i[0], &y.i[0])
|
||||
}
|
||||
|
||||
// GcdInt sets d to the greatest common divisor of a and b,
|
||||
// which must be positive numbers.
|
||||
// If x and y are not nil, GcdInt sets x and y such that d = a*x + b*y.
|
||||
// If either a or b is not positive, GcdInt sets d = x = y = 0.
|
||||
func GcdInt(d, x, y, a, b *Int) {
|
||||
d.doinit()
|
||||
x.doinit()
|
||||
y.doinit()
|
||||
a.doinit()
|
||||
b.doinit()
|
||||
C.mpz_gcdext(&d.i[0], &x.i[0], &y.i[0], &a.i[0], &b.i[0])
|
||||
}
|
||||
|
||||
// ProbablyPrime performs n Miller-Rabin tests to check whether z is prime.
|
||||
// If it returns true, z is prime with probability 1 - 1/4^n.
|
||||
// If it returns false, z is not prime.
|
||||
func (z *Int) ProbablyPrime(n int) bool {
|
||||
z.doinit()
|
||||
return int(C.mpz_probab_prime_p(&z.i[0], C.int(n))) > 0
|
||||
}
|
||||
73
misc/cgo/gmp/pi.go
Normal file
73
misc/cgo/gmp/pi.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2009 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
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
big "."
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
tmp1 = big.NewInt(0)
|
||||
tmp2 = big.NewInt(0)
|
||||
numer = big.NewInt(1)
|
||||
accum = big.NewInt(0)
|
||||
denom = big.NewInt(1)
|
||||
ten = big.NewInt(10)
|
||||
)
|
||||
|
||||
func extractDigit() int64 {
|
||||
if big.CmpInt(numer, accum) > 0 {
|
||||
return -1
|
||||
}
|
||||
tmp1.Lsh(numer, 1).Add(tmp1, numer).Add(tmp1, accum)
|
||||
big.DivModInt(tmp1, tmp2, tmp1, denom)
|
||||
tmp2.Add(tmp2, numer)
|
||||
if big.CmpInt(tmp2, denom) >= 0 {
|
||||
return -1
|
||||
}
|
||||
return tmp1.Int64()
|
||||
}
|
||||
|
||||
func nextTerm(k int64) {
|
||||
y2 := k*2 + 1
|
||||
accum.Add(accum, tmp1.Lsh(numer, 1))
|
||||
accum.Mul(accum, tmp1.SetInt64(y2))
|
||||
numer.Mul(numer, tmp1.SetInt64(k))
|
||||
denom.Mul(denom, tmp1.SetInt64(y2))
|
||||
}
|
||||
|
||||
func eliminateDigit(d int64) {
|
||||
accum.Sub(accum, tmp1.Mul(denom, tmp1.SetInt64(d)))
|
||||
accum.Mul(accum, ten)
|
||||
numer.Mul(numer, ten)
|
||||
}
|
||||
|
||||
func main() {
|
||||
i := 0
|
||||
k := int64(0)
|
||||
for {
|
||||
d := int64(-1)
|
||||
for d < 0 {
|
||||
k++
|
||||
nextTerm(k)
|
||||
d = extractDigit()
|
||||
}
|
||||
eliminateDigit(d)
|
||||
fmt.Printf("%c", d+'0')
|
||||
|
||||
if i++; i%50 == 0 {
|
||||
fmt.Printf("\n")
|
||||
if i >= 1000 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.NumCgoCall(), numer.Len(), accum.Len(), denom.Len())
|
||||
}
|
||||
8
misc/chrome/gophertool/README.txt
Normal file
8
misc/chrome/gophertool/README.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
To install:
|
||||
|
||||
1) chrome://extensions/
|
||||
2) click "[+] Developer Mode" in top right
|
||||
3) "Load unpacked extension..."
|
||||
4) pick $GOROOT/misc/chrome/gophertool
|
||||
|
||||
Done. It'll now auto-reload from source.
|
||||
12
misc/chrome/gophertool/background.html
Normal file
12
misc/chrome/gophertool/background.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 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.
|
||||
-->
|
||||
<head>
|
||||
<script src="gopher.js"></script>
|
||||
<script src="background.js"></script>
|
||||
</head>
|
||||
</html>
|
||||
|
||||
9
misc/chrome/gophertool/background.js
Normal file
9
misc/chrome/gophertool/background.js
Normal file
@@ -0,0 +1,9 @@
|
||||
chrome.omnibox.onInputEntered.addListener(function(t) {
|
||||
var url = urlForInput(t);
|
||||
if (url) {
|
||||
chrome.tabs.query({ "active": true, "currentWindow": true }, function(tab) {
|
||||
if (!tab) return;
|
||||
chrome.tabs.update(tab.id, { "url": url, "selected": true });
|
||||
});
|
||||
}
|
||||
});
|
||||
41
misc/chrome/gophertool/gopher.js
Normal file
41
misc/chrome/gophertool/gopher.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
var numericRE = /^\d+$/;
|
||||
var commitRE = /^(?:\d+:)?([0-9a-f]{6,40})$/; // e.g "8486:ab29d2698a47" or "ab29d2698a47"
|
||||
var gerritChangeIdRE = /^I[0-9a-f]{4,40}$/; // e.g. Id69c00d908d18151486007ec03da5495b34b05f5
|
||||
var pkgRE = /^[a-z0-9_\/]+$/;
|
||||
|
||||
function urlForInput(t) {
|
||||
if (!t) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (numericRE.test(t)) {
|
||||
if (t < 150000) {
|
||||
// We could use the golang.org/cl/ handler here, but
|
||||
// avoid some redirect latency and go right there, since
|
||||
// one is easy. (no server-side mapping)
|
||||
return "https://github.com/golang/go/issues/" + t;
|
||||
}
|
||||
return "https://golang.org/cl/" + t;
|
||||
}
|
||||
|
||||
if (gerritChangeIdRE.test(t)) {
|
||||
return "https://golang.org/cl/" + t;
|
||||
}
|
||||
|
||||
var match = commitRE.exec(t);
|
||||
if (match) {
|
||||
return "https://golang.org/change/" + match[1];
|
||||
}
|
||||
|
||||
if (pkgRE.test(t)) {
|
||||
// TODO: make this smarter, using a list of packages + substring matches.
|
||||
// Get the list from godoc itself in JSON format?
|
||||
return "https://golang.org/pkg/" + t;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
BIN
misc/chrome/gophertool/gopher.png
Normal file
BIN
misc/chrome/gophertool/gopher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
20
misc/chrome/gophertool/manifest.json
Normal file
20
misc/chrome/gophertool/manifest.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "Hacking Gopher",
|
||||
"version": "1.0",
|
||||
"manifest_version": 2,
|
||||
"description": "Go Hacking utility",
|
||||
"background": {
|
||||
"page": "background.html"
|
||||
},
|
||||
"browser_action": {
|
||||
"default_icon": "gopher.png",
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"omnibox": { "keyword": "golang" },
|
||||
"icons": {
|
||||
"16": "gopher.png"
|
||||
},
|
||||
"permissions": [
|
||||
"tabs"
|
||||
]
|
||||
}
|
||||
21
misc/chrome/gophertool/popup.html
Normal file
21
misc/chrome/gophertool/popup.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 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.
|
||||
-->
|
||||
<head>
|
||||
<script src="gopher.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</head>
|
||||
<body style='margin: 0.5em; font-family: sans;'>
|
||||
<small><a href="#" url="https://golang.org/issue">issue</a>,
|
||||
<a href="#" url="https://golang.org/cl">codereview</a>,
|
||||
<a href="#" url="https://golang.org/change">commit</a>, or
|
||||
<a href="#" url="https://golang.org/pkg/">pkg</a> id/name:</small>
|
||||
<form style='margin: 0' id='navform'><nobr><input id="inputbox" size=10 tabindex=1 /><input type="submit" value="go" /></nobr></form>
|
||||
<small>Also: <a href="#" url="https://build.golang.org">buildbots</a>
|
||||
<a href="#" url="https://github.com/golang/go">GitHub</a>
|
||||
</small>
|
||||
</body>
|
||||
</html>
|
||||
46
misc/chrome/gophertool/popup.js
Normal file
46
misc/chrome/gophertool/popup.js
Normal file
@@ -0,0 +1,46 @@
|
||||
function openURL(url) {
|
||||
chrome.tabs.create({ "url": url })
|
||||
}
|
||||
|
||||
function addLinks() {
|
||||
var links = document.getElementsByTagName("a");
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var url = links[i].getAttribute("url");
|
||||
if (url)
|
||||
links[i].addEventListener("click", function () {
|
||||
openURL(this.getAttribute("url"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
addLinks();
|
||||
console.log("hacking gopher pop-up loaded.");
|
||||
document.getElementById("inputbox").focus();
|
||||
});
|
||||
|
||||
window.addEventListener("submit", function () {
|
||||
console.log("submitting form");
|
||||
var box = document.getElementById("inputbox");
|
||||
box.focus();
|
||||
|
||||
var t = box.value;
|
||||
if (t == "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
var success = function(url) {
|
||||
console.log("matched " + t + " to: " + url)
|
||||
box.value = "";
|
||||
openURL(url);
|
||||
return false; // cancel form submission
|
||||
};
|
||||
|
||||
var url = urlForInput(t);
|
||||
if (url) {
|
||||
return success(url);
|
||||
}
|
||||
|
||||
console.log("no match for text: " + t)
|
||||
return false;
|
||||
});
|
||||
5
misc/editors
Normal file
5
misc/editors
Normal file
@@ -0,0 +1,5 @@
|
||||
For information about plugins and other support for Go in editors and shells,
|
||||
see this page on the Go Wiki:
|
||||
|
||||
https://golang.org/wiki/IDEsAndTextEditorPlugins
|
||||
|
||||
6
misc/go.mod
Normal file
6
misc/go.mod
Normal file
@@ -0,0 +1,6 @@
|
||||
// Module misc contains binaries that pertain to specific platforms
|
||||
// (Android, iOS, and WebAssembly), as well as some miscellaneous
|
||||
// tests and tools.
|
||||
module misc
|
||||
|
||||
go 1.22
|
||||
25
misc/go_android_exec/README
Normal file
25
misc/go_android_exec/README
Normal file
@@ -0,0 +1,25 @@
|
||||
Android
|
||||
=======
|
||||
|
||||
For details on developing Go for Android, see the documentation in the
|
||||
mobile subrepository:
|
||||
|
||||
https://github.com/golang/mobile
|
||||
|
||||
To run the standard library tests, enable Cgo and use an appropriate
|
||||
C compiler from the Android NDK. For example,
|
||||
|
||||
CGO_ENABLED=1 \
|
||||
GOOS=android \
|
||||
GOARCH=arm64 \
|
||||
CC_FOR_TARGET=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \
|
||||
./all.bash
|
||||
|
||||
To run tests on the Android device, add the bin directory to PATH so the
|
||||
go tool can find the go_android_$GOARCH_exec wrapper generated by
|
||||
make.bash. For example, to run the go1 benchmarks
|
||||
|
||||
export PATH=$GOROOT/bin:$PATH
|
||||
cd $GOROOT/test/bench/go1/
|
||||
GOOS=android GOARCH=arm64 go test -bench=. -count=N -timeout=T
|
||||
|
||||
76
misc/go_android_exec/exitcode_test.go
Normal file
76
misc/go_android_exec/exitcode_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2023 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 !(windows || js || wasip1)
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExitCodeFilter(t *testing.T) {
|
||||
// Write text to the filter one character at a time.
|
||||
var out strings.Builder
|
||||
f, exitStr := newExitCodeFilter(&out)
|
||||
// Embed a "fake" exit code in the middle to check that we don't get caught on it.
|
||||
pre := "abc" + exitStr + "123def"
|
||||
text := pre + exitStr + `1`
|
||||
for i := 0; i < len(text); i++ {
|
||||
_, err := f.Write([]byte{text[i]})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// The "pre" output should all have been flushed already.
|
||||
if want, got := pre, out.String(); want != got {
|
||||
t.Errorf("filter should have already flushed %q, but flushed %q", want, got)
|
||||
}
|
||||
|
||||
code, err := f.Finish()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Nothing more should have been written to out.
|
||||
if want, got := pre, out.String(); want != got {
|
||||
t.Errorf("want output %q, got %q", want, got)
|
||||
}
|
||||
if want := 1; want != code {
|
||||
t.Errorf("want exit code %d, got %d", want, code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExitCodeMissing(t *testing.T) {
|
||||
var wantErr *regexp.Regexp
|
||||
check := func(text string) {
|
||||
t.Helper()
|
||||
var out strings.Builder
|
||||
f, exitStr := newExitCodeFilter(&out)
|
||||
if want := "exitcode="; want != exitStr {
|
||||
t.Fatalf("test assumes exitStr will be %q, but got %q", want, exitStr)
|
||||
}
|
||||
f.Write([]byte(text))
|
||||
_, err := f.Finish()
|
||||
// We should get a no exit code error
|
||||
if err == nil || !wantErr.MatchString(err.Error()) {
|
||||
t.Errorf("want error matching %s, got %s", wantErr, err)
|
||||
}
|
||||
// And it should flush all output (even if it looks
|
||||
// like we may be getting an exit code)
|
||||
if got := out.String(); text != got {
|
||||
t.Errorf("want full output %q, got %q", text, got)
|
||||
}
|
||||
}
|
||||
wantErr = regexp.MustCompile("^no exit code")
|
||||
check("abc")
|
||||
check("exitcode")
|
||||
check("exitcode=")
|
||||
check("exitcode=123\n")
|
||||
wantErr = regexp.MustCompile("^bad exit code: .* value out of range")
|
||||
check("exitcode=999999999999999999999999")
|
||||
}
|
||||
527
misc/go_android_exec/main.go
Normal file
527
misc/go_android_exec/main.go
Normal file
@@ -0,0 +1,527 @@
|
||||
// Copyright 2014 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 wrapper uses syscall.Flock to prevent concurrent adb commands,
|
||||
// so for now it only builds on platforms that support that system call.
|
||||
// TODO(#33974): use a more portable library for file locking.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
|
||||
|
||||
// This program can be used as go_android_GOARCH_exec by the Go tool.
|
||||
// It executes binaries on an android device using adb.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func adbRun(args string) (int, error) {
|
||||
// The exit code of adb is often wrong. In theory it was fixed in 2016
|
||||
// (https://code.google.com/p/android/issues/detail?id=3254), but it's
|
||||
// still broken on our builders in 2023. Instead, append the exitcode to
|
||||
// the output and parse it from there.
|
||||
filter, exitStr := newExitCodeFilter(os.Stdout)
|
||||
args += "; echo -n " + exitStr + "$?"
|
||||
|
||||
cmd := adbCmd("exec-out", args)
|
||||
cmd.Stdout = filter
|
||||
// If the adb subprocess somehow hangs, go test will kill this wrapper
|
||||
// and wait for our os.Stderr (and os.Stdout) to close as a result.
|
||||
// However, if the os.Stderr (or os.Stdout) file descriptors are
|
||||
// passed on, the hanging adb subprocess will hold them open and
|
||||
// go test will hang forever.
|
||||
//
|
||||
// Avoid that by wrapping stderr, breaking the short circuit and
|
||||
// forcing cmd.Run to use another pipe and goroutine to pass
|
||||
// along stderr from adb.
|
||||
cmd.Stderr = struct{ io.Writer }{os.Stderr}
|
||||
err := cmd.Run()
|
||||
|
||||
// Before we process err, flush any further output and get the exit code.
|
||||
exitCode, err2 := filter.Finish()
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("adb exec-out %s: %v", args, err)
|
||||
}
|
||||
return exitCode, err2
|
||||
}
|
||||
|
||||
func adb(args ...string) error {
|
||||
if out, err := adbCmd(args...).CombinedOutput(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "adb %s\n%s", strings.Join(args, " "), out)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func adbCmd(args ...string) *exec.Cmd {
|
||||
if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
|
||||
args = append(strings.Split(flags, " "), args...)
|
||||
}
|
||||
return exec.Command("adb", args...)
|
||||
}
|
||||
|
||||
const (
|
||||
deviceRoot = "/data/local/tmp/go_android_exec"
|
||||
deviceGoroot = deviceRoot + "/goroot"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("go_android_exec: ")
|
||||
exitCode, err := runMain()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func runMain() (int, error) {
|
||||
// Concurrent use of adb is flaky, so serialize adb commands.
|
||||
// See https://github.com/golang/go/issues/23795 or
|
||||
// https://issuetracker.google.com/issues/73230216.
|
||||
lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
|
||||
lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer lock.Close()
|
||||
if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// In case we're booting a device or emulator alongside all.bash, wait for
|
||||
// it to be ready. adb wait-for-device is not enough, we have to
|
||||
// wait for sys.boot_completed.
|
||||
if err := adb("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Done once per make.bash.
|
||||
if err := adbCopyGoroot(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Prepare a temporary directory that will be cleaned up at the end.
|
||||
// Binary names can conflict.
|
||||
// E.g. template.test from the {html,text}/template packages.
|
||||
binName := filepath.Base(os.Args[1])
|
||||
deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
|
||||
deviceGopath := deviceGotmp + "/gopath"
|
||||
defer adb("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
|
||||
|
||||
// Determine the package by examining the current working
|
||||
// directory, which will look something like
|
||||
// "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
|
||||
// We extract everything after the $GOROOT or $GOPATH to run on the
|
||||
// same relative directory on the target device.
|
||||
importPath, isStd, modPath, modDir, err := pkgPath()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var deviceCwd string
|
||||
if isStd {
|
||||
// Note that we use path.Join here instead of filepath.Join:
|
||||
// The device paths should be slash-separated even if the go_android_exec
|
||||
// wrapper itself is compiled for Windows.
|
||||
deviceCwd = path.Join(deviceGoroot, "src", importPath)
|
||||
} else {
|
||||
deviceCwd = path.Join(deviceGopath, "src", importPath)
|
||||
if modDir != "" {
|
||||
// In module mode, the user may reasonably expect the entire module
|
||||
// to be present. Copy it over.
|
||||
deviceModDir := path.Join(deviceGopath, "src", modPath)
|
||||
if err := adb("exec-out", "mkdir", "-p", path.Dir(deviceModDir)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// We use a single recursive 'adb push' of the module root instead of
|
||||
// walking the tree and copying it piecewise. If the directory tree
|
||||
// contains nested modules this could push a lot of unnecessary contents,
|
||||
// but for the golang.org/x repos it seems to be significantly (~2x)
|
||||
// faster than copying one file at a time (via filepath.WalkDir),
|
||||
// apparently due to high latency in 'adb' commands.
|
||||
if err := adb("push", modDir, deviceModDir); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := adbCopyTree(deviceCwd, importPath); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Copy .go files from the package.
|
||||
goFiles, err := filepath.Glob("*.go")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(goFiles) > 0 {
|
||||
args := append(append([]string{"push"}, goFiles...), deviceCwd)
|
||||
if err := adb(args...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
|
||||
if err := adb("push", os.Args[1], deviceBin); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Forward SIGQUIT from the go command to show backtraces from
|
||||
// the binary instead of from this wrapper.
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGQUIT)
|
||||
go func() {
|
||||
for range quit {
|
||||
// We don't have the PID of the running process; use the
|
||||
// binary name instead.
|
||||
adb("exec-out", "killall -QUIT "+binName)
|
||||
}
|
||||
}()
|
||||
cmd := `export TMPDIR="` + deviceGotmp + `"` +
|
||||
`; export GOROOT="` + deviceGoroot + `"` +
|
||||
`; export GOPATH="` + deviceGopath + `"` +
|
||||
`; export CGO_ENABLED=0` +
|
||||
`; export GOPROXY=` + os.Getenv("GOPROXY") +
|
||||
`; export GOCACHE="` + deviceRoot + `/gocache"` +
|
||||
`; export PATH="` + deviceGoroot + `/bin":$PATH` +
|
||||
`; export HOME="` + deviceRoot + `/home"` +
|
||||
`; cd "` + deviceCwd + `"` +
|
||||
"; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ")
|
||||
code, err := adbRun(cmd)
|
||||
signal.Reset(syscall.SIGQUIT)
|
||||
close(quit)
|
||||
return code, err
|
||||
}
|
||||
|
||||
type exitCodeFilter struct {
|
||||
w io.Writer // Pass through to w
|
||||
exitRe *regexp.Regexp
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func newExitCodeFilter(w io.Writer) (*exitCodeFilter, string) {
|
||||
const exitStr = "exitcode="
|
||||
|
||||
// Build a regexp that matches any prefix of the exit string at the end of
|
||||
// the input. We do it this way to avoid assuming anything about the
|
||||
// subcommand output (e.g., it might not be \n-terminated).
|
||||
var exitReStr strings.Builder
|
||||
for i := 1; i <= len(exitStr); i++ {
|
||||
fmt.Fprintf(&exitReStr, "%s$|", exitStr[:i])
|
||||
}
|
||||
// Finally, match the exit string along with an exit code.
|
||||
// This is the only case we use a group, and we'll use this
|
||||
// group to extract the numeric code.
|
||||
fmt.Fprintf(&exitReStr, "%s([0-9]+)$", exitStr)
|
||||
exitRe := regexp.MustCompile(exitReStr.String())
|
||||
|
||||
return &exitCodeFilter{w: w, exitRe: exitRe}, exitStr
|
||||
}
|
||||
|
||||
func (f *exitCodeFilter) Write(data []byte) (int, error) {
|
||||
n := len(data)
|
||||
f.buf.Write(data)
|
||||
// Flush to w until a potential match of exitRe
|
||||
b := f.buf.Bytes()
|
||||
match := f.exitRe.FindIndex(b)
|
||||
if match == nil {
|
||||
// Flush all of the buffer.
|
||||
_, err := f.w.Write(b)
|
||||
f.buf.Reset()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
} else {
|
||||
// Flush up to the beginning of the (potential) match.
|
||||
_, err := f.w.Write(b[:match[0]])
|
||||
f.buf.Next(match[0])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (f *exitCodeFilter) Finish() (int, error) {
|
||||
// f.buf could be empty, contain a partial match of exitRe, or
|
||||
// contain a full match.
|
||||
b := f.buf.Bytes()
|
||||
defer f.buf.Reset()
|
||||
match := f.exitRe.FindSubmatch(b)
|
||||
if len(match) < 2 || match[1] == nil {
|
||||
// Not a full match. Flush.
|
||||
if _, err := f.w.Write(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, fmt.Errorf("no exit code (in %q)", string(b))
|
||||
}
|
||||
|
||||
// Parse the exit code.
|
||||
code, err := strconv.Atoi(string(match[1]))
|
||||
if err != nil {
|
||||
// Something is malformed. Flush.
|
||||
if _, err := f.w.Write(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, fmt.Errorf("bad exit code: %v (in %q)", err, string(b))
|
||||
}
|
||||
return code, nil
|
||||
}
|
||||
|
||||
// pkgPath determines the package import path of the current working directory,
|
||||
// and indicates whether it is
|
||||
// and returns the path to the package source relative to $GOROOT (or $GOPATH).
|
||||
func pkgPath() (importPath string, isStd bool, modPath, modDir string, err error) {
|
||||
errorf := func(format string, args ...any) (string, bool, string, string, error) {
|
||||
return "", false, "", "", fmt.Errorf(format, args...)
|
||||
}
|
||||
goTool, err := goTool()
|
||||
if err != nil {
|
||||
return errorf("%w", err)
|
||||
}
|
||||
cmd := exec.Command(goTool, "list", "-e", "-f", "{{.ImportPath}}:{{.Standard}}{{with .Module}}:{{.Path}}:{{.Dir}}{{end}}", ".")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
|
||||
return errorf("%v: %s", cmd, ee.Stderr)
|
||||
}
|
||||
return errorf("%v: %w", cmd, err)
|
||||
}
|
||||
|
||||
parts := strings.SplitN(string(bytes.TrimSpace(out)), ":", 4)
|
||||
if len(parts) < 2 {
|
||||
return errorf("%v: missing ':' in output: %q", cmd, out)
|
||||
}
|
||||
importPath = parts[0]
|
||||
if importPath == "" || importPath == "." {
|
||||
return errorf("current directory does not have a Go import path")
|
||||
}
|
||||
isStd, err = strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
return errorf("%v: non-boolean .Standard in output: %q", cmd, out)
|
||||
}
|
||||
if len(parts) >= 4 {
|
||||
modPath = parts[2]
|
||||
modDir = parts[3]
|
||||
}
|
||||
|
||||
return importPath, isStd, modPath, modDir, nil
|
||||
}
|
||||
|
||||
// adbCopyTree copies testdata, go.mod, go.sum files from subdir
|
||||
// and from parent directories all the way up to the root of subdir.
|
||||
// go.mod and go.sum files are needed for the go tool modules queries,
|
||||
// and the testdata directories for tests. It is common for tests to
|
||||
// reach out into testdata from parent packages.
|
||||
func adbCopyTree(deviceCwd, subdir string) error {
|
||||
dir := ""
|
||||
for {
|
||||
for _, name := range []string{"testdata", "go.mod", "go.sum"} {
|
||||
hostPath := filepath.Join(dir, name)
|
||||
if _, err := os.Stat(hostPath); err != nil {
|
||||
continue
|
||||
}
|
||||
devicePath := path.Join(deviceCwd, dir)
|
||||
if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := adb("push", hostPath, devicePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if subdir == "." {
|
||||
break
|
||||
}
|
||||
subdir = filepath.Dir(subdir)
|
||||
dir = path.Join(dir, "..")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
|
||||
// and temporary data. Then, it copies relevant parts of GOROOT to the device,
|
||||
// including the go tool built for android.
|
||||
// A lock file ensures this only happens once, even with concurrent exec
|
||||
// wrappers.
|
||||
func adbCopyGoroot() error {
|
||||
goTool, err := goTool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(goTool, "version")
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", cmd, err)
|
||||
}
|
||||
goVersion := string(out)
|
||||
|
||||
// Also known by cmd/dist. The bootstrap command deletes the file.
|
||||
statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
|
||||
stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stat.Close()
|
||||
// Serialize check and copying.
|
||||
if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := io.ReadAll(stat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if string(s) == goVersion {
|
||||
return nil
|
||||
}
|
||||
|
||||
goroot, err := findGoroot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete the device's GOROOT, GOPATH and any leftover test data,
|
||||
// and recreate GOROOT.
|
||||
if err := adb("exec-out", "rm", "-rf", deviceRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build Go for Android.
|
||||
cmd = exec.Command(goTool, "install", "cmd")
|
||||
out, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if len(bytes.TrimSpace(out)) > 0 {
|
||||
log.Printf("\n%s", out)
|
||||
}
|
||||
return fmt.Errorf("%v: %w", cmd, err)
|
||||
}
|
||||
if err := adb("exec-out", "mkdir", "-p", deviceGoroot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the Android tools from the relevant bin subdirectory to GOROOT/bin.
|
||||
cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/go")
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err = cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", cmd, err)
|
||||
}
|
||||
platformBin := filepath.Dir(string(bytes.TrimSpace(out)))
|
||||
if platformBin == "." {
|
||||
return errors.New("failed to locate cmd/go for target platform")
|
||||
}
|
||||
if err := adb("push", platformBin, path.Join(deviceGoroot, "bin")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy only the relevant subdirectories from pkg: pkg/include and the
|
||||
// platform-native binaries in pkg/tool.
|
||||
if err := adb("exec-out", "mkdir", "-p", path.Join(deviceGoroot, "pkg", "tool")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := adb("push", filepath.Join(goroot, "pkg", "include"), path.Join(deviceGoroot, "pkg", "include")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/compile")
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err = cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", cmd, err)
|
||||
}
|
||||
platformToolDir := filepath.Dir(string(bytes.TrimSpace(out)))
|
||||
if platformToolDir == "." {
|
||||
return errors.New("failed to locate cmd/compile for target platform")
|
||||
}
|
||||
relToolDir, err := filepath.Rel(filepath.Join(goroot), platformToolDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := adb("push", platformToolDir, path.Join(deviceGoroot, relToolDir)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy all other files from GOROOT.
|
||||
dirents, err := os.ReadDir(goroot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, de := range dirents {
|
||||
switch de.Name() {
|
||||
case "bin", "pkg":
|
||||
// We already created GOROOT/bin and GOROOT/pkg above; skip those.
|
||||
continue
|
||||
}
|
||||
if err := adb("push", filepath.Join(goroot, de.Name()), path.Join(deviceGoroot, de.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := stat.WriteString(goVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findGoroot() (string, error) {
|
||||
gorootOnce.Do(func() {
|
||||
// If runtime.GOROOT reports a non-empty path, assume that it is valid.
|
||||
// (It may be empty if this binary was built with -trimpath.)
|
||||
gorootPath = runtime.GOROOT()
|
||||
if gorootPath != "" {
|
||||
return
|
||||
}
|
||||
|
||||
// runtime.GOROOT is empty — perhaps go_android_exec was built with
|
||||
// -trimpath and GOROOT is unset. Try 'go env GOROOT' as a fallback,
|
||||
// assuming that the 'go' command in $PATH is the correct one.
|
||||
|
||||
cmd := exec.Command("go", "env", "GOROOT")
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
gorootErr = fmt.Errorf("%v: %w", cmd, err)
|
||||
}
|
||||
|
||||
gorootPath = string(bytes.TrimSpace(out))
|
||||
if gorootPath == "" {
|
||||
gorootErr = errors.New("GOROOT not found")
|
||||
}
|
||||
})
|
||||
|
||||
return gorootPath, gorootErr
|
||||
}
|
||||
|
||||
func goTool() (string, error) {
|
||||
goroot, err := findGoroot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(goroot, "bin", "go"), nil
|
||||
}
|
||||
|
||||
var (
|
||||
gorootOnce sync.Once
|
||||
gorootPath string
|
||||
gorootErr error
|
||||
)
|
||||
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>
|
||||
`
|
||||
191
misc/linkcheck/linkcheck.go
Normal file
191
misc/linkcheck/linkcheck.go
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// The linkcheck command finds missing links in the godoc website.
|
||||
// It crawls a URL recursively and notes URLs and URL fragments
|
||||
// that it's seen and prints a report of missing links at the end.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
root = flag.String("root", "http://localhost:6060", "Root to crawl")
|
||||
verbose = flag.Bool("verbose", false, "verbose")
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup // outstanding fetches
|
||||
var urlq = make(chan string) // URLs to crawl
|
||||
|
||||
// urlFrag is a URL and its optional #fragment (without the #)
|
||||
type urlFrag struct {
|
||||
url, frag string
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
crawled = make(map[string]bool) // URL without fragment -> true
|
||||
neededFrags = make(map[urlFrag][]string) // URL#frag -> who needs it
|
||||
)
|
||||
|
||||
var aRx = regexp.MustCompile(`<a href=['"]?(/[^\s'">]+)`)
|
||||
|
||||
// Owned by crawlLoop goroutine:
|
||||
var (
|
||||
linkSources = make(map[string][]string) // url no fragment -> sources
|
||||
fragExists = make(map[urlFrag]bool)
|
||||
problems []string
|
||||
)
|
||||
|
||||
func localLinks(body string) (links []string) {
|
||||
seen := map[string]bool{}
|
||||
mv := aRx.FindAllStringSubmatch(body, -1)
|
||||
for _, m := range mv {
|
||||
ref := m[1]
|
||||
if strings.HasPrefix(ref, "/src/") {
|
||||
continue
|
||||
}
|
||||
if !seen[ref] {
|
||||
seen[ref] = true
|
||||
links = append(links, m[1])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var idRx = regexp.MustCompile(`\bid=['"]?([^\s'">]+)`)
|
||||
|
||||
func pageIDs(body string) (ids []string) {
|
||||
mv := idRx.FindAllStringSubmatch(body, -1)
|
||||
for _, m := range mv {
|
||||
ids = append(ids, m[1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// url may contain a #fragment, and the fragment is then noted as needing to exist.
|
||||
func crawl(url string, sourceURL string) {
|
||||
if strings.Contains(url, "/devel/release") {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if u, frag, ok := strings.Cut(url, "#"); ok {
|
||||
url = u
|
||||
if frag != "" {
|
||||
uf := urlFrag{url, frag}
|
||||
neededFrags[uf] = append(neededFrags[uf], sourceURL)
|
||||
}
|
||||
}
|
||||
if crawled[url] {
|
||||
return
|
||||
}
|
||||
crawled[url] = true
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
urlq <- url
|
||||
}()
|
||||
}
|
||||
|
||||
func addProblem(url, errmsg string) {
|
||||
msg := fmt.Sprintf("Error on %s: %s (from %s)", url, errmsg, linkSources[url])
|
||||
if *verbose {
|
||||
log.Print(msg)
|
||||
}
|
||||
problems = append(problems, msg)
|
||||
}
|
||||
|
||||
func crawlLoop() {
|
||||
for url := range urlq {
|
||||
if err := doCrawl(url); err != nil {
|
||||
addProblem(url, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doCrawl(url string) error {
|
||||
defer wg.Done()
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := http.DefaultTransport.RoundTrip(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Handle redirects.
|
||||
if res.StatusCode/100 == 3 {
|
||||
newURL, err := res.Location()
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving redirect: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(newURL.String(), *root) {
|
||||
// Skip off-site redirects.
|
||||
return nil
|
||||
}
|
||||
crawl(newURL.String(), url)
|
||||
return nil
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return errors.New(res.Status)
|
||||
}
|
||||
slurp, err := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading %s body: %v", url, err)
|
||||
}
|
||||
if *verbose {
|
||||
log.Printf("Len of %s: %d", url, len(slurp))
|
||||
}
|
||||
body := string(slurp)
|
||||
for _, ref := range localLinks(body) {
|
||||
if *verbose {
|
||||
log.Printf(" links to %s", ref)
|
||||
}
|
||||
dest := *root + ref
|
||||
linkSources[dest] = append(linkSources[dest], url)
|
||||
crawl(dest, url)
|
||||
}
|
||||
for _, id := range pageIDs(body) {
|
||||
if *verbose {
|
||||
log.Printf(" url %s has #%s", url, id)
|
||||
}
|
||||
fragExists[urlFrag{url, id}] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
go crawlLoop()
|
||||
crawl(*root, "")
|
||||
|
||||
wg.Wait()
|
||||
close(urlq)
|
||||
for uf, needers := range neededFrags {
|
||||
if !fragExists[uf] {
|
||||
problems = append(problems, fmt.Sprintf("Missing fragment for %+v from %v", uf, needers))
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range problems {
|
||||
fmt.Println(s)
|
||||
}
|
||||
if len(problems) > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
17
misc/wasm/go_js_wasm_exec
Executable file
17
misc/wasm/go_js_wasm_exec
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2018 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.
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
# Increase the V8 stack size from the default of 984K
|
||||
# to 8192K to ensure all tests can pass without hitting
|
||||
# stack size limits.
|
||||
exec node --stack-size=8192 "$DIR/wasm_exec_node.js" "$@"
|
||||
23
misc/wasm/go_wasip1_wasm_exec
Executable file
23
misc/wasm/go_wasip1_wasm_exec
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2023 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.
|
||||
|
||||
case "$GOWASIRUNTIME" in
|
||||
"wasmedge")
|
||||
exec wasmedge --dir=/ --env PWD="$PWD" --env PATH="$PATH" ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
|
||||
;;
|
||||
"wasmer")
|
||||
exec wasmer run --dir=/ --env PWD="$PWD" --env PATH="$PATH" ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
|
||||
;;
|
||||
"wazero")
|
||||
exec wazero run -mount /:/ -env-inherit -cachedir "${TMPDIR:-/tmp}"/wazero ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
|
||||
;;
|
||||
"wasmtime" | "")
|
||||
exec wasmtime run --dir=/ --env PWD="$PWD" --env PATH="$PATH" -W max-wasm-stack=1048576 ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown Go WASI runtime specified: $GOWASIRUNTIME"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
49
misc/wasm/wasm_exec.html
Normal file
49
misc/wasm/wasm_exec.html
Normal file
@@ -0,0 +1,49 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2018 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.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Go wasm</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Add the following polyfill for Microsoft Edge 17/18 support:
|
||||
<script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
|
||||
(see https://caniuse.com/#feat=textencoder)
|
||||
-->
|
||||
<script src="wasm_exec.js"></script>
|
||||
<script>
|
||||
if (!WebAssembly.instantiateStreaming) { // polyfill
|
||||
WebAssembly.instantiateStreaming = async (resp, importObject) => {
|
||||
const source = await (await resp).arrayBuffer();
|
||||
return await WebAssembly.instantiate(source, importObject);
|
||||
};
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
let mod, inst;
|
||||
WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
|
||||
mod = result.module;
|
||||
inst = result.instance;
|
||||
document.getElementById("runButton").disabled = false;
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
async function run() {
|
||||
console.clear();
|
||||
await go.run(inst);
|
||||
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onClick="run();" id="runButton" disabled>Run</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
561
misc/wasm/wasm_exec.js
Normal file
561
misc/wasm/wasm_exec.js
Normal file
@@ -0,0 +1,561 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!globalThis.fs) {
|
||||
let outputBuf = "";
|
||||
globalThis.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf("\n");
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substring(0, nl));
|
||||
outputBuf = outputBuf.substring(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
callback(enosys());
|
||||
return;
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
chmod(path, mode, callback) { callback(enosys()); },
|
||||
chown(path, uid, gid, callback) { callback(enosys()); },
|
||||
close(fd, callback) { callback(enosys()); },
|
||||
fchmod(fd, mode, callback) { callback(enosys()); },
|
||||
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
||||
fstat(fd, callback) { callback(enosys()); },
|
||||
fsync(fd, callback) { callback(null); },
|
||||
ftruncate(fd, length, callback) { callback(enosys()); },
|
||||
lchown(path, uid, gid, callback) { callback(enosys()); },
|
||||
link(path, link, callback) { callback(enosys()); },
|
||||
lstat(path, callback) { callback(enosys()); },
|
||||
mkdir(path, perm, callback) { callback(enosys()); },
|
||||
open(path, flags, mode, callback) { callback(enosys()); },
|
||||
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
||||
readdir(path, callback) { callback(enosys()); },
|
||||
readlink(path, callback) { callback(enosys()); },
|
||||
rename(from, to, callback) { callback(enosys()); },
|
||||
rmdir(path, callback) { callback(enosys()); },
|
||||
stat(path, callback) { callback(enosys()); },
|
||||
symlink(path, link, callback) { callback(enosys()); },
|
||||
truncate(path, length, callback) { callback(enosys()); },
|
||||
unlink(path, callback) { callback(enosys()); },
|
||||
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
||||
};
|
||||
}
|
||||
|
||||
if (!globalThis.process) {
|
||||
globalThis.process = {
|
||||
getuid() { return -1; },
|
||||
getgid() { return -1; },
|
||||
geteuid() { return -1; },
|
||||
getegid() { return -1; },
|
||||
getgroups() { throw enosys(); },
|
||||
pid: -1,
|
||||
ppid: -1,
|
||||
umask() { throw enosys(); },
|
||||
cwd() { throw enosys(); },
|
||||
chdir() { throw enosys(); },
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalThis.crypto) {
|
||||
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
||||
}
|
||||
|
||||
if (!globalThis.performance) {
|
||||
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
||||
}
|
||||
|
||||
if (!globalThis.TextEncoder) {
|
||||
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
if (!globalThis.TextDecoder) {
|
||||
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
globalThis.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
this.exit = (code) => {
|
||||
if (code !== 0) {
|
||||
console.warn("exit code:", code);
|
||||
}
|
||||
};
|
||||
this._exitPromise = new Promise((resolve) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
this._pendingEvent = null;
|
||||
this._scheduledTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
}
|
||||
|
||||
const setInt32 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
}
|
||||
|
||||
const getInt64 = (addr) => {
|
||||
const low = this.mem.getUint32(addr + 0, true);
|
||||
const high = this.mem.getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
}
|
||||
|
||||
const loadValue = (addr) => {
|
||||
const f = this.mem.getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = this.mem.getUint32(addr, true);
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7FF80000;
|
||||
|
||||
if (typeof v === "number" && v !== 0) {
|
||||
if (isNaN(v)) {
|
||||
this.mem.setUint32(addr + 4, nanHead, true);
|
||||
this.mem.setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
this.mem.setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v === undefined) {
|
||||
this.mem.setFloat64(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = this._values.length;
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case "object":
|
||||
if (v !== null) {
|
||||
typeFlag = 1;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 3;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 4;
|
||||
break;
|
||||
}
|
||||
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
this.mem.setUint32(addr, id, true);
|
||||
}
|
||||
|
||||
const loadSlice = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (addr) => {
|
||||
const saddr = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
_gotest: {
|
||||
add: (a, b) => a + b,
|
||||
},
|
||||
gojs: {
|
||||
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||
|
||||
// func wasmExit(code int32)
|
||||
"runtime.wasmExit": (sp) => {
|
||||
sp >>>= 0;
|
||||
const code = this.mem.getInt32(sp + 8, true);
|
||||
this.exited = true;
|
||||
delete this._inst;
|
||||
delete this._values;
|
||||
delete this._goRefCounts;
|
||||
delete this._ids;
|
||||
delete this._idPool;
|
||||
this.exit(code);
|
||||
},
|
||||
|
||||
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||
"runtime.wasmWrite": (sp) => {
|
||||
sp >>>= 0;
|
||||
const fd = getInt64(sp + 8);
|
||||
const p = getInt64(sp + 16);
|
||||
const n = this.mem.getInt32(sp + 24, true);
|
||||
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
||||
},
|
||||
|
||||
// func resetMemoryDataView()
|
||||
"runtime.resetMemoryDataView": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
},
|
||||
|
||||
// func nanotime1() int64
|
||||
"runtime.nanotime1": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime() (sec int64, nsec int32)
|
||||
"runtime.walltime": (sp) => {
|
||||
sp >>>= 0;
|
||||
const msec = (new Date).getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||
},
|
||||
|
||||
// func scheduleTimeoutEvent(delay int64) int32
|
||||
"runtime.scheduleTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this._nextCallbackTimeoutID;
|
||||
this._nextCallbackTimeoutID++;
|
||||
this._scheduledTimeouts.set(id, setTimeout(
|
||||
() => {
|
||||
this._resume();
|
||||
while (this._scheduledTimeouts.has(id)) {
|
||||
// for some reason Go failed to register the timeout event, log and try again
|
||||
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||
console.warn("scheduleTimeoutEvent: missed timeout event");
|
||||
this._resume();
|
||||
}
|
||||
},
|
||||
getInt64(sp + 8),
|
||||
));
|
||||
this.mem.setInt32(sp + 16, id, true);
|
||||
},
|
||||
|
||||
// func clearTimeoutEvent(id int32)
|
||||
"runtime.clearTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getInt32(sp + 8, true);
|
||||
clearTimeout(this._scheduledTimeouts.get(id));
|
||||
this._scheduledTimeouts.delete(id);
|
||||
},
|
||||
|
||||
// func getRandomData(r []byte)
|
||||
"runtime.getRandomData": (sp) => {
|
||||
sp >>>= 0;
|
||||
crypto.getRandomValues(loadSlice(sp + 8));
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
"syscall/js.finalizeRef": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getUint32(sp + 8, true);
|
||||
this._goRefCounts[id]--;
|
||||
if (this._goRefCounts[id] === 0) {
|
||||
const v = this._values[id];
|
||||
this._values[id] = null;
|
||||
this._ids.delete(v);
|
||||
this._idPool.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, loadString(sp + 8));
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (sp) => {
|
||||
sp >>>= 0;
|
||||
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 32, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
"syscall/js.valueDelete": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const m = Reflect.get(v, loadString(sp + 16));
|
||||
const args = loadSliceOfValues(sp + 32);
|
||||
const result = Reflect.apply(m, v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, result);
|
||||
this.mem.setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, err);
|
||||
this.mem.setUint8(sp + 64, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.apply(v, undefined, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.construct(v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||
storeValue(sp + 16, str);
|
||||
setInt64(sp + 24, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = loadValue(sp + 8);
|
||||
loadSlice(sp + 16).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadSlice(sp + 8);
|
||||
const src = loadValue(sp + 32);
|
||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
"syscall/js.copyBytesToJS": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadValue(sp + 8);
|
||||
const src = loadSlice(sp + 16);
|
||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
"debug": (value) => {
|
||||
console.log(value);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
if (!(instance instanceof WebAssembly.Instance)) {
|
||||
throw new Error("Go.run: WebAssembly.Instance expected");
|
||||
}
|
||||
this._inst = instance;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
globalThis,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||
this._ids = new Map([ // mapping from JS values to reference ids
|
||||
[0, 1],
|
||||
[null, 2],
|
||||
[true, 3],
|
||||
[false, 4],
|
||||
[globalThis, 5],
|
||||
[this, 6],
|
||||
]);
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
|
||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||
let offset = 4096;
|
||||
|
||||
const strPtr = (str) => {
|
||||
const ptr = offset;
|
||||
const bytes = encoder.encode(str + "\0");
|
||||
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
||||
offset += bytes.length;
|
||||
if (offset % 8 !== 0) {
|
||||
offset += 8 - (offset % 8);
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
const argc = this.argv.length;
|
||||
|
||||
const argvPtrs = [];
|
||||
this.argv.forEach((arg) => {
|
||||
argvPtrs.push(strPtr(arg));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const keys = Object.keys(this.env).sort();
|
||||
keys.forEach((key) => {
|
||||
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const argv = offset;
|
||||
argvPtrs.forEach((ptr) => {
|
||||
this.mem.setUint32(offset, ptr, true);
|
||||
this.mem.setUint32(offset + 4, 0, true);
|
||||
offset += 8;
|
||||
});
|
||||
|
||||
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||
const wasmMinDataAddr = 4096 + 8192;
|
||||
if (offset >= wasmMinDataAddr) {
|
||||
throw new Error("total length of command line and environment variables exceeds limit");
|
||||
}
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
this._inst.exports.resume();
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
}
|
||||
|
||||
_makeFuncWrapper(id) {
|
||||
const go = this;
|
||||
return function () {
|
||||
const event = { id: id, this: this, args: arguments };
|
||||
go._pendingEvent = event;
|
||||
go._resume();
|
||||
return event.result;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
39
misc/wasm/wasm_exec_node.js
Normal file
39
misc/wasm/wasm_exec_node.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
"use strict";
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
globalThis.require = require;
|
||||
globalThis.fs = require("fs");
|
||||
globalThis.TextEncoder = require("util").TextEncoder;
|
||||
globalThis.TextDecoder = require("util").TextDecoder;
|
||||
|
||||
globalThis.performance ??= require("performance");
|
||||
|
||||
globalThis.crypto ??= require("crypto");
|
||||
|
||||
require("./wasm_exec");
|
||||
|
||||
const go = new Go();
|
||||
go.argv = process.argv.slice(2);
|
||||
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
|
||||
go.exit = process.exit;
|
||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
|
||||
process.on("exit", (code) => { // Node.js exits if no event handler is pending
|
||||
if (code === 0 && !go.exited) {
|
||||
// deadlock, make Go print error and stack traces
|
||||
go._pendingEvent = { id: 0 };
|
||||
go._resume();
|
||||
}
|
||||
});
|
||||
return go.run(result.instance);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user