summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
authorMichael Stapelberg <stapelberg@debian.org>2013-03-04 21:27:36 +0100
committerMichael Stapelberg <michael@stapelberg.de>2013-03-04 21:27:36 +0100
commit04b08da9af0c450d645ab7389d1467308cfc2db8 (patch)
treedb247935fa4f2f94408edc3acd5d0d4f997aa0d8 /misc
parent917c5fb8ec48e22459d77e3849e6d388f93d3260 (diff)
downloadgolang-04b08da9af0c450d645ab7389d1467308cfc2db8.tar.gz
Imported Upstream version 1.1~hg20130304upstream/1.1_hg20130304
Diffstat (limited to 'misc')
-rwxr-xr-xmisc/benchcmp62
-rw-r--r--misc/cgo/life/life.go6
-rw-r--r--misc/cgo/life/main.go4
-rw-r--r--misc/cgo/life/main.out (renamed from misc/cgo/life/golden.out)1
-rwxr-xr-xmisc/cgo/life/test.bash14
-rw-r--r--misc/cgo/stdio/chain.go2
-rw-r--r--misc/cgo/stdio/chain.out55
-rw-r--r--misc/cgo/stdio/fib.go2
-rw-r--r--misc/cgo/stdio/fib.out91
-rw-r--r--misc/cgo/stdio/file.go5
-rw-r--r--misc/cgo/stdio/hello.go2
-rw-r--r--misc/cgo/stdio/hello.out1
-rw-r--r--misc/cgo/stdio/run.out (renamed from misc/cgo/stdio/golden.out)0
-rw-r--r--misc/cgo/stdio/stdio.go22
-rwxr-xr-xmisc/cgo/stdio/test.bash20
-rw-r--r--misc/cgo/test/Makefile48
-rw-r--r--misc/cgo/test/backdoor/runtime.c2
-rw-r--r--misc/cgo/test/backdoor/runtime_gccgo.c18
-rw-r--r--misc/cgo/test/basic.go24
-rw-r--r--misc/cgo/test/callback.go2
-rw-r--r--misc/cgo/test/cgo_linux_test.go9
-rw-r--r--misc/cgo/test/cgo_test.go11
-rw-r--r--misc/cgo/test/cthread.go44
-rw-r--r--misc/cgo/test/cthread_unix.c34
-rw-r--r--misc/cgo/test/cthread_windows.c37
-rw-r--r--misc/cgo/test/issue1560.go42
-rw-r--r--misc/cgo/test/issue1635.go33
-rw-r--r--misc/cgo/test/issue3261.go43
-rw-r--r--misc/cgo/test/issue3729.go47
-rw-r--r--misc/cgo/test/issue3729w.go16
-rw-r--r--misc/cgo/test/issue3741.go22
-rw-r--r--misc/cgo/test/issue3775.go29
-rw-r--r--misc/cgo/test/issue3945.go22
-rw-r--r--misc/cgo/test/issue4029.go60
-rw-r--r--misc/cgo/test/issue4029w.go12
-rw-r--r--misc/cgo/test/issue4054a.go23
-rw-r--r--misc/cgo/test/issue4054b.go23
-rw-r--r--misc/cgo/test/issue4273.c10
-rw-r--r--misc/cgo/test/issue4273b.c11
-rw-r--r--misc/cgo/test/issue4417.go42
-rw-r--r--misc/cgo/test/setgid_linux.go32
-rw-r--r--misc/cgo/test/sleep_windows_386.go (renamed from misc/cgo/test/sleep_windows.go)4
-rwxr-xr-xmisc/cgo/testso/test.bash2
-rw-r--r--misc/chrome/gophertool/background.html14
-rw-r--r--misc/chrome/gophertool/background.js9
-rw-r--r--misc/chrome/gophertool/gopher.js2
-rw-r--r--misc/chrome/gophertool/manifest.json7
-rw-r--r--misc/chrome/gophertool/popup.html51
-rw-r--r--misc/chrome/gophertool/popup.js46
-rw-r--r--misc/dashboard/app/app.yaml2
-rw-r--r--misc/dashboard/app/build/build.go46
-rw-r--r--misc/dashboard/app/build/handler.go2
-rw-r--r--misc/dashboard/app/build/init.go9
-rw-r--r--misc/dashboard/app/build/notify.go29
-rw-r--r--misc/dashboard/app/build/test.go33
-rw-r--r--misc/dashboard/app/build/ui.go5
-rw-r--r--misc/dashboard/builder/Makefile4
-rw-r--r--misc/dashboard/builder/doc.go10
-rw-r--r--misc/dashboard/builder/exec.go70
-rw-r--r--misc/dashboard/builder/http.go4
-rw-r--r--misc/dashboard/builder/main.go313
-rw-r--r--misc/dashboard/builder/vcs.go148
-rw-r--r--misc/dashboard/codereview/app.yaml24
-rw-r--r--misc/dashboard/codereview/cron.yaml4
-rw-r--r--misc/dashboard/codereview/dashboard/cl.go481
-rw-r--r--misc/dashboard/codereview/dashboard/front.go299
-rw-r--r--misc/dashboard/codereview/dashboard/gc.go47
-rw-r--r--misc/dashboard/codereview/dashboard/mail.go68
-rw-r--r--misc/dashboard/codereview/dashboard/people.go42
-rw-r--r--misc/dashboard/codereview/index.yaml25
-rw-r--r--misc/dashboard/codereview/queue.yaml4
-rw-r--r--misc/dashboard/codereview/static/gopherstamp.jpgbin0 -> 16996 bytes
-rw-r--r--misc/dashboard/codereview/static/icon.pngbin0 -> 412 bytes
-rw-r--r--misc/dist/bindist.go282
-rw-r--r--misc/dist/darwin/Distribution32
-rw-r--r--misc/dist/darwin/Resources/bg.pngbin0 -> 11466 bytes
-rwxr-xr-x[-rw-r--r--]misc/dist/darwin/scripts/postinstall7
-rw-r--r--misc/dist/darwin/scripts/preinstall8
-rw-r--r--misc/dist/stat_darwin.go32
-rw-r--r--misc/dist/stat_linux.go32
-rw-r--r--misc/emacs/go-mode-load.el88
-rw-r--r--misc/emacs/go-mode.el1292
-rwxr-xr-xmisc/git/pre-commit26
-rw-r--r--misc/goplay/doc.go12
-rw-r--r--misc/goplay/goplay.go2
-rw-r--r--misc/osx/README3
-rw-r--r--misc/osx/etc/paths.d/go1
-rwxr-xr-xmisc/osx/package.bash69
-rw-r--r--misc/osx/scripts/postinstall23
-rwxr-xr-xmisc/pprof89
-rw-r--r--misc/swig/callback/Makefile17
-rw-r--r--misc/swig/callback/callback.go11
-rw-r--r--misc/swig/callback/callback_test.go34
-rw-r--r--misc/swig/callback/run.go39
-rw-r--r--misc/swig/stdio/file.swig15
-rw-r--r--misc/swig/stdio/file_test.go22
-rw-r--r--misc/swig/stdio/hello.go11
-rw-r--r--misc/vim/autoload/go/complete.vim48
-rw-r--r--misc/vim/ftplugin/go/fmt.vim5
-rw-r--r--misc/vim/ftplugin/go/godoc.vim13
-rw-r--r--misc/vim/ftplugin/go/import.vim38
-rwxr-xr-xmisc/vim/ftplugin/go/test.sh78
-rw-r--r--misc/vim/plugin/godoc.vim2
-rw-r--r--misc/vim/readme.txt9
-rw-r--r--misc/zsh/go2
105 files changed, 3528 insertions, 1601 deletions
diff --git a/misc/benchcmp b/misc/benchcmp
index 015e7d2b2..3180f57ea 100755
--- a/misc/benchcmp
+++ b/misc/benchcmp
@@ -7,8 +7,13 @@ case "$1" in
-*)
echo 'usage: benchcmp old.txt new.txt' >&2
echo >&2
- echo 'Each input file should be go test -test.run=NONE -test.bench=. > [old,new].txt' >&2
+ echo 'Each input file should be from:' >&2
+ echo ' go test -test.run=NONE -test.bench=. > [old,new].txt' >&2
+ echo >&2
echo 'Benchcmp compares the first and last for each benchmark.' >&2
+ echo >&2
+ echo 'If -test.benchmem=true is added to the "go test" command' >&2
+ echo 'benchcmp will also compare memory allocations.' >&2
exit 2
esac
@@ -27,10 +32,31 @@ $1 ~ /Benchmark/ && $4 == "ns/op" {
new[$1] = $3
if($6 == "MB/s")
newmb[$1] = $5
+
+ # allocs/op might be at $8 or $10 depending on if
+ # SetBytes was used or not.
+ # B/op might be at $6 or $8, it should be immediately
+ # followed by allocs/op
+ if($8 == "allocs/op") {
+ newbytes[$1] = $5
+ newalloc[$1] = $7
+ }
+ if($10 == "allocs/op") {
+ newbytes[$1] = $7
+ newalloc[$1] = $9
+ }
} else {
old[$1] = $3
- if($6 = "MB/s")
+ if($6 == "MB/s")
oldmb[$1] = $5
+ if($8 == "allocs/op") {
+ oldbytes[$1] = $5
+ oldalloc[$1] = $7
+ }
+ if($10 == "allocs/op") {
+ oldbytes[$1] = $7
+ oldalloc[$1] = $9
+ }
}
}
@@ -62,5 +88,37 @@ END {
sprintf("%.2f", newmb[what]),
sprintf("%.2f", newmb[what]/oldmb[what]))
}
+
+ # print allocs
+ anyalloc = 0
+ for(i=0; i<n; i++) {
+ what = name[i]
+ if(!(what in newalloc))
+ continue
+ if(anyalloc++ == 0)
+ printf("\n%-*s %12s %12s %7s\n", len, "benchmark", "old allocs", "new allocs", "delta")
+ if(oldalloc[what] == 0)
+ delta="n/a"
+ else
+ delta=sprintf("%.2f", 100*newalloc[what]/oldalloc[what]-100)
+ printf("%-*s %12d %12d %6s%%\n", len, what,
+ oldalloc[what], newalloc[what], delta)
+ }
+
+ # print alloc bytes
+ anybytes = 0
+ for(i=0; i<n; i++) {
+ what = name[i]
+ if(!(what in newbytes))
+ continue
+ if(anybytes++ == 0)
+ printf("\n%-*s %12s %12s %7s\n", len, "benchmark", "old bytes", "new bytes", "delta")
+ if(oldbytes[what] == 0)
+ delta="n/a"
+ else
+ delta=sprintf("%.2f", 100*newbytes[what]/oldbytes[what]-100)
+ printf("%-*s %12d %12d %6s%%\n", len, what,
+ oldbytes[what], newbytes[what], delta)
+ }
}
' "$@"
diff --git a/misc/cgo/life/life.go b/misc/cgo/life/life.go
index ec000ce3a..fda5495e5 100644
--- a/misc/cgo/life/life.go
+++ b/misc/cgo/life/life.go
@@ -1,3 +1,5 @@
+// skip
+
// Copyright 2010 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.
@@ -9,8 +11,8 @@ import "C"
import "unsafe"
-func Run(gen, x, y int, a []int) {
- n := make([]int, x*y)
+func Run(gen, x, y int, a []int32) {
+ n := make([]int32, x*y)
for i := 0; i < gen; i++ {
C.Step(C.int(x), C.int(y), (*C.int)(unsafe.Pointer(&a[0])), (*C.int)(unsafe.Pointer(&n[0])))
copy(a, n)
diff --git a/misc/cgo/life/main.go b/misc/cgo/life/main.go
index 47ae0e18c..725e10f76 100644
--- a/misc/cgo/life/main.go
+++ b/misc/cgo/life/main.go
@@ -1,3 +1,5 @@
+// cmpout
+
// Copyright 2010 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.
@@ -22,7 +24,7 @@ var gen = flag.Int("gen", 10, "generations")
func main() {
flag.Parse()
- var a [MAXDIM * MAXDIM]int
+ var a [MAXDIM * MAXDIM]int32
for i := 2; i < *dim; i += 8 {
for j := 2; j < *dim-3; j += 8 {
for y := 0; y < 3; y++ {
diff --git a/misc/cgo/life/golden.out b/misc/cgo/life/main.out
index 539d2106d..26fc9c6e3 100644
--- a/misc/cgo/life/golden.out
+++ b/misc/cgo/life/main.out
@@ -1,4 +1,3 @@
-* life
XXX XXX
diff --git a/misc/cgo/life/test.bash b/misc/cgo/life/test.bash
deleted file mode 100755
index bb483522c..000000000
--- a/misc/cgo/life/test.bash
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-# Copyright 2010 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.
-
-set -e
-go build -o life main.go
-
-echo '*' life >run.out
-./life >>run.out
-diff run.out golden.out
-
-rm -f life
-
diff --git a/misc/cgo/stdio/chain.go b/misc/cgo/stdio/chain.go
index 1cf0b1fe5..a55cefa40 100644
--- a/misc/cgo/stdio/chain.go
+++ b/misc/cgo/stdio/chain.go
@@ -1,3 +1,5 @@
+// cmpout
+
// 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.
diff --git a/misc/cgo/stdio/chain.out b/misc/cgo/stdio/chain.out
new file mode 100644
index 000000000..963cf9b66
--- /dev/null
+++ b/misc/cgo/stdio/chain.out
@@ -0,0 +1,55 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
diff --git a/misc/cgo/stdio/fib.go b/misc/cgo/stdio/fib.go
index 6d3ccfd52..981ffeb9a 100644
--- a/misc/cgo/stdio/fib.go
+++ b/misc/cgo/stdio/fib.go
@@ -1,3 +1,5 @@
+// cmpout
+
// 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.
diff --git a/misc/cgo/stdio/fib.out b/misc/cgo/stdio/fib.out
new file mode 100644
index 000000000..17ff50335
--- /dev/null
+++ b/misc/cgo/stdio/fib.out
@@ -0,0 +1,91 @@
+0
+1
+1
+2
+3
+5
+8
+13
+21
+34
+55
+89
+144
+233
+377
+610
+987
+1597
+2584
+4181
+6765
+10946
+17711
+28657
+46368
+75025
+121393
+196418
+317811
+514229
+832040
+1346269
+2178309
+3524578
+5702887
+9227465
+14930352
+24157817
+39088169
+63245986
+102334155
+165580141
+267914296
+433494437
+701408733
+1134903170
+1836311903
+2971215073
+4807526976
+7778742049
+12586269025
+20365011074
+32951280099
+53316291173
+86267571272
+139583862445
+225851433717
+365435296162
+591286729879
+956722026041
+1548008755920
+2504730781961
+4052739537881
+6557470319842
+10610209857723
+17167680177565
+27777890035288
+44945570212853
+72723460248141
+117669030460994
+190392490709135
+308061521170129
+498454011879264
+806515533049393
+1304969544928657
+2111485077978050
+3416454622906707
+5527939700884757
+8944394323791464
+14472334024676221
+23416728348467685
+37889062373143906
+61305790721611591
+99194853094755497
+160500643816367088
+259695496911122585
+420196140727489673
+679891637638612258
+1100087778366101931
+1779979416004714189
+2880067194370816120
diff --git a/misc/cgo/stdio/file.go b/misc/cgo/stdio/file.go
index 6e7d479ad..e7bb906a5 100644
--- a/misc/cgo/stdio/file.go
+++ b/misc/cgo/stdio/file.go
@@ -1,3 +1,5 @@
+// skip
+
// 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.
@@ -23,9 +25,6 @@ import "unsafe"
type File C.FILE
-var Stdout = (*File)(C.stdout)
-var Stderr = (*File)(C.stderr)
-
// Test reference to library symbol.
// Stdout and stderr are too special to be a reliable test.
//var = C.environ
diff --git a/misc/cgo/stdio/hello.go b/misc/cgo/stdio/hello.go
index 4ab3c7447..9cfeefbba 100644
--- a/misc/cgo/stdio/hello.go
+++ b/misc/cgo/stdio/hello.go
@@ -1,3 +1,5 @@
+// cmpout
+
// 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.
diff --git a/misc/cgo/stdio/hello.out b/misc/cgo/stdio/hello.out
new file mode 100644
index 000000000..4b5fa6370
--- /dev/null
+++ b/misc/cgo/stdio/hello.out
@@ -0,0 +1 @@
+hello, world
diff --git a/misc/cgo/stdio/golden.out b/misc/cgo/stdio/run.out
index c0e496547..c0e496547 100644
--- a/misc/cgo/stdio/golden.out
+++ b/misc/cgo/stdio/run.out
diff --git a/misc/cgo/stdio/stdio.go b/misc/cgo/stdio/stdio.go
new file mode 100644
index 000000000..76cb8ad80
--- /dev/null
+++ b/misc/cgo/stdio/stdio.go
@@ -0,0 +1,22 @@
+// skip
+
+// 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.
+
+package stdio
+
+/*
+#include <stdio.h>
+
+// on mingw, stderr and stdout are defined as &_iob[FILENO]
+// on netbsd, they are defined as &__sF[FILENO]
+// and cgo doesn't recognize them, so write a function to get them,
+// instead of depending on internals of libc implementation.
+FILE *getStdout(void) { return stdout; }
+FILE *getStderr(void) { return stderr; }
+*/
+import "C"
+
+var Stdout = (*File)(C.getStdout())
+var Stderr = (*File)(C.getStderr())
diff --git a/misc/cgo/stdio/test.bash b/misc/cgo/stdio/test.bash
deleted file mode 100755
index 21829fa31..000000000
--- a/misc/cgo/stdio/test.bash
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-# 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.
-
-set -e
-go build hello.go
-go build fib.go
-go build chain.go
-
-echo '*' hello >run.out
-./hello >>run.out
-echo '*' fib >>run.out
-./fib >>run.out
-echo '*' chain >>run.out
-./chain >>run.out
-diff run.out golden.out
-
-rm -f hello fib chain
-
diff --git a/misc/cgo/test/Makefile b/misc/cgo/test/Makefile
deleted file mode 100644
index 2b7187acb..000000000
--- a/misc/cgo/test/Makefile
+++ /dev/null
@@ -1,48 +0,0 @@
-# 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.
-
-include ../../../src/Make.inc
-
-TARG=runtime/cgotest
-
-CGOFILES=\
- align.go\
- basic.go\
- callback.go\
- env.go\
- exports.go\
- helpers.go\
- issue1222.go\
- issue1328.go\
- issue1560.go\
- issue2462.go\
- duplicate_symbol.go\
-
-CGO_OFILES=\
- callback_c.o\
-
-OFILES=\
- runtime.$O\
-
-ifeq ($(GOOS),windows)
-GCCVERSION=$(shell gcc -dumpversion)
-ifeq ($(GOARCH),386)
-GCCLIBDIR=/mingw/lib/gcc/mingw32/$(GCCVERSION)
-CHKSTK=_chkstk.o
-else
-GCCLIBDIR=/mingw/lib/gcc/x86_64-w64-mingw32/$(GCCVERSION)
-CHKSTK=_chkstk_ms.o
-endif
-
-CGOFILES+=sleep_windows.go
-CGO_OFILES+=$(CHKSTK)
-
-$(CHKSTK):
- ar -x "$(GCCLIBDIR)/libgcc.a" $@
-endif
-
-include ../../../src/Make.pkg
-
-test:
- echo cgo: tests disabled. gotest is gone. TODO \ No newline at end of file
diff --git a/misc/cgo/test/backdoor/runtime.c b/misc/cgo/test/backdoor/runtime.c
index 54e6a1ef8..194a9c8e4 100644
--- a/misc/cgo/test/backdoor/runtime.c
+++ b/misc/cgo/test/backdoor/runtime.c
@@ -6,6 +6,8 @@
// Must be in a non-cgo-using package so that
// the go command compiles this file with 6c, not gcc.
+// +build gc
+
typedef char bool;
bool runtime·lockedOSThread(void);
diff --git a/misc/cgo/test/backdoor/runtime_gccgo.c b/misc/cgo/test/backdoor/runtime_gccgo.c
new file mode 100644
index 000000000..218b2c3eb
--- /dev/null
+++ b/misc/cgo/test/backdoor/runtime_gccgo.c
@@ -0,0 +1,18 @@
+// Copyright 2012 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.
+
+// Expose some runtime functions for testing.
+// This is the gccgo version of runtime.c.
+
+// +build gccgo
+
+_Bool runtime_lockedOSThread(void);
+
+_Bool LockedOSThread(void) asm(GOPKGPATH ".LockedOSThread");
+
+_Bool
+LockedOSThread(void)
+{
+ return runtime_lockedOSThread();
+}
diff --git a/misc/cgo/test/basic.go b/misc/cgo/test/basic.go
index 70ec5e43a..79cbf2b9c 100644
--- a/misc/cgo/test/basic.go
+++ b/misc/cgo/test/basic.go
@@ -14,15 +14,16 @@ package cgotest
#define SHIFT(x, y) ((x)<<(y))
#define KILO SHIFT(1, 10)
+#define UINT32VAL 0xc008427bU
enum E {
Enum1 = 1,
Enum2 = 2,
};
-typedef unsigned char uuid_t[20];
+typedef unsigned char cgo_uuid_t[20];
-void uuid_generate(uuid_t x) {
+void uuid_generate(cgo_uuid_t x) {
x[0] = 0;
}
@@ -55,6 +56,7 @@ int add(int x, int y) {
*/
import "C"
import (
+ "runtime"
"syscall"
"testing"
"unsafe"
@@ -65,7 +67,7 @@ const EINVAL = C.EINVAL /* test #define */
var KILO = C.KILO
func uuidgen() {
- var uuid C.uuid_t
+ var uuid C.cgo_uuid_t
C.uuid_generate(&uuid[0])
}
@@ -118,7 +120,12 @@ func testErrno(t *testing.T) {
func testMultipleAssign(t *testing.T) {
p := C.CString("234")
n, m := C.strtol(p, nil, 345), C.strtol(p, nil, 10)
- if n != 0 || m != 234 {
+ if runtime.GOOS == "openbsd" {
+ // Bug in OpenBSD strtol(3) - base > 36 succeeds.
+ if (n != 0 && n != 239089) || m != 234 {
+ t.Fatal("Strtol x2: ", n, m)
+ }
+ } else if n != 0 || m != 234 {
t.Fatal("Strtol x2: ", n, m)
}
C.free(unsafe.Pointer(p))
@@ -141,3 +148,12 @@ func benchCgoCall(b *testing.B) {
C.add(x, y)
}
}
+
+// Issue 2470.
+func testUnsignedInt(t *testing.T) {
+ a := (int64)(C.UINT32VAL)
+ b := (int64)(0xc008427b)
+ if a != b {
+ t.Errorf("Incorrect unsigned int - got %x, want %x", a, b)
+ }
+}
diff --git a/misc/cgo/test/callback.go b/misc/cgo/test/callback.go
index e6a1462b3..4f5d3f855 100644
--- a/misc/cgo/test/callback.go
+++ b/misc/cgo/test/callback.go
@@ -110,7 +110,7 @@ func testZeroArgCallback(t *testing.T) {
func goFoo() {
x := 1
for i := 0; i < 10000; i++ {
- // variadic call mallocs + writes to
+ // variadic call mallocs + writes to
variadic(x, x, x)
if x != 1 {
panic("bad x")
diff --git a/misc/cgo/test/cgo_linux_test.go b/misc/cgo/test/cgo_linux_test.go
new file mode 100644
index 000000000..056d67c96
--- /dev/null
+++ b/misc/cgo/test/cgo_linux_test.go
@@ -0,0 +1,9 @@
+// Copyright 2012 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.
+
+package cgotest
+
+import "testing"
+
+func TestSetgid(t *testing.T) { testSetgid(t) }
diff --git a/misc/cgo/test/cgo_test.go b/misc/cgo/test/cgo_test.go
index 34beee69d..536fa507a 100644
--- a/misc/cgo/test/cgo_test.go
+++ b/misc/cgo/test/cgo_test.go
@@ -6,7 +6,7 @@ package cgotest
import "testing"
-// The actual test functions are in non-_test.go files
+// The actual test functions are in non-_test.go files
// so that they can use cgo (import "C").
// These wrappers are here for gotest to find.
@@ -16,6 +16,7 @@ func TestEnum(t *testing.T) { testEnum(t) }
func TestAtol(t *testing.T) { testAtol(t) }
func TestErrno(t *testing.T) { testErrno(t) }
func TestMultipleAssign(t *testing.T) { testMultipleAssign(t) }
+func TestUnsignedInt(t *testing.T) { testUnsignedInt(t) }
func TestCallback(t *testing.T) { testCallback(t) }
func TestCallbackGC(t *testing.T) { testCallbackGC(t) }
func TestCallbackPanic(t *testing.T) { testCallbackPanic(t) }
@@ -27,5 +28,13 @@ func Test1328(t *testing.T) { test1328(t) }
func TestParallelSleep(t *testing.T) { testParallelSleep(t) }
func TestSetEnv(t *testing.T) { testSetEnv(t) }
func TestHelpers(t *testing.T) { testHelpers(t) }
+func TestLibgcc(t *testing.T) { testLibgcc(t) }
+func Test1635(t *testing.T) { test1635(t) }
+func TestPrintf(t *testing.T) { testPrintf(t) }
+func Test4029(t *testing.T) { test4029(t) }
+func TestBoolAlign(t *testing.T) { testBoolAlign(t) }
+func Test3729(t *testing.T) { test3729(t) }
+func Test3775(t *testing.T) { test3775(t) }
+func TestCthread(t *testing.T) { testCthread(t) }
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
diff --git a/misc/cgo/test/cthread.go b/misc/cgo/test/cthread.go
new file mode 100644
index 000000000..d918d033f
--- /dev/null
+++ b/misc/cgo/test/cthread.go
@@ -0,0 +1,44 @@
+// 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.
+
+package cgotest
+
+// extern void doAdd(int, int);
+import "C"
+
+import (
+ "runtime"
+ "sync"
+ "testing"
+)
+
+var sum struct {
+ sync.Mutex
+ i int
+}
+
+//export Add
+func Add(x int) {
+ defer func() {
+ recover()
+ }()
+ sum.Lock()
+ sum.i += x
+ sum.Unlock()
+ var p *int
+ *p = 2
+}
+
+func testCthread(t *testing.T) {
+ if runtime.GOARCH == "arm" {
+ t.Skip("testCthread disabled on arm")
+ }
+
+ C.doAdd(10, 6)
+
+ want := 10 * (10 - 1) / 2 * 6
+ if sum.i != want {
+ t.Fatalf("sum=%d, want %d", sum.i, want)
+ }
+}
diff --git a/misc/cgo/test/cthread_unix.c b/misc/cgo/test/cthread_unix.c
new file mode 100644
index 000000000..998bc00cb
--- /dev/null
+++ b/misc/cgo/test/cthread_unix.c
@@ -0,0 +1,34 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd
+
+#include <pthread.h>
+#include "_cgo_export.h"
+
+static void*
+addThread(void *p)
+{
+ int i, max;
+
+ max = *(int*)p;
+ for(i=0; i<max; i++)
+ Add(i);
+ return 0;
+}
+
+void
+doAdd(int max, int nthread)
+{
+ enum { MaxThread = 20 };
+ int i;
+ pthread_t thread_id[MaxThread];
+
+ if(nthread > MaxThread)
+ nthread = MaxThread;
+ for(i=0; i<nthread; i++)
+ pthread_create(&thread_id[i], 0, addThread, &max);
+ for(i=0; i<nthread; i++)
+ pthread_join(thread_id[i], 0);
+}
diff --git a/misc/cgo/test/cthread_windows.c b/misc/cgo/test/cthread_windows.c
new file mode 100644
index 000000000..5f370a818
--- /dev/null
+++ b/misc/cgo/test/cthread_windows.c
@@ -0,0 +1,37 @@
+// 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.
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <process.h>
+#include "_cgo_export.h"
+
+__stdcall
+static unsigned int
+addThread(void *p)
+{
+ int i, max;
+
+ max = *(int*)p;
+ for(i=0; i<max; i++)
+ Add(i);
+ return 0;
+}
+
+void
+doAdd(int max, int nthread)
+{
+ enum { MaxThread = 20 };
+ int i;
+ uintptr_t thread_id[MaxThread];
+
+ if(nthread > MaxThread)
+ nthread = MaxThread;
+ for(i=0; i<nthread; i++)
+ thread_id[i] = _beginthreadex(0, 0, addThread, &max, 0, 0);
+ for(i=0; i<nthread; i++) {
+ WaitForSingleObject((HANDLE)thread_id[i], INFINITE);
+ CloseHandle((HANDLE)thread_id[i]);
+ }
+}
diff --git a/misc/cgo/test/issue1560.go b/misc/cgo/test/issue1560.go
index 3faa966e7..147ce94b5 100644
--- a/misc/cgo/test/issue1560.go
+++ b/misc/cgo/test/issue1560.go
@@ -15,6 +15,7 @@ void twoSleep(int);
import "C"
import (
+ "runtime"
"testing"
"time"
)
@@ -27,19 +28,48 @@ func parallelSleep(n int) {
}
//export BackgroundSleep
-func BackgroundSleep(n int) {
+func BackgroundSleep(n int32) {
go func() {
C.sleep(C.uint(n))
sleepDone <- true
}()
}
+// wasteCPU starts a background goroutine to waste CPU
+// to cause the power management to raise the CPU frequency.
+// On ARM this has the side effect of making sleep more accurate.
+func wasteCPU() chan struct{} {
+ done := make(chan struct{})
+ go func() {
+ for {
+ select {
+ case <-done:
+ return
+ default:
+ }
+ }
+ }()
+ // pause for a short amount of time to allow the
+ // power management to recognise load has risen.
+ <-time.After(300 * time.Millisecond)
+ return done
+}
+
func testParallelSleep(t *testing.T) {
+ if runtime.GOARCH == "arm" {
+ // on ARM, the 1.3s deadline is frequently missed,
+ // and burning cpu seems to help
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
+ defer close(wasteCPU())
+ }
+
+ sleepSec := 1
start := time.Now()
- parallelSleep(1)
- dt := time.Now().Sub(start)
- // bug used to run sleeps in serial, producing a 2-second delay.
- if dt >= 1300*time.Millisecond {
- t.Fatalf("parallel 1-second sleeps slept for %f seconds", dt.Seconds())
+ parallelSleep(sleepSec)
+ dt := time.Since(start)
+ t.Logf("sleep(%d) slept for %v", sleepSec, dt)
+ // bug used to run sleeps in serial, producing a 2*sleepSec-second delay.
+ if dt >= time.Duration(sleepSec)*1300*time.Millisecond {
+ t.Fatalf("parallel %d-second sleeps slept for %f seconds", sleepSec, dt.Seconds())
}
}
diff --git a/misc/cgo/test/issue1635.go b/misc/cgo/test/issue1635.go
new file mode 100644
index 000000000..6bfe110fd
--- /dev/null
+++ b/misc/cgo/test/issue1635.go
@@ -0,0 +1,33 @@
+// Copyright 2012 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.
+
+package cgotest
+
+/*
+// Mac OS X's gcc will generate scattered relocation 2/1 for
+// this function on Darwin/386, and 8l couldn't handle it.
+// this example is in issue 1635
+#include <stdio.h>
+void scatter() {
+ void *p = scatter;
+ printf("scatter = %p\n", p);
+}
+
+// this example is in issue 3253
+int hola = 0;
+int testHola() { return hola; }
+*/
+import "C"
+
+import "testing"
+
+func test1635(t *testing.T) {
+ C.scatter()
+ if v := C.hola; v != 0 {
+ t.Fatalf("C.hola is %d, should be 0", v)
+ }
+ if v := C.testHola(); v != 0 {
+ t.Fatalf("C.testHola() is %d, should be 0", v)
+ }
+}
diff --git a/misc/cgo/test/issue3261.go b/misc/cgo/test/issue3261.go
new file mode 100644
index 000000000..0411be892
--- /dev/null
+++ b/misc/cgo/test/issue3261.go
@@ -0,0 +1,43 @@
+// Copyright 2012 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.
+
+package cgotest
+
+/*
+// libgcc on ARM might be compiled as thumb code, but our 5l
+// can't handle that, so we have to disable this test on arm.
+#ifdef __ARMEL__
+#include <stdio.h>
+int vabs(int x) {
+ puts("testLibgcc is disabled on ARM because 5l cannot handle thumb library.");
+ return (x < 0) ? -x : x;
+}
+#else
+int __absvsi2(int); // dummy prototype for libgcc function
+// we shouldn't name the function abs, as gcc might use
+// the builtin one.
+int vabs(int x) { return __absvsi2(x); }
+#endif
+*/
+import "C"
+
+import "testing"
+
+func testLibgcc(t *testing.T) {
+ var table = []struct {
+ in, out C.int
+ }{
+ {0, 0},
+ {1, 1},
+ {-42, 42},
+ {1000300, 1000300},
+ {1 - 1<<31, 1<<31 - 1},
+ }
+ for _, v := range table {
+ if o := C.vabs(v.in); o != v.out {
+ t.Fatalf("abs(%d) got %d, should be %d", v.in, o, v.out)
+ return
+ }
+ }
+}
diff --git a/misc/cgo/test/issue3729.go b/misc/cgo/test/issue3729.go
new file mode 100644
index 000000000..1bea38b6e
--- /dev/null
+++ b/misc/cgo/test/issue3729.go
@@ -0,0 +1,47 @@
+// Copyright 2012 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.
+
+// Issue 3729: cmd/cgo: access errno from void C function
+// void f(void) returns [0]byte, error in Go world.
+
+// +build !windows
+
+package cgotest
+
+/*
+#include <errno.h>
+
+void g(void) {
+ errno = E2BIG;
+}
+
+// try to pass some non-trivial arguments to function g2
+const char _expA = 0x42;
+const float _expB = 3.14159;
+const short _expC = 0x55aa;
+const int _expD = 0xdeadbeef;
+void g2(int x, char a, float b, short c, int d) {
+ if (a == _expA && b == _expB && c == _expC && d == _expD)
+ errno = x;
+ else
+ errno = -1;
+}
+*/
+import "C"
+
+import (
+ "syscall"
+ "testing"
+)
+
+func test3729(t *testing.T) {
+ _, e := C.g()
+ if e != syscall.E2BIG {
+ t.Errorf("got %q, expect %q", e, syscall.E2BIG)
+ }
+ _, e = C.g2(C.EINVAL, C._expA, C._expB, C._expC, C._expD)
+ if e != syscall.EINVAL {
+ t.Errorf("got %q, expect %q", e, syscall.EINVAL)
+ }
+}
diff --git a/misc/cgo/test/issue3729w.go b/misc/cgo/test/issue3729w.go
new file mode 100644
index 000000000..702115b81
--- /dev/null
+++ b/misc/cgo/test/issue3729w.go
@@ -0,0 +1,16 @@
+// Copyright 2012 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.
+
+// Issue 3729: cmd/cgo: access errno from void C function
+// void f(void) returns [0]byte, error in Go world.
+
+// +build windows
+
+package cgotest
+
+import "testing"
+
+func test3729(t *testing.T) {
+ t.Log("skip errno test on Windows")
+}
diff --git a/misc/cgo/test/issue3741.go b/misc/cgo/test/issue3741.go
new file mode 100644
index 000000000..3d3bbf951
--- /dev/null
+++ b/misc/cgo/test/issue3741.go
@@ -0,0 +1,22 @@
+// Copyright 2012 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.
+
+package cgotest
+
+import "C"
+
+//export exportSliceIn
+func exportSliceIn(s []byte) bool {
+ return len(s) == cap(s)
+}
+
+//export exportSliceOut
+func exportSliceOut() []byte {
+ return []byte{1}
+}
+
+//export exportSliceInOut
+func exportSliceInOut(s []byte) []byte {
+ return s
+}
diff --git a/misc/cgo/test/issue3775.go b/misc/cgo/test/issue3775.go
new file mode 100644
index 000000000..c05a5d4be
--- /dev/null
+++ b/misc/cgo/test/issue3775.go
@@ -0,0 +1,29 @@
+package cgotest
+
+/*
+void lockOSThreadCallback(void);
+inline static void lockOSThreadC(void)
+{
+ lockOSThreadCallback();
+}
+int usleep(unsigned usec);
+*/
+import "C"
+
+import (
+ "runtime"
+ "testing"
+)
+
+func test3775(t *testing.T) {
+ // Used to panic because of the UnlockOSThread below.
+ C.lockOSThreadC()
+}
+
+//export lockOSThreadCallback
+func lockOSThreadCallback() {
+ runtime.LockOSThread()
+ runtime.UnlockOSThread()
+ go C.usleep(10000)
+ runtime.Gosched()
+}
diff --git a/misc/cgo/test/issue3945.go b/misc/cgo/test/issue3945.go
new file mode 100644
index 000000000..331cd0baf
--- /dev/null
+++ b/misc/cgo/test/issue3945.go
@@ -0,0 +1,22 @@
+// Copyright 2012 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.
+
+package cgotest
+
+// Test that cgo reserves enough stack space during cgo call.
+// See http://golang.org/issue/3945 for details.
+
+// #include <stdio.h>
+//
+// void say() {
+// printf("%s from C\n", "hello");
+// }
+//
+import "C"
+
+import "testing"
+
+func testPrintf(t *testing.T) {
+ C.say()
+}
diff --git a/misc/cgo/test/issue4029.go b/misc/cgo/test/issue4029.go
new file mode 100644
index 000000000..7495d38fe
--- /dev/null
+++ b/misc/cgo/test/issue4029.go
@@ -0,0 +1,60 @@
+// Copyright 2012 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.
+
+// +build !windows
+
+package cgotest
+
+/*
+#include <dlfcn.h>
+#cgo linux LDFLAGS: -ldl
+*/
+import "C"
+
+import (
+ "fmt"
+ "testing"
+)
+
+//export IMPIsOpaque
+func IMPIsOpaque() {
+ fmt.Println("isOpaque")
+}
+
+//export IMPInitWithFrame
+func IMPInitWithFrame() {
+ fmt.Println("IInitWithFrame")
+}
+
+//export IMPDrawRect
+func IMPDrawRect() {
+ fmt.Println("drawRect:")
+}
+
+//export IMPWindowResize
+func IMPWindowResize() {
+ fmt.Println("windowDidResize:")
+}
+
+func test4029(t *testing.T) {
+ loadThySelf(t, "IMPWindowResize")
+ loadThySelf(t, "IMPDrawRect")
+ loadThySelf(t, "IMPInitWithFrame")
+ loadThySelf(t, "IMPIsOpaque")
+}
+
+func loadThySelf(t *testing.T, symbol string) {
+ this_process := C.dlopen(nil, C.RTLD_NOW)
+ if this_process == nil {
+ t.Fatal("dlopen:", C.GoString(C.dlerror()))
+ }
+ defer C.dlclose(this_process)
+
+ symbol_address := C.dlsym(this_process, C.CString(symbol))
+ if symbol_address == nil {
+ t.Fatal("dlsym:", C.GoString(C.dlerror()))
+ } else {
+ t.Log(symbol, symbol_address)
+ }
+}
diff --git a/misc/cgo/test/issue4029w.go b/misc/cgo/test/issue4029w.go
new file mode 100644
index 000000000..1cf43df37
--- /dev/null
+++ b/misc/cgo/test/issue4029w.go
@@ -0,0 +1,12 @@
+// Copyright 2012 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.
+
+// +build windows
+
+package cgotest
+
+import "testing"
+
+func test4029(t *testing.T) {
+}
diff --git a/misc/cgo/test/issue4054a.go b/misc/cgo/test/issue4054a.go
new file mode 100644
index 000000000..2abdac590
--- /dev/null
+++ b/misc/cgo/test/issue4054a.go
@@ -0,0 +1,23 @@
+// Copyright 2012 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.
+
+package cgotest
+
+/*
+typedef enum {
+ A = 0,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+} issue4054a;
+*/
+import "C"
+
+var issue4054a = []int{C.A, C.B, C.C, C.D, C.E, C.F, C.G, C.H, C.I, C.J}
diff --git a/misc/cgo/test/issue4054b.go b/misc/cgo/test/issue4054b.go
new file mode 100644
index 000000000..048964c89
--- /dev/null
+++ b/misc/cgo/test/issue4054b.go
@@ -0,0 +1,23 @@
+// Copyright 2012 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.
+
+package cgotest
+
+/*
+typedef enum {
+ A = 0,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+} issue4054b;
+*/
+import "C"
+
+var issue4054b = []int{C.A, C.B, C.C, C.D, C.E, C.F, C.G, C.H, C.I, C.J}
diff --git a/misc/cgo/test/issue4273.c b/misc/cgo/test/issue4273.c
new file mode 100644
index 000000000..a3fcf3b0a
--- /dev/null
+++ b/misc/cgo/test/issue4273.c
@@ -0,0 +1,10 @@
+// Copyright 2012 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.
+
+#ifdef __ELF__
+__attribute__((weak))
+__attribute__((visibility("hidden")))
+void _compilerrt_abort_impl(const char *file, int line, const char *func) {
+}
+#endif
diff --git a/misc/cgo/test/issue4273b.c b/misc/cgo/test/issue4273b.c
new file mode 100644
index 000000000..93e2f4fab
--- /dev/null
+++ b/misc/cgo/test/issue4273b.c
@@ -0,0 +1,11 @@
+// Copyright 2012 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.
+
+#ifdef __ELF__
+extern void _compilerrt_abort_impl(const char *file, int line, const char *func);
+
+void __my_abort(const char *file, int line, const char *func) {
+ _compilerrt_abort_impl(file, line, func);
+}
+#endif
diff --git a/misc/cgo/test/issue4417.go b/misc/cgo/test/issue4417.go
new file mode 100644
index 000000000..0b48071d4
--- /dev/null
+++ b/misc/cgo/test/issue4417.go
@@ -0,0 +1,42 @@
+// Copyright 2012 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.
+
+// Issue 4417: cmd/cgo: bool alignment/padding issue.
+// bool alignment is wrong and causing wrong arguments when calling functions.
+//
+
+package cgotest
+
+/*
+#include <stdbool.h>
+
+static int c_bool(bool a, bool b, int c, bool d, bool e) {
+ return c;
+}
+*/
+import "C"
+import "testing"
+
+func testBoolAlign(t *testing.T) {
+ b := C.c_bool(true, true, 10, true, false)
+ if b != 10 {
+ t.Fatalf("found %d expected 10\n", b)
+ }
+ b = C.c_bool(true, true, 5, true, true)
+ if b != 5 {
+ t.Fatalf("found %d expected 5\n", b)
+ }
+ b = C.c_bool(true, true, 3, true, false)
+ if b != 3 {
+ t.Fatalf("found %d expected 3\n", b)
+ }
+ b = C.c_bool(false, false, 1, true, false)
+ if b != 1 {
+ t.Fatalf("found %d expected 1\n", b)
+ }
+ b = C.c_bool(false, true, 200, true, false)
+ if b != 200 {
+ t.Fatalf("found %d expected 200\n", b)
+ }
+}
diff --git a/misc/cgo/test/setgid_linux.go b/misc/cgo/test/setgid_linux.go
new file mode 100644
index 000000000..829afce1b
--- /dev/null
+++ b/misc/cgo/test/setgid_linux.go
@@ -0,0 +1,32 @@
+// Copyright 2012 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.
+
+// Test that setgid does not hang on GNU/Linux.
+// See http://code.google.com/p/go/issues/detail?id=3871 for details.
+
+package cgotest
+
+/*
+#include <sys/types.h>
+#include <unistd.h>
+*/
+import "C"
+
+import (
+ "testing"
+ "time"
+)
+
+func testSetgid(t *testing.T) {
+ c := make(chan bool)
+ go func() {
+ C.setgid(0)
+ c <- true
+ }()
+ select {
+ case <-c:
+ case <-time.After(5 * time.Second):
+ t.Error("setgid hung")
+ }
+}
diff --git a/misc/cgo/test/sleep_windows.go b/misc/cgo/test/sleep_windows_386.go
index 007a1bb4c..75687d783 100644
--- a/misc/cgo/test/sleep_windows.go
+++ b/misc/cgo/test/sleep_windows_386.go
@@ -5,6 +5,10 @@
package cgotest
/*
+// mingw32 on windows/386 provides usleep() but not sleep(),
+// as we don't want to require all other OSes to provide usleep,
+// we emulate sleep(int s) using win32 API Sleep(int ms).
+
#include <windows.h>
unsigned int sleep(unsigned int seconds) {
diff --git a/misc/cgo/testso/test.bash b/misc/cgo/testso/test.bash
index ecef873c8..5f113d216 100755
--- a/misc/cgo/testso/test.bash
+++ b/misc/cgo/testso/test.bash
@@ -4,7 +4,7 @@
# license that can be found in the LICENSE file.
set -e
-gcc $(go env GOGCCFLAGS) -shared -o libcgosotest.so cgoso_c.c
+$(go env CC) $(go env GOGCCFLAGS) -shared -o libcgosotest.so cgoso_c.c
go build main.go
LD_LIBRARY_PATH=. ./main
rm -f libcgosotest.so main
diff --git a/misc/chrome/gophertool/background.html b/misc/chrome/gophertool/background.html
index 058c18142..06daa98b1 100644
--- a/misc/chrome/gophertool/background.html
+++ b/misc/chrome/gophertool/background.html
@@ -6,19 +6,7 @@
-->
<head>
<script src="gopher.js"></script>
-<script>
-
-chrome.omnibox.onInputEntered.addListener(function(t) {
- var url = urlForInput(t);
- if (url) {
- chrome.tabs.getSelected(null, function(tab) {
- if (!tab) return;
- chrome.tabs.update(tab.id, { "url": url, "selected": true });
- });
- }
-});
-
-</script>
+<script src="background.js"></script>
</head>
</html>
diff --git a/misc/chrome/gophertool/background.js b/misc/chrome/gophertool/background.js
new file mode 100644
index 000000000..d18faa986
--- /dev/null
+++ b/misc/chrome/gophertool/background.js
@@ -0,0 +1,9 @@
+chrome.omnibox.onInputEntered.addListener(function(t) {
+ var url = urlForInput(t);
+ if (url) {
+ chrome.tabs.getSelected(null, function(tab) {
+ if (!tab) return;
+ chrome.tabs.update(tab.id, { "url": url, "selected": true });
+ });
+ }
+});
diff --git a/misc/chrome/gophertool/gopher.js b/misc/chrome/gophertool/gopher.js
index 847c1c70d..3238f0fcc 100644
--- a/misc/chrome/gophertool/gopher.js
+++ b/misc/chrome/gophertool/gopher.js
@@ -12,7 +12,7 @@ function urlForInput(t) {
}
if (numericRE.test(t)) {
- if (t < 1000000) {
+ if (t < 150000) {
return "http://code.google.com/p/go/issues/detail?id=" + t;
}
return "http://codereview.appspot.com/" + t + "/";
diff --git a/misc/chrome/gophertool/manifest.json b/misc/chrome/gophertool/manifest.json
index 3a2540a86..04386594a 100644
--- a/misc/chrome/gophertool/manifest.json
+++ b/misc/chrome/gophertool/manifest.json
@@ -1,11 +1,14 @@
{
"name": "Hacking Gopher",
"version": "1.0",
+ "manifest_version": 2,
"description": "Go Hacking utility",
- "background_page": "background.html",
+ "background": {
+ "page": "background.html"
+ },
"browser_action": {
"default_icon": "gopher.png",
- "popup": "popup.html"
+ "default_popup": "popup.html"
},
"omnibox": { "keyword": "golang" },
"icons": {
diff --git a/misc/chrome/gophertool/popup.html b/misc/chrome/gophertool/popup.html
index 4816c392b..8bb7795fa 100644
--- a/misc/chrome/gophertool/popup.html
+++ b/misc/chrome/gophertool/popup.html
@@ -6,49 +6,14 @@
-->
<head>
<script src="gopher.js"></script>
-<script>
-
-function focusinput() {
- document.getElementById("inputbox").focus();
-}
-
-function navigate() {
- 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;
-}
-
-function openURL(url) {
- chrome.tabs.create({ "url": url })
-}
-
-</script>
+<script src="popup.js"></script>
</head>
-<body onload="focusinput()" style='margin: 0.5em; font-family: sans;'>
-<small><a href="#" onclick="openURL('http://code.google.com/p/go/issues/list')">issue</a>,
-<a href="#" onclick="openURL('http://codereview.appspot.com/')">codereview</a>,
-<a href="#" onclick="openURL('http://code.google.com/p/go/source/list')">commit</a>, or
-<a href="#" onclick="openURL('http://golang.org/pkg/')">pkg</a> id/name:</small>
-<form style='margin: 0' onsubmit="return navigate();"><nobr><input id="inputbox" size=10 /><input type="submit" value="go" /></nobr></form>
-<small>Also: <a href="#" onclick="openURL('http://build.golang.org/')">buildbots</small>
+<body style='margin: 0.5em; font-family: sans;'>
+<small><a href="#" url="http://code.google.com/p/go/issues/list">issue</a>,
+<a href="#" url="http://codereview.appspot.com/">codereview</a>,
+<a href="#" url="http://code.google.com/p/go/source/list">commit</a>, or
+<a href="#" url="http://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="http://build.golang.org">buildbots</a></small>
</body>
</html>
diff --git a/misc/chrome/gophertool/popup.js b/misc/chrome/gophertool/popup.js
new file mode 100644
index 000000000..410d65120
--- /dev/null
+++ b/misc/chrome/gophertool/popup.js
@@ -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;
+});
diff --git a/misc/dashboard/app/app.yaml b/misc/dashboard/app/app.yaml
index 6e19db09c..c5a1f6cb8 100644
--- a/misc/dashboard/app/app.yaml
+++ b/misc/dashboard/app/app.yaml
@@ -6,7 +6,7 @@
application: golang-org
version: build
runtime: go
-api_version: go1beta
+api_version: go1
handlers:
- url: /static
diff --git a/misc/dashboard/app/build/build.go b/misc/dashboard/app/build/build.go
index c49fa8bb2..e0c0f0048 100644
--- a/misc/dashboard/app/build/build.go
+++ b/misc/dashboard/app/build/build.go
@@ -49,6 +49,10 @@ func (p *Package) LastCommit(c appengine.Context) (*Commit, error) {
Order("-Time").
Limit(1).
GetAll(c, &commits)
+ if _, ok := err.(*datastore.ErrFieldMismatch); ok {
+ // Some fields have been removed, so it's okay to ignore this error.
+ err = nil
+ }
if err != nil {
return nil, err
}
@@ -65,6 +69,10 @@ func GetPackage(c appengine.Context, path string) (*Package, error) {
if err == datastore.ErrNoSuchEntity {
return nil, fmt.Errorf("package %q not found", path)
}
+ if _, ok := err.(*datastore.ErrFieldMismatch); ok {
+ // Some fields have been removed, so it's okay to ignore this error.
+ err = nil
+ }
return p, err
}
@@ -111,19 +119,35 @@ func (c *Commit) Valid() error {
return nil
}
+// each result line is approx 105 bytes. This constant is a tradeoff between
+// build history and the AppEngine datastore limit of 1mb.
+const maxResults = 1000
+
// AddResult adds the denormalized Reuslt data to the Commit's Result field.
// It must be called from inside a datastore transaction.
func (com *Commit) AddResult(c appengine.Context, r *Result) error {
if err := datastore.Get(c, com.Key(c), com); err != nil {
return fmt.Errorf("getting Commit: %v", err)
}
- com.ResultData = append(com.ResultData, r.Data())
+ com.ResultData = trim(append(com.ResultData, r.Data()), maxResults)
if _, err := datastore.Put(c, com.Key(c), com); err != nil {
return fmt.Errorf("putting Commit: %v", err)
}
return nil
}
+func trim(s []string, n int) []string {
+ l := min(len(s), n)
+ return s[len(s)-l:]
+}
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
// Result returns the build Result for this Commit for the given builder/goHash.
func (c *Commit) Result(builder, goHash string) *Result {
for _, r := range c.ResultData {
@@ -160,20 +184,11 @@ func partsToHash(c *Commit, p []string) *Result {
}
}
-// OK returns the Commit's build state for a specific builder and goHash.
-func (c *Commit) OK(builder, goHash string) (ok, present bool) {
- r := c.Result(builder, goHash)
- if r == nil {
- return false, false
- }
- return r.OK, true
-}
-
// A Result describes a build result for a Commit on an OS/architecture.
//
// Each Result entity is a descendant of its associated Commit entity.
type Result struct {
- Builder string // "arch-os[-note]"
+ Builder string // "os-arch[-note]"
Hash string
PackagePath string // (empty for Go commits)
@@ -184,7 +199,7 @@ type Result struct {
Log string `datastore:"-"` // for JSON unmarshaling only
LogHash string `datastore:",noindex"` // Key to the Log record.
- RunTime int64 // time to build+test in nanoseconds
+ RunTime int64 // time to build+test in nanoseconds
}
func (r *Result) Key(c appengine.Context) *datastore.Key {
@@ -297,7 +312,12 @@ func Packages(c appengine.Context, kind string) ([]*Package, error) {
q := datastore.NewQuery("Package").Filter("Kind=", kind)
for t := q.Run(c); ; {
pkg := new(Package)
- if _, err := t.Next(pkg); err == datastore.Done {
+ _, err := t.Next(pkg)
+ if _, ok := err.(*datastore.ErrFieldMismatch); ok {
+ // Some fields have been removed, so it's okay to ignore this error.
+ err = nil
+ }
+ if err == datastore.Done {
break
} else if err != nil {
return nil, err
diff --git a/misc/dashboard/app/build/handler.go b/misc/dashboard/app/build/handler.go
index 5d1e3094c..1a1118641 100644
--- a/misc/dashboard/app/build/handler.go
+++ b/misc/dashboard/app/build/handler.go
@@ -322,7 +322,7 @@ func resultHandler(r *http.Request) (interface{}, error) {
// logHandler displays log text for a given hash.
// It handles paths like "/log/hash".
func logHandler(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-type", "text/plain")
+ w.Header().Set("Content-type", "text/plain; charset=utf-8")
c := appengine.NewContext(r)
hash := r.URL.Path[len("/log/"):]
key := datastore.NewKey(c, "Log", hash, 0, nil)
diff --git a/misc/dashboard/app/build/init.go b/misc/dashboard/app/build/init.go
index 5311688b7..85a766b9d 100644
--- a/misc/dashboard/app/build/init.go
+++ b/misc/dashboard/app/build/init.go
@@ -24,6 +24,8 @@ var subRepos = []string{
"crypto",
"image",
"net",
+ "talks",
+ "exp",
}
// Put subRepos into defaultPackages.
@@ -42,7 +44,12 @@ func initHandler(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
defer cache.Tick(c)
for _, p := range defaultPackages {
- if err := datastore.Get(c, p.Key(c), new(Package)); err == nil {
+ err := datastore.Get(c, p.Key(c), new(Package))
+ if _, ok := err.(*datastore.ErrFieldMismatch); ok {
+ // Some fields have been removed, so it's okay to ignore this error.
+ err = nil
+ }
+ if err == nil {
continue
} else if err != datastore.ErrNoSuchEntity {
logErr(w, r, err)
diff --git a/misc/dashboard/app/build/notify.go b/misc/dashboard/app/build/notify.go
index e02344ca8..52b91f6c1 100644
--- a/misc/dashboard/app/build/notify.go
+++ b/misc/dashboard/app/build/notify.go
@@ -21,6 +21,13 @@ const (
domain = "build.golang.org"
)
+// failIgnore is a set of builders that we don't email about because
+// they're too flaky.
+var failIgnore = map[string]bool{
+ "netbsd-386-bsiegert": true,
+ "netbsd-amd64-bsiegert": true,
+}
+
// notifyOnFailure checks whether the supplied Commit or the subsequent
// Commit (if present) breaks the build for this builder.
// If either of those commits break the build an email notification is sent
@@ -30,6 +37,10 @@ const (
// This must be run in a datastore transaction, and the provided *Commit must
// have been retrieved from the datastore within that transaction.
func notifyOnFailure(c appengine.Context, com *Commit, builder string) error {
+ if failIgnore[builder] {
+ return nil
+ }
+
// TODO(adg): implement notifications for packages
if com.PackagePath != "" {
return nil
@@ -37,15 +48,15 @@ func notifyOnFailure(c appengine.Context, com *Commit, builder string) error {
p := &Package{Path: com.PackagePath}
var broken *Commit
- ok, present := com.OK(builder, "")
- if !present {
+ cr := com.Result(builder, "")
+ if cr == nil {
return fmt.Errorf("no result for %s/%s", com.Hash, builder)
}
q := datastore.NewQuery("Commit").Ancestor(p.Key(c))
- if ok {
+ if cr.OK {
// This commit is OK. Notify if next Commit is broken.
next := new(Commit)
- q.Filter("ParentHash=", com.Hash)
+ q = q.Filter("ParentHash=", com.Hash)
if err := firstMatch(c, q, next); err != nil {
if err == datastore.ErrNoSuchEntity {
// OK at tip, no notification necessary.
@@ -53,13 +64,15 @@ func notifyOnFailure(c appengine.Context, com *Commit, builder string) error {
}
return err
}
- if ok, present := next.OK(builder, ""); present && !ok {
+ if nr := next.Result(builder, ""); nr != nil && !nr.OK {
+ c.Debugf("commit ok: %#v\nresult: %#v", com, cr)
+ c.Debugf("next commit broken: %#v\nnext result:%#v", next, nr)
broken = next
}
} else {
// This commit is broken. Notify if the previous Commit is OK.
prev := new(Commit)
- q.Filter("Hash=", com.ParentHash)
+ q = q.Filter("Hash=", com.ParentHash)
if err := firstMatch(c, q, prev); err != nil {
if err == datastore.ErrNoSuchEntity {
// No previous result, let the backfill of
@@ -68,7 +81,9 @@ func notifyOnFailure(c appengine.Context, com *Commit, builder string) error {
}
return err
}
- if ok, present := prev.OK(builder, ""); present && ok {
+ if pr := prev.Result(builder, ""); pr != nil && pr.OK {
+ c.Debugf("commit broken: %#v\nresult: %#v", com, cr)
+ c.Debugf("previous commit ok: %#v\nprevious result:%#v", prev, pr)
broken = com
}
}
diff --git a/misc/dashboard/app/build/test.go b/misc/dashboard/app/build/test.go
index d8470fec1..7e5539236 100644
--- a/misc/dashboard/app/build/test.go
+++ b/misc/dashboard/app/build/test.go
@@ -43,14 +43,15 @@ var testPackages = []*Package{
var tCommitTime = time.Now().Add(-time.Hour * 24 * 7)
-func tCommit(hash, parentHash string) *Commit {
+func tCommit(hash, parentHash, path string) *Commit {
tCommitTime.Add(time.Hour) // each commit should have a different time
return &Commit{
- Hash: hash,
- ParentHash: parentHash,
- Time: tCommitTime,
- User: "adg",
- Desc: "change description",
+ PackagePath: path,
+ Hash: hash,
+ ParentHash: parentHash,
+ Time: tCommitTime,
+ User: "adg",
+ Desc: "change description " + hash,
}
}
@@ -64,9 +65,9 @@ var testRequests = []struct {
{"/packages?kind=subrepo", nil, nil, []*Package{testPackage}},
// Go repo
- {"/commit", nil, tCommit("0001", "0000"), nil},
- {"/commit", nil, tCommit("0002", "0001"), nil},
- {"/commit", nil, tCommit("0003", "0002"), nil},
+ {"/commit", nil, tCommit("0001", "0000", ""), nil},
+ {"/commit", nil, tCommit("0002", "0001", ""), nil},
+ {"/commit", nil, tCommit("0003", "0002", ""), nil},
{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
{"/result", nil, &Result{Builder: "linux-386", Hash: "0001", OK: true}, nil},
@@ -81,12 +82,12 @@ var testRequests = []struct {
{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0002"}}},
// branches
- {"/commit", nil, tCommit("0004", "0003"), nil},
- {"/commit", nil, tCommit("0005", "0002"), nil},
+ {"/commit", nil, tCommit("0004", "0003", ""), nil},
+ {"/commit", nil, tCommit("0005", "0002", ""), nil},
{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0005"}}},
{"/result", nil, &Result{Builder: "linux-386", Hash: "0005", OK: true}, nil},
{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0004"}}},
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0004", OK: true}, nil},
+ {"/result", nil, &Result{Builder: "linux-386", Hash: "0004", OK: false}, nil},
{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
// logs
@@ -98,9 +99,9 @@ var testRequests = []struct {
{"/result", nil, &Result{Builder: "linux-386", Hash: "0003", OK: false, Log: "test"}, nil},
// non-Go repos
- {"/commit", nil, &Commit{PackagePath: testPkg, Hash: "1001", ParentHash: "1000"}, nil},
- {"/commit", nil, &Commit{PackagePath: testPkg, Hash: "1002", ParentHash: "1001"}, nil},
- {"/commit", nil, &Commit{PackagePath: testPkg, Hash: "1003", ParentHash: "1002"}, nil},
+ {"/commit", nil, tCommit("1001", "1000", testPkg), nil},
+ {"/commit", nil, tCommit("1002", "1001", testPkg), nil},
+ {"/commit", nil, tCommit("1003", "1002", testPkg), nil},
{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1003"}}},
{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1003", GoHash: "0001", OK: true}, nil},
{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1002"}}},
@@ -230,7 +231,7 @@ func testHandler(w http.ResponseWriter, r *http.Request) {
return
}
}
- fmt.Fprint(w, "PASS")
+ fmt.Fprint(w, "PASS\nYou should see only one mail notification (for 0003/linux-386) in the dev_appserver logs.")
}
func nukeEntities(c appengine.Context, kinds []string) error {
diff --git a/misc/dashboard/app/build/ui.go b/misc/dashboard/app/build/ui.go
index 0337aa306..cc3629a5a 100644
--- a/misc/dashboard/app/build/ui.go
+++ b/misc/dashboard/app/build/ui.go
@@ -97,7 +97,7 @@ type Pagination struct {
func goCommits(c appengine.Context, page int) ([]*Commit, error) {
q := datastore.NewQuery("Commit").
Ancestor((&Package{}).Key(c)).
- Order("-Time").
+ Order("-Num").
Limit(commitsPerPage).
Offset(page * commitsPerPage)
var commits []*Commit
@@ -211,6 +211,9 @@ func builderArch(s string) string {
// builderArchShort returns a short arch tag for a builder string
func builderArchShort(s string) string {
+ if s == "linux-amd64-race" {
+ return "race"
+ }
arch := builderArch(s)
switch arch {
case "amd64":
diff --git a/misc/dashboard/builder/Makefile b/misc/dashboard/builder/Makefile
index abf3755ab..4e4d408bf 100644
--- a/misc/dashboard/builder/Makefile
+++ b/misc/dashboard/builder/Makefile
@@ -2,8 +2,8 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
-gobuilder: $(shell ls *.go)
+builder: $(shell ls *.go)
go build -o $@ $^
clean:
- rm -f gobuilder
+ rm -f builder
diff --git a/misc/dashboard/builder/doc.go b/misc/dashboard/builder/doc.go
index 30d8fe948..519286170 100644
--- a/misc/dashboard/builder/doc.go
+++ b/misc/dashboard/builder/doc.go
@@ -4,15 +4,15 @@
/*
-Go Builder is a continuous build client for the Go project.
+Go Builder is a continuous build client for the Go project.
It integrates with the Go Dashboard AppEngine application.
Go Builder is intended to run continuously as a background process.
-It periodically pulls updates from the Go Mercurial repository.
+It periodically pulls updates from the Go Mercurial repository.
When a newer revision is found, Go Builder creates a clone of the repository,
-runs all.bash, and reports build success or failure to the Go Dashboard.
+runs all.bash, and reports build success or failure to the Go Dashboard.
For a release revision (a change description that matches "release.YYYY-MM-DD"),
Go Builder will create a tar.gz archive of the GOROOT and deliver it to the
@@ -22,7 +22,7 @@ Usage:
gobuilder goos-goarch...
- Several goos-goarch combinations can be provided, and the builder will
+ Several goos-goarch combinations can be provided, and the builder will
build them in serial.
Optional flags:
@@ -55,4 +55,4 @@ If the Google Code credentials are not provided the archival step
will be skipped.
*/
-package documentation
+package main
diff --git a/misc/dashboard/builder/exec.go b/misc/dashboard/builder/exec.go
index 802d5f079..a4aabd284 100644
--- a/misc/dashboard/builder/exec.go
+++ b/misc/dashboard/builder/exec.go
@@ -6,14 +6,16 @@ package main
import (
"bytes"
+ "fmt"
"io"
"log"
"os"
"os/exec"
+ "time"
)
// run is a simple wrapper for exec.Run/Close
-func run(envv []string, dir string, argv ...string) error {
+func run(timeout time.Duration, envv []string, dir string, argv ...string) error {
if *verbose {
log.Println("run", argv)
}
@@ -21,43 +23,57 @@ func run(envv []string, dir string, argv ...string) error {
cmd.Dir = dir
cmd.Env = envv
cmd.Stderr = os.Stderr
- return cmd.Run()
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ return waitWithTimeout(timeout, cmd)
}
-// runLog runs a process and returns the combined stdout/stderr,
-// as well as writing it to logfile (if specified). It returns
-// process combined stdout and stderr output, exit status and error.
-// The error returned is nil, if process is started successfully,
-// even if exit status is not successful.
-func runLog(envv []string, logfile, dir string, argv ...string) (string, int, error) {
- if *verbose {
- log.Println("runLog", argv)
- }
+// runLog runs a process and returns the combined stdout/stderr. It returns
+// process combined stdout and stderr output, exit status and error. The
+// error returned is nil, if process is started successfully, even if exit
+// status is not successful.
+func runLog(timeout time.Duration, envv []string, dir string, argv ...string) (string, bool, error) {
+ var b bytes.Buffer
+ ok, err := runOutput(timeout, envv, &b, dir, argv...)
+ return b.String(), ok, err
+}
- b := new(bytes.Buffer)
- var w io.Writer = b
- if logfile != "" {
- f, err := os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
- if err != nil {
- return "", 0, err
- }
- defer f.Close()
- w = io.MultiWriter(f, b)
+// runOutput runs a process and directs any output to the supplied writer.
+// It returns exit status and error. The error returned is nil, if process
+// is started successfully, even if exit status is not successful.
+func runOutput(timeout time.Duration, envv []string, out io.Writer, dir string, argv ...string) (bool, error) {
+ if *verbose {
+ log.Println("runOutput", argv)
}
cmd := exec.Command(argv[0], argv[1:]...)
cmd.Dir = dir
cmd.Env = envv
- cmd.Stdout = w
- cmd.Stderr = w
+ cmd.Stdout = out
+ cmd.Stderr = out
startErr := cmd.Start()
if startErr != nil {
- return "", 1, startErr
+ return false, startErr
+ }
+ if err := waitWithTimeout(timeout, cmd); err != nil {
+ return false, err
}
- exitStatus := 0
- if err := cmd.Wait(); err != nil {
- exitStatus = 1 // TODO(bradfitz): this is fake. no callers care, so just return a bool instead.
+ return true, nil
+}
+
+func waitWithTimeout(timeout time.Duration, cmd *exec.Cmd) error {
+ errc := make(chan error, 1)
+ go func() {
+ errc <- cmd.Wait()
+ }()
+ var err error
+ select {
+ case <-time.After(timeout):
+ cmd.Process.Kill()
+ err = fmt.Errorf("timed out after %v", timeout)
+ case err = <-errc:
}
- return b.String(), exitStatus, nil
+ return err
}
diff --git a/misc/dashboard/builder/http.go b/misc/dashboard/builder/http.go
index e50ae5724..b50e84551 100644
--- a/misc/dashboard/builder/http.go
+++ b/misc/dashboard/builder/http.go
@@ -56,8 +56,10 @@ func dash(meth, cmd string, args url.Values, req, resp interface{}) error {
if err != nil {
return err
}
-
defer r.Body.Close()
+ if r.StatusCode != http.StatusOK {
+ return fmt.Errorf("bad http response: %v", r.Status)
+ }
body := new(bytes.Buffer)
if _, err := body.ReadFrom(r.Body); err != nil {
return err
diff --git a/misc/dashboard/builder/main.go b/misc/dashboard/builder/main.go
index 85bb7ad4b..b2b8f43a6 100644
--- a/misc/dashboard/builder/main.go
+++ b/misc/dashboard/builder/main.go
@@ -6,16 +6,15 @@ package main
import (
"bytes"
- "encoding/xml"
"flag"
"fmt"
+ "io"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
- "strconv"
"strings"
"time"
)
@@ -32,6 +31,7 @@ const (
// These variables are copied from the gobuilder's environment
// to the envv of its subprocesses.
var extraEnv = []string{
+ "CC",
"GOARM",
"GOHOSTARCH",
"GOHOSTOS",
@@ -40,25 +40,27 @@ var extraEnv = []string{
}
type Builder struct {
+ goroot *Repo
name string
goos, goarch string
key string
}
var (
- buildroot = flag.String("buildroot", filepath.Join(os.TempDir(), "gobuilder"), "Directory under which to build")
- commitFlag = flag.Bool("commit", false, "upload information about new commits")
- dashboard = flag.String("dashboard", "build.golang.org", "Go Dashboard Host")
- buildRelease = flag.Bool("release", false, "Build and upload binary release archives")
- buildRevision = flag.String("rev", "", "Build specified revision and exit")
- buildCmd = flag.String("cmd", filepath.Join(".", allCmd), "Build command (specify relative to go/src/)")
- failAll = flag.Bool("fail", false, "fail all builds")
- parallel = flag.Bool("parallel", false, "Build multiple targets in parallel")
- verbose = flag.Bool("v", false, "verbose")
+ buildroot = flag.String("buildroot", defaultBuildRoot(), "Directory under which to build")
+ dashboard = flag.String("dashboard", "build.golang.org", "Go Dashboard Host")
+ buildRelease = flag.Bool("release", false, "Build and upload binary release archives")
+ buildRevision = flag.String("rev", "", "Build specified revision and exit")
+ buildCmd = flag.String("cmd", filepath.Join(".", allCmd), "Build command (specify relative to go/src/)")
+ failAll = flag.Bool("fail", false, "fail all builds")
+ parallel = flag.Bool("parallel", false, "Build multiple targets in parallel")
+ buildTimeout = flag.Duration("buildTimeout", 60*time.Minute, "Maximum time to wait for builds and tests")
+ cmdTimeout = flag.Duration("cmdTimeout", 5*time.Minute, "Maximum time to wait for an external command")
+ commitInterval = flag.Duration("commitInterval", 1*time.Minute, "Time to wait between polling for new commits (0 disables commit poller)")
+ verbose = flag.Bool("v", false, "verbose")
)
var (
- goroot string
binaryTagRe = regexp.MustCompile(`^(release\.r|weekly\.)[0-9\-.]+`)
releaseRe = regexp.MustCompile(`^release\.r[0-9\-.]+`)
allCmd = "all" + suffix
@@ -73,26 +75,15 @@ func main() {
os.Exit(2)
}
flag.Parse()
- if len(flag.Args()) == 0 && !*commitFlag {
+ if len(flag.Args()) == 0 {
flag.Usage()
}
- goroot = filepath.Join(*buildroot, "goroot")
- builders := make([]*Builder, len(flag.Args()))
- for i, builder := range flag.Args() {
- b, err := NewBuilder(builder)
- if err != nil {
- log.Fatal(err)
- }
- builders[i] = b
- }
-
- if *failAll {
- failMode(builders)
- return
+ goroot := &Repo{
+ Path: filepath.Join(*buildroot, "goroot"),
}
// set up work environment, use existing enviroment if possible
- if hgRepoExists(goroot) {
+ if goroot.Exists() || *failAll {
log.Print("Found old workspace, will use it")
} else {
if err := os.RemoveAll(*buildroot); err != nil {
@@ -101,22 +92,31 @@ func main() {
if err := os.Mkdir(*buildroot, mkdirPerm); err != nil {
log.Fatalf("Error making build root (%s): %s", *buildroot, err)
}
- if err := hgClone(hgUrl, goroot); err != nil {
+ var err error
+ goroot, err = RemoteRepo(hgUrl).Clone(goroot.Path, "tip")
+ if err != nil {
log.Fatal("Error cloning repository:", err)
}
}
- if *commitFlag {
- if len(flag.Args()) == 0 {
- commitWatcher()
- return
+ // set up builders
+ builders := make([]*Builder, len(flag.Args()))
+ for i, name := range flag.Args() {
+ b, err := NewBuilder(goroot, name)
+ if err != nil {
+ log.Fatal(err)
}
- go commitWatcher()
+ builders[i] = b
+ }
+
+ if *failAll {
+ failMode(builders)
+ return
}
// if specified, build revision and return
if *buildRevision != "" {
- hash, err := fullHash(goroot, *buildRevision)
+ hash, err := goroot.FullHash(*buildRevision)
if err != nil {
log.Fatal("Error finding revision: ", err)
}
@@ -128,7 +128,10 @@ func main() {
return
}
- // go continuous build mode (default)
+ // Start commit watcher
+ go commitWatcher(goroot)
+
+ // go continuous build mode
// check for new commits and build them
for {
built := false
@@ -175,15 +178,18 @@ func failMode(builders []*Builder) {
}
}
-func NewBuilder(builder string) (*Builder, error) {
- b := &Builder{name: builder}
+func NewBuilder(goroot *Repo, name string) (*Builder, error) {
+ b := &Builder{
+ goroot: goroot,
+ name: name,
+ }
// get goos/goarch from builder string
- s := strings.SplitN(builder, "-", 3)
+ s := strings.SplitN(b.name, "-", 3)
if len(s) >= 2 {
b.goos, b.goarch = s[0], s[1]
} else {
- return nil, fmt.Errorf("unsupported builder form: %s", builder)
+ return nil, fmt.Errorf("unsupported builder form: %s", name)
}
// read keys from keyfile
@@ -206,7 +212,7 @@ func NewBuilder(builder string) (*Builder, error) {
}
// build checks for a new commit for this builder
-// and builds it if one is found.
+// and builds it if one is found.
// It returns true if a build was attempted.
func (b *Builder) build() bool {
hash, err := b.todo("build-go-commit", "", "")
@@ -217,16 +223,8 @@ func (b *Builder) build() bool {
if hash == "" {
return false
}
- // Look for hash locally before running hg pull.
- if _, err := fullHash(goroot, hash[:12]); err != nil {
- // Don't have hash, so run hg pull.
- if err := run(nil, goroot, "hg", "pull"); err != nil {
- log.Println("hg pull failed:", err)
- return false
- }
- }
- err = b.buildHash(hash)
- if err != nil {
+
+ if err := b.buildHash(hash); err != nil {
log.Println(err)
}
return true
@@ -242,34 +240,49 @@ func (b *Builder) buildHash(hash string) error {
}
defer os.RemoveAll(workpath)
- // clone repo
- if err := run(nil, workpath, "hg", "clone", goroot, "go"); err != nil {
+ // pull before cloning to ensure we have the revision
+ if err := b.goroot.Pull(); err != nil {
return err
}
- // update to specified revision
- if err := run(nil, filepath.Join(workpath, "go"), "hg", "update", hash); err != nil {
+ // clone repo at specified revision
+ if _, err := b.goroot.Clone(filepath.Join(workpath, "go"), hash); err != nil {
return err
}
srcDir := filepath.Join(workpath, "go", "src")
// build
+ var buildlog bytes.Buffer
logfile := filepath.Join(workpath, "build.log")
+ f, err := os.Create(logfile)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ w := io.MultiWriter(f, &buildlog)
+
cmd := *buildCmd
if !filepath.IsAbs(cmd) {
cmd = filepath.Join(srcDir, cmd)
}
startTime := time.Now()
- buildLog, status, err := runLog(b.envv(), logfile, srcDir, cmd)
+ ok, err := runOutput(*buildTimeout, b.envv(), w, srcDir, cmd)
runTime := time.Now().Sub(startTime)
- if err != nil {
- return fmt.Errorf("%s: %s", *buildCmd, err)
+ errf := func() string {
+ if err != nil {
+ return fmt.Sprintf("error: %v", err)
+ }
+ if !ok {
+ return "failed"
+ }
+ return "success"
}
+ fmt.Fprintf(w, "Build complete, duration %v. Result: %v\n", runTime, errf())
- if status != 0 {
+ if err != nil || !ok {
// record failure
- return b.recordResult(false, "", hash, "", buildLog, runTime)
+ return b.recordResult(false, "", hash, "", buildlog.String(), runTime)
}
// record success
@@ -278,13 +291,15 @@ func (b *Builder) buildHash(hash string) error {
}
// build Go sub-repositories
- b.buildSubrepos(filepath.Join(workpath, "go"), hash)
+ goRoot := filepath.Join(workpath, "go")
+ goPath := workpath
+ b.buildSubrepos(goRoot, goPath, hash)
return nil
}
// failBuild checks for a new commit for this builder
-// and fails it if one is found.
+// and fails it if one is found.
// It returns true if a build was "attempted".
func (b *Builder) failBuild() bool {
hash, err := b.todo("build-go-commit", "", "")
@@ -304,7 +319,7 @@ func (b *Builder) failBuild() bool {
return true
}
-func (b *Builder) buildSubrepos(goRoot, goHash string) {
+func (b *Builder) buildSubrepos(goRoot, goPath, goHash string) {
for _, pkg := range dashboardPackages("subrepo") {
// get the latest todo for this package
hash, err := b.todo("build-package", pkg, goHash)
@@ -320,7 +335,7 @@ func (b *Builder) buildSubrepos(goRoot, goHash string) {
if *verbose {
log.Printf("buildSubrepos %s: building %q", pkg, hash)
}
- buildLog, err := b.buildSubrepo(goRoot, pkg, hash)
+ buildLog, err := b.buildSubrepo(goRoot, goPath, pkg, hash)
if err != nil {
if buildLog == "" {
buildLog = err.Error()
@@ -338,45 +353,39 @@ func (b *Builder) buildSubrepos(goRoot, goHash string) {
// buildSubrepo fetches the given package, updates it to the specified hash,
// and runs 'go test -short pkg/...'. It returns the build log and any error.
-func (b *Builder) buildSubrepo(goRoot, pkg, hash string) (string, error) {
- goBin := filepath.Join(goRoot, "bin")
- goTool := filepath.Join(goBin, "go")
- env := append(b.envv(), "GOROOT="+goRoot)
+func (b *Builder) buildSubrepo(goRoot, goPath, pkg, hash string) (string, error) {
+ goTool := filepath.Join(goRoot, "bin", "go")
+ env := append(b.envv(), "GOROOT="+goRoot, "GOPATH="+goPath)
- // add goBin to PATH
+ // add $GOROOT/bin and $GOPATH/bin to PATH
for i, e := range env {
const p = "PATH="
if !strings.HasPrefix(e, p) {
continue
}
- env[i] = p + goBin + string(os.PathListSeparator) + e[len(p):]
+ sep := string(os.PathListSeparator)
+ env[i] = p + filepath.Join(goRoot, "bin") + sep + filepath.Join(goPath, "bin") + sep + e[len(p):]
}
// fetch package and dependencies
- log, status, err := runLog(env, "", goRoot, goTool, "get", "-d", pkg)
- if err == nil && status != 0 {
- err = fmt.Errorf("go exited with status %d", status)
+ log, ok, err := runLog(*cmdTimeout, env, goPath, goTool, "get", "-d", pkg+"/...")
+ if err == nil && !ok {
+ err = fmt.Errorf("go exited with status 1")
}
if err != nil {
- // 'go get -d' will fail for a subrepo because its top-level
- // directory does not contain a go package. No matter, just
- // check whether an hg directory exists and proceed.
- hgDir := filepath.Join(goRoot, "src/pkg", pkg, ".hg")
- if fi, e := os.Stat(hgDir); e != nil || !fi.IsDir() {
- return log, err
- }
+ return log, err
}
// hg update to the specified hash
- pkgPath := filepath.Join(goRoot, "src/pkg", pkg)
- if err := run(nil, pkgPath, "hg", "update", hash); err != nil {
+ repo := Repo{Path: filepath.Join(goPath, "src", pkg)}
+ if err := repo.UpdateTo(hash); err != nil {
return "", err
}
// test the package
- log, status, err = runLog(env, "", goRoot, goTool, "test", "-short", pkg+"/...")
- if err == nil && status != 0 {
- err = fmt.Errorf("go exited with status %d", status)
+ log, ok, err = runLog(*buildTimeout, env, goPath, goTool, "test", "-short", pkg+"/...")
+ if err == nil && !ok {
+ err = fmt.Errorf("go exited with status 1")
}
return log, err
}
@@ -449,9 +458,13 @@ func isFile(name string) bool {
}
// commitWatcher polls hg for new commits and tells the dashboard about them.
-func commitWatcher() {
+func commitWatcher(goroot *Repo) {
+ if *commitInterval == 0 {
+ log.Printf("commitInterval is %s, disabling commitWatcher", *commitInterval)
+ return
+ }
// Create builder just to get master key.
- b, err := NewBuilder("mercurial-commit")
+ b, err := NewBuilder(goroot, "mercurial-commit")
if err != nil {
log.Fatal(err)
}
@@ -462,104 +475,46 @@ func commitWatcher() {
log.Printf("poll...")
}
// Main Go repository.
- commitPoll(key, "")
+ commitPoll(goroot, "", key)
// Go sub-repositories.
for _, pkg := range dashboardPackages("subrepo") {
- commitPoll(key, pkg)
+ pkgroot := &Repo{
+ Path: filepath.Join(*buildroot, pkg),
+ }
+ commitPoll(pkgroot, pkg, key)
}
if *verbose {
log.Printf("sleep...")
}
- time.Sleep(60e9)
- }
-}
-
-func hgClone(url, path string) error {
- return run(nil, *buildroot, "hg", "clone", url, path)
-}
-
-func hgRepoExists(path string) bool {
- fi, err := os.Stat(filepath.Join(path, ".hg"))
- if err != nil {
- return false
+ time.Sleep(*commitInterval)
}
- return fi.IsDir()
-}
-
-// HgLog represents a single Mercurial revision.
-type HgLog struct {
- Hash string
- Author string
- Date string
- Desc string
- Parent string
-
- // Internal metadata
- added bool
}
// logByHash is a cache of all Mercurial revisions we know about,
// indexed by full hash.
var logByHash = map[string]*HgLog{}
-// xmlLogTemplate is a template to pass to Mercurial to make
-// hg log print the log in valid XML for parsing with xml.Unmarshal.
-const xmlLogTemplate = `
- <Log>
- <Hash>{node|escape}</Hash>
- <Parent>{parent|escape}</Parent>
- <Author>{author|escape}</Author>
- <Date>{date|rfc3339date}</Date>
- <Desc>{desc|escape}</Desc>
- </Log>
-`
-
// commitPoll pulls any new revisions from the hg server
// and tells the server about them.
-func commitPoll(key, pkg string) {
- pkgRoot := goroot
-
- if pkg != "" {
- pkgRoot = filepath.Join(*buildroot, pkg)
- if !hgRepoExists(pkgRoot) {
- if err := hgClone(repoURL(pkg), pkgRoot); err != nil {
- log.Printf("%s: hg clone failed: %v", pkg, err)
- if err := os.RemoveAll(pkgRoot); err != nil {
- log.Printf("%s: %v", pkg, err)
- }
- return
+func commitPoll(repo *Repo, pkg, key string) {
+ if !repo.Exists() {
+ var err error
+ repo, err = RemoteRepo(repoURL(pkg)).Clone(repo.Path, "tip")
+ if err != nil {
+ log.Printf("%s: hg clone failed: %v", pkg, err)
+ if err := os.RemoveAll(repo.Path); err != nil {
+ log.Printf("%s: %v", pkg, err)
}
}
- }
-
- if err := run(nil, pkgRoot, "hg", "pull"); err != nil {
- log.Printf("hg pull: %v", err)
return
}
- const N = 50 // how many revisions to grab
-
- data, _, err := runLog(nil, "", pkgRoot, "hg", "log",
- "--encoding=utf-8",
- "--limit="+strconv.Itoa(N),
- "--template="+xmlLogTemplate,
- )
+ logs, err := repo.Log() // repo.Log calls repo.Pull internally
if err != nil {
log.Printf("hg log: %v", err)
return
}
- var logStruct struct {
- Log []HgLog
- }
- err = xml.Unmarshal([]byte("<Top>"+data+"</Top>"), &logStruct)
- if err != nil {
- log.Printf("unmarshal hg log: %v", err)
- return
- }
-
- logs := logStruct.Log
-
// Pass 1. Fill in parents and add new log entries to logsByHash.
// Empty parent means take parent from next log entry.
// Non-empty parent has form 1234:hashhashhash; we want full hash.
@@ -568,7 +523,7 @@ func commitPoll(key, pkg string) {
if l.Parent == "" && i+1 < len(logs) {
l.Parent = logs[i+1].Hash
} else if l.Parent != "" {
- l.Parent, _ = fullHash(pkgRoot, l.Parent)
+ l.Parent, _ = repo.FullHash(l.Parent)
}
if *verbose {
log.Printf("hg log %s: %s < %s\n", pkg, l.Hash, l.Parent)
@@ -580,8 +535,7 @@ func commitPoll(key, pkg string) {
}
}
- for i := range logs {
- l := &logs[i]
+ for _, l := range logs {
addCommit(pkg, l.Hash, key)
}
}
@@ -625,28 +579,6 @@ func addCommit(pkg, hash, key string) bool {
return true
}
-// fullHash returns the full hash for the given Mercurial revision.
-func fullHash(root, rev string) (string, error) {
- s, _, err := runLog(nil, "", root,
- "hg", "log",
- "--encoding=utf-8",
- "--rev="+rev,
- "--limit=1",
- "--template={node}",
- )
- if err != nil {
- return "", nil
- }
- s = strings.TrimSpace(s)
- if s == "" {
- return "", fmt.Errorf("cannot find revision")
- }
- if len(s) != 40 {
- return "", fmt.Errorf("hg returned invalid hash " + s)
- }
- return s, nil
-}
-
var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+(\.[a-z0-9\-]+)?)(/[a-z0-9A-Z_.\-/]+)?$`)
// repoURL returns the repository URL for the supplied import path.
@@ -668,6 +600,19 @@ func defaultSuffix() string {
return ".bash"
}
+// defaultBuildRoot returns default buildroot directory.
+func defaultBuildRoot() string {
+ var d string
+ if runtime.GOOS == "windows" {
+ // will use c:\, otherwise absolute paths become too long
+ // during builder run, see http://golang.org/issue/3358.
+ d = `c:\`
+ } else {
+ d = os.TempDir()
+ }
+ return filepath.Join(d, "gobuilder")
+}
+
func getenvOk(k string) (v string, ok bool) {
v = os.Getenv(k)
if v != "" {
diff --git a/misc/dashboard/builder/vcs.go b/misc/dashboard/builder/vcs.go
new file mode 100644
index 000000000..63198a34b
--- /dev/null
+++ b/misc/dashboard/builder/vcs.go
@@ -0,0 +1,148 @@
+// 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.
+
+package main
+
+import (
+ "encoding/xml"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+// Repo represents a mercurial repository.
+type Repo struct {
+ Path string
+ sync.Mutex
+}
+
+// RemoteRepo constructs a *Repo representing a remote repository.
+func RemoteRepo(url string) *Repo {
+ return &Repo{
+ Path: url,
+ }
+}
+
+// Clone clones the current Repo to a new destination
+// returning a new *Repo if successful.
+func (r *Repo) Clone(path, rev string) (*Repo, error) {
+ r.Lock()
+ defer r.Unlock()
+ if err := run(*cmdTimeout, nil, *buildroot, r.hgCmd("clone", "-r", rev, r.Path, path)...); err != nil {
+ return nil, err
+ }
+ return &Repo{
+ Path: path,
+ }, nil
+}
+
+// UpdateTo updates the working copy of this Repo to the
+// supplied revision.
+func (r *Repo) UpdateTo(hash string) error {
+ r.Lock()
+ defer r.Unlock()
+ return run(*cmdTimeout, nil, r.Path, r.hgCmd("update", hash)...)
+}
+
+// Exists reports whether this Repo represents a valid Mecurial repository.
+func (r *Repo) Exists() bool {
+ fi, err := os.Stat(filepath.Join(r.Path, ".hg"))
+ if err != nil {
+ return false
+ }
+ return fi.IsDir()
+}
+
+// Pull pulls changes from the default path, that is, the path
+// this Repo was cloned from.
+func (r *Repo) Pull() error {
+ r.Lock()
+ defer r.Unlock()
+ return run(*cmdTimeout, nil, r.Path, r.hgCmd("pull")...)
+}
+
+// Log returns the changelog for this repository.
+func (r *Repo) Log() ([]HgLog, error) {
+ if err := r.Pull(); err != nil {
+ return nil, err
+ }
+ const N = 50 // how many revisions to grab
+
+ r.Lock()
+ defer r.Unlock()
+ data, _, err := runLog(*cmdTimeout, nil, r.Path, r.hgCmd("log",
+ "--encoding=utf-8",
+ "--limit="+strconv.Itoa(N),
+ "--template="+xmlLogTemplate)...,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ var logStruct struct {
+ Log []HgLog
+ }
+ err = xml.Unmarshal([]byte("<Top>"+data+"</Top>"), &logStruct)
+ if err != nil {
+ log.Printf("unmarshal hg log: %v", err)
+ return nil, err
+ }
+ return logStruct.Log, nil
+}
+
+// FullHash returns the full hash for the given Mercurial revision.
+func (r *Repo) FullHash(rev string) (string, error) {
+ r.Lock()
+ defer r.Unlock()
+ s, _, err := runLog(*cmdTimeout, nil, r.Path,
+ r.hgCmd("log",
+ "--encoding=utf-8",
+ "--rev="+rev,
+ "--limit=1",
+ "--template={node}")...,
+ )
+ if err != nil {
+ return "", nil
+ }
+ s = strings.TrimSpace(s)
+ if s == "" {
+ return "", fmt.Errorf("cannot find revision")
+ }
+ if len(s) != 40 {
+ return "", fmt.Errorf("hg returned invalid hash " + s)
+ }
+ return s, nil
+}
+
+func (r *Repo) hgCmd(args ...string) []string {
+ return append([]string{"hg", "--config", "extensions.codereview=!"}, args...)
+}
+
+// HgLog represents a single Mercurial revision.
+type HgLog struct {
+ Hash string
+ Author string
+ Date string
+ Desc string
+ Parent string
+
+ // Internal metadata
+ added bool
+}
+
+// xmlLogTemplate is a template to pass to Mercurial to make
+// hg log print the log in valid XML for parsing with xml.Unmarshal.
+const xmlLogTemplate = `
+ <Log>
+ <Hash>{node|escape}</Hash>
+ <Parent>{parent|escape}</Parent>
+ <Author>{author|escape}</Author>
+ <Date>{date|rfc3339date}</Date>
+ <Desc>{desc|escape}</Desc>
+ </Log>
+`
diff --git a/misc/dashboard/codereview/app.yaml b/misc/dashboard/codereview/app.yaml
new file mode 100644
index 000000000..372eca5a1
--- /dev/null
+++ b/misc/dashboard/codereview/app.yaml
@@ -0,0 +1,24 @@
+application: gocodereview
+version: 1
+runtime: go
+api_version: go1
+
+inbound_services:
+- mail
+
+handlers:
+- url: /static/(.*)
+ static_files: static/\1
+ upload: static/.*
+- url: /_ah/mail/.*
+ script: _go_app
+ login: admin
+- url: /_ah/queue/go/delay
+ script: _go_app
+ login: admin
+- url: /(gc|update-cl)
+ script: _go_app
+ login: admin
+- url: /.*
+ script: _go_app
+ login: required
diff --git a/misc/dashboard/codereview/cron.yaml b/misc/dashboard/codereview/cron.yaml
new file mode 100644
index 000000000..3d33d32b5
--- /dev/null
+++ b/misc/dashboard/codereview/cron.yaml
@@ -0,0 +1,4 @@
+cron:
+- description: GC
+ url: /gc
+ schedule: every 6 hours
diff --git a/misc/dashboard/codereview/dashboard/cl.go b/misc/dashboard/codereview/dashboard/cl.go
new file mode 100644
index 000000000..e150ea123
--- /dev/null
+++ b/misc/dashboard/codereview/dashboard/cl.go
@@ -0,0 +1,481 @@
+// Copyright 2012 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.
+
+package dashboard
+
+// This file handles operations on the CL entity kind.
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "regexp"
+ "sort"
+ "strings"
+ "time"
+
+ "appengine"
+ "appengine/datastore"
+ "appengine/taskqueue"
+ "appengine/urlfetch"
+ "appengine/user"
+)
+
+func init() {
+ http.HandleFunc("/assign", handleAssign)
+ http.HandleFunc("/update-cl", handleUpdateCL)
+}
+
+const codereviewBase = "http://codereview.appspot.com"
+const gobotBase = "http://research.swtch.com/gobot_codereview"
+
+var clRegexp = regexp.MustCompile(`\d+`)
+
+// CL represents a code review.
+type CL struct {
+ Number string // e.g. "5903061"
+ Closed bool
+ Owner string // email address
+
+ Created, Modified time.Time
+
+ Description []byte `datastore:",noindex"`
+ FirstLine string `datastore:",noindex"`
+ LGTMs []string
+ NotLGTMs []string
+ LastUpdateBy string // author of most recent review message
+ LastUpdate string `datastore:",noindex"` // first line of most recent review message
+
+ // Mail information.
+ Subject string `datastore:",noindex"`
+ Recipients []string `datastore:",noindex"`
+ LastMessageID string `datastore:",noindex"`
+
+ // These are person IDs (e.g. "rsc"); they may be empty
+ Author string
+ Reviewer string
+}
+
+// Reviewed reports whether the reviewer has replied to the CL.
+// The heuristic is that the CL has been replied to if it is LGTMed
+// or if the last CL message was from the reviewer.
+func (cl *CL) Reviewed() bool {
+ if cl.LastUpdateBy == cl.Reviewer {
+ return true
+ }
+ if person := emailToPerson[cl.LastUpdateBy]; person != "" && person == cl.Reviewer {
+ return true
+ }
+ for _, who := range cl.LGTMs {
+ if who == cl.Reviewer {
+ return true
+ }
+ }
+ return false
+}
+
+// DisplayOwner returns the CL's owner, either as their email address
+// or the person ID if it's a reviewer. It is for display only.
+func (cl *CL) DisplayOwner() string {
+ if p, ok := emailToPerson[cl.Owner]; ok {
+ return p
+ }
+ return cl.Owner
+}
+
+func (cl *CL) FirstLineHTML() template.HTML {
+ s := template.HTMLEscapeString(cl.FirstLine)
+ // Embolden the package name.
+ if i := strings.Index(s, ":"); i >= 0 {
+ s = "<b>" + s[:i] + "</b>" + s[i:]
+ }
+ return template.HTML(s)
+}
+
+func formatEmails(e []string) template.HTML {
+ x := make([]string, len(e))
+ for i, s := range e {
+ s = template.HTMLEscapeString(s)
+ if !strings.Contains(s, "@") {
+ s = "<b>" + s + "</b>"
+ }
+ s = `<span class="email">` + s + "</span>"
+ x[i] = s
+ }
+ return template.HTML(strings.Join(x, ", "))
+}
+
+func (cl *CL) LGTMHTML() template.HTML {
+ return formatEmails(cl.LGTMs)
+}
+
+func (cl *CL) NotLGTMHTML() template.HTML {
+ return formatEmails(cl.NotLGTMs)
+}
+
+func (cl *CL) ModifiedAgo() string {
+ // Just the first non-zero unit.
+ units := [...]struct {
+ suffix string
+ unit time.Duration
+ }{
+ {"d", 24 * time.Hour},
+ {"h", time.Hour},
+ {"m", time.Minute},
+ {"s", time.Second},
+ }
+ d := time.Now().Sub(cl.Modified)
+ for _, u := range units {
+ if d > u.unit {
+ return fmt.Sprintf("%d%s", d/u.unit, u.suffix)
+ }
+ }
+ return "just now"
+}
+
+func handleAssign(w http.ResponseWriter, r *http.Request) {
+ c := appengine.NewContext(r)
+
+ if r.Method != "POST" {
+ http.Error(w, "Bad method "+r.Method, 400)
+ return
+ }
+
+ u := user.Current(c)
+ person, ok := emailToPerson[u.Email]
+ if !ok {
+ http.Error(w, "Not allowed", http.StatusUnauthorized)
+ return
+ }
+
+ n, rev := r.FormValue("cl"), r.FormValue("r")
+ if !clRegexp.MatchString(n) {
+ c.Errorf("Bad CL %q", n)
+ http.Error(w, "Bad CL", 400)
+ return
+ }
+ if _, ok := preferredEmail[rev]; !ok && rev != "" {
+ c.Errorf("Unknown reviewer %q", rev)
+ http.Error(w, "Unknown reviewer", 400)
+ return
+ }
+
+ key := datastore.NewKey(c, "CL", n, 0, nil)
+
+ if rev != "" {
+ // Make sure the reviewer is listed in Rietveld as a reviewer.
+ url := codereviewBase + "/" + n + "/fields"
+ resp, err := urlfetch.Client(c).Get(url + "?field=reviewers")
+ if err != nil {
+ c.Errorf("Retrieving CL reviewer list failed: %v", err)
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ c.Errorf("Retrieving CL reviewer list failed: got HTTP response %d", resp.StatusCode)
+ http.Error(w, "Failed contacting Rietveld", 500)
+ return
+ }
+
+ var apiResp struct {
+ Reviewers []string `json:"reviewers"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
+ // probably can't be retried
+ msg := fmt.Sprintf("Malformed JSON from %v: %v", url, err)
+ c.Errorf("%s", msg)
+ http.Error(w, msg, 500)
+ return
+ }
+ found := false
+ for _, r := range apiResp.Reviewers {
+ if emailToPerson[r] == rev {
+ found = true
+ break
+ }
+ }
+ if !found {
+ c.Infof("Adding %v as a reviewer of CL %v", rev, n)
+
+ url := fmt.Sprintf("%s?cl=%s&r=%s&obo=%s", gobotBase, n, rev, person)
+ resp, err := urlfetch.Client(c).Get(url)
+ if err != nil {
+ c.Errorf("Gobot GET failed: %v", err)
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ c.Errorf("Gobot GET failed: got HTTP response %d", resp.StatusCode)
+ http.Error(w, "Failed contacting Gobot", 500)
+ return
+ }
+
+ c.Infof("Gobot said %q", resp.Status)
+ }
+ }
+
+ // Update our own record.
+ err := datastore.RunInTransaction(c, func(c appengine.Context) error {
+ cl := new(CL)
+ err := datastore.Get(c, key, cl)
+ if err != nil {
+ return err
+ }
+ cl.Reviewer = rev
+ _, err = datastore.Put(c, key, cl)
+ return err
+ }, nil)
+ if err != nil {
+ msg := fmt.Sprintf("Assignment failed: %v", err)
+ c.Errorf("%s", msg)
+ http.Error(w, msg, 500)
+ return
+ }
+ c.Infof("Assigned CL %v to %v", n, rev)
+}
+
+func UpdateCLLater(c appengine.Context, n string, delay time.Duration) {
+ t := taskqueue.NewPOSTTask("/update-cl", url.Values{
+ "cl": []string{n},
+ })
+ t.Delay = delay
+ if _, err := taskqueue.Add(c, t, "update-cl"); err != nil {
+ c.Errorf("Failed adding task: %v", err)
+ }
+}
+
+func handleUpdateCL(w http.ResponseWriter, r *http.Request) {
+ c := appengine.NewContext(r)
+
+ n := r.FormValue("cl")
+ if !clRegexp.MatchString(n) {
+ c.Errorf("Bad CL %q", n)
+ http.Error(w, "Bad CL", 400)
+ return
+ }
+
+ if err := updateCL(c, n); err != nil {
+ c.Errorf("Failed updating CL %v: %v", n, err)
+ http.Error(w, "Failed update", 500)
+ return
+ }
+
+ io.WriteString(w, "OK")
+}
+
+// apiMessage describes the JSON sent back by Rietveld in the CL messages list.
+type apiMessage struct {
+ Date string `json:"date"`
+ Text string `json:"text"`
+ Sender string `json:"sender"`
+ Recipients []string `json:"recipients"`
+ Approval bool `json:"approval"`
+}
+
+// byDate implements sort.Interface to order the messages by date, earliest first.
+// The dates are sent in RFC 3339 format, so string comparison matches time value comparison.
+type byDate []*apiMessage
+
+func (x byDate) Len() int { return len(x) }
+func (x byDate) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x byDate) Less(i, j int) bool { return x[i].Date < x[j].Date }
+
+// updateCL updates a single CL. If a retryable failure occurs, an error is returned.
+func updateCL(c appengine.Context, n string) error {
+ c.Debugf("Updating CL %v", n)
+ key := datastore.NewKey(c, "CL", n, 0, nil)
+
+ url := codereviewBase + "/api/" + n + "?messages=true"
+ resp, err := urlfetch.Client(c).Get(url)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ raw, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return fmt.Errorf("Failed reading HTTP body: %v", err)
+ }
+
+ // Special case for abandoned CLs.
+ if resp.StatusCode == 404 && bytes.Contains(raw, []byte("No issue exists with that id")) {
+ // Don't bother checking for errors. The CL might never have been saved, for instance.
+ datastore.Delete(c, key)
+ c.Infof("Deleted abandoned CL %v", n)
+ return nil
+ }
+
+ if resp.StatusCode != 200 {
+ return fmt.Errorf("Update: got HTTP response %d", resp.StatusCode)
+ }
+
+ var apiResp struct {
+ Description string `json:"description"`
+ Reviewers []string `json:"reviewers"`
+ Created string `json:"created"`
+ OwnerEmail string `json:"owner_email"`
+ Modified string `json:"modified"`
+ Closed bool `json:"closed"`
+ Subject string `json:"subject"`
+ Messages []*apiMessage `json:"messages"`
+ }
+ if err := json.Unmarshal(raw, &apiResp); err != nil {
+ // probably can't be retried
+ c.Errorf("Malformed JSON from %v: %v", url, err)
+ return nil
+ }
+ //c.Infof("RAW: %+v", apiResp)
+ sort.Sort(byDate(apiResp.Messages))
+
+ cl := &CL{
+ Number: n,
+ Closed: apiResp.Closed,
+ Owner: apiResp.OwnerEmail,
+ Description: []byte(apiResp.Description),
+ FirstLine: apiResp.Description,
+ Subject: apiResp.Subject,
+ Author: emailToPerson[apiResp.OwnerEmail],
+ }
+ cl.Created, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Created)
+ if err != nil {
+ c.Errorf("Bad creation time %q: %v", apiResp.Created, err)
+ }
+ cl.Modified, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Modified)
+ if err != nil {
+ c.Errorf("Bad modification time %q: %v", apiResp.Modified, err)
+ }
+ if i := strings.Index(cl.FirstLine, "\n"); i >= 0 {
+ cl.FirstLine = cl.FirstLine[:i]
+ }
+ // Treat zero reviewers as a signal that the CL is completed.
+ // This could be after the CL has been submitted, but before the CL author has synced,
+ // but it could also be a CL manually edited to remove reviewers.
+ if len(apiResp.Reviewers) == 0 {
+ cl.Closed = true
+ }
+
+ lgtm := make(map[string]bool)
+ notLGTM := make(map[string]bool)
+ rcpt := make(map[string]bool)
+ for _, msg := range apiResp.Messages {
+ s, rev := msg.Sender, false
+ if p, ok := emailToPerson[s]; ok {
+ s, rev = p, true
+ }
+
+ line := firstLine(msg.Text)
+ if line != "" {
+ cl.LastUpdateBy = msg.Sender
+ cl.LastUpdate = line
+ }
+
+ // CLs submitted by someone other than the CL owner do not immediately
+ // transition to "closed". Let's simulate the intention by treating
+ // messages starting with "*** Submitted as " from a reviewer as a
+ // signal that the CL is now closed.
+ if rev && strings.HasPrefix(msg.Text, "*** Submitted as ") {
+ cl.Closed = true
+ }
+
+ if msg.Approval {
+ lgtm[s] = true
+ delete(notLGTM, s) // "LGTM" overrules previous "NOT LGTM"
+ }
+ if strings.Contains(line, "NOT LGTM") {
+ notLGTM[s] = true
+ delete(lgtm, s) // "NOT LGTM" overrules previous "LGTM"
+ }
+
+ for _, r := range msg.Recipients {
+ rcpt[r] = true
+ }
+ }
+ for l := range lgtm {
+ cl.LGTMs = append(cl.LGTMs, l)
+ }
+ for l := range notLGTM {
+ cl.NotLGTMs = append(cl.NotLGTMs, l)
+ }
+ for r := range rcpt {
+ cl.Recipients = append(cl.Recipients, r)
+ }
+ sort.Strings(cl.LGTMs)
+ sort.Strings(cl.NotLGTMs)
+ sort.Strings(cl.Recipients)
+
+ err = datastore.RunInTransaction(c, func(c appengine.Context) error {
+ ocl := new(CL)
+ err := datastore.Get(c, key, ocl)
+ if err != nil && err != datastore.ErrNoSuchEntity {
+ return err
+ } else if err == nil {
+ // LastMessageID and Reviewer need preserving.
+ cl.LastMessageID = ocl.LastMessageID
+ cl.Reviewer = ocl.Reviewer
+ }
+ _, err = datastore.Put(c, key, cl)
+ return err
+ }, nil)
+ if err != nil {
+ return err
+ }
+ c.Infof("Updated CL %v", n)
+ return nil
+}
+
+// trailingSpaceRE matches trailing spaces.
+var trailingSpaceRE = regexp.MustCompile(`(?m)[ \t\r]+$`)
+
+// removeRE is the list of patterns to skip over at the beginning of a
+// message when looking for message text.
+var removeRE = regexp.MustCompile(`(?m-s)\A(` +
+ // Skip leading "Hello so-and-so," generated by codereview plugin.
+ `(Hello(.|\n)*?\n\n)` +
+
+ // Skip quoted text.
+ `|((On.*|.* writes|.* wrote):\n)` +
+ `|((>.*\n)+)` +
+
+ // Skip lines with no letters.
+ `|(([^A-Za-z]*\n)+)` +
+
+ // Skip links to comments and file info.
+ `|(http://codereview.*\n([^ ]+:[0-9]+:.*\n)?)` +
+ `|(File .*:\n)` +
+
+ `)`,
+)
+
+// firstLine returns the first interesting line of the message text.
+func firstLine(text string) string {
+ // Cut trailing spaces.
+ text = trailingSpaceRE.ReplaceAllString(text, "")
+
+ // Skip uninteresting lines.
+ for {
+ text = strings.TrimSpace(text)
+ m := removeRE.FindStringIndex(text)
+ if m == nil || m[0] != 0 {
+ break
+ }
+ text = text[m[1]:]
+ }
+
+ // Chop line at newline or else at 74 bytes.
+ i := strings.Index(text, "\n")
+ if i >= 0 {
+ text = text[:i]
+ }
+ if len(text) > 74 {
+ text = text[:70] + "..."
+ }
+ return text
+}
diff --git a/misc/dashboard/codereview/dashboard/front.go b/misc/dashboard/codereview/dashboard/front.go
new file mode 100644
index 000000000..c7b0f0fbf
--- /dev/null
+++ b/misc/dashboard/codereview/dashboard/front.go
@@ -0,0 +1,299 @@
+// Copyright 2012 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.
+
+package dashboard
+
+// This file handles the front page.
+
+import (
+ "bytes"
+ "html/template"
+ "io"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "appengine"
+ "appengine/datastore"
+ "appengine/user"
+)
+
+func init() {
+ http.HandleFunc("/", handleFront)
+ http.HandleFunc("/favicon.ico", http.NotFound)
+}
+
+// maximum number of active CLs to show in person-specific tables.
+const maxCLs = 100
+
+func handleFront(w http.ResponseWriter, r *http.Request) {
+ c := appengine.NewContext(r)
+
+ data := &frontPageData{
+ Reviewers: personList,
+ User: user.Current(c).Email,
+ IsAdmin: user.IsAdmin(c),
+ }
+ var currentPerson string
+ u := data.User
+ you := "you"
+ if e := r.FormValue("user"); e != "" {
+ u = e
+ you = e
+ }
+ currentPerson, data.UserIsReviewer = emailToPerson[u]
+ if !data.UserIsReviewer {
+ currentPerson = u
+ }
+
+ var wg sync.WaitGroup
+ errc := make(chan error, 10)
+ activeCLs := datastore.NewQuery("CL").
+ Filter("Closed =", false).
+ Order("-Modified")
+
+ tableFetch := func(index int, f func(tbl *clTable) error) {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ start := time.Now()
+ if err := f(&data.Tables[index]); err != nil {
+ errc <- err
+ }
+ data.Timing[index] = time.Now().Sub(start)
+ }()
+ }
+
+ data.Tables[0].Title = "CLs assigned to " + you + " for review"
+ if data.UserIsReviewer {
+ tableFetch(0, func(tbl *clTable) error {
+ q := activeCLs.Filter("Reviewer =", currentPerson).Limit(maxCLs)
+ tbl.Assignable = true
+ _, err := q.GetAll(c, &tbl.CLs)
+ return err
+ })
+ }
+
+ tableFetch(1, func(tbl *clTable) error {
+ q := activeCLs
+ if data.UserIsReviewer {
+ q = q.Filter("Author =", currentPerson)
+ } else {
+ q = q.Filter("Owner =", currentPerson)
+ }
+ q = q.Limit(maxCLs)
+ tbl.Title = "CLs sent by " + you
+ tbl.Assignable = true
+ _, err := q.GetAll(c, &tbl.CLs)
+ return err
+ })
+
+ tableFetch(2, func(tbl *clTable) error {
+ q := activeCLs.Limit(50)
+ tbl.Title = "Other active CLs"
+ tbl.Assignable = true
+ if _, err := q.GetAll(c, &tbl.CLs); err != nil {
+ return err
+ }
+ // filter
+ for i := len(tbl.CLs) - 1; i >= 0; i-- {
+ cl := tbl.CLs[i]
+ if cl.Owner == currentPerson || cl.Author == currentPerson || cl.Reviewer == currentPerson {
+ // Preserve order.
+ copy(tbl.CLs[i:], tbl.CLs[i+1:])
+ tbl.CLs = tbl.CLs[:len(tbl.CLs)-1]
+ }
+ }
+ return nil
+ })
+
+ tableFetch(3, func(tbl *clTable) error {
+ q := datastore.NewQuery("CL").
+ Filter("Closed =", true).
+ Order("-Modified").
+ Limit(10)
+ tbl.Title = "Recently closed CLs"
+ tbl.Assignable = false
+ _, err := q.GetAll(c, &tbl.CLs)
+ return err
+ })
+
+ // Not really a table fetch.
+ tableFetch(0, func(_ *clTable) error {
+ var err error
+ data.LogoutURL, err = user.LogoutURL(c, "/")
+ return err
+ })
+
+ wg.Wait()
+
+ select {
+ case err := <-errc:
+ c.Errorf("%v", err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ default:
+ }
+
+ var b bytes.Buffer
+ if err := frontPage.ExecuteTemplate(&b, "front", &data); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ io.Copy(w, &b)
+}
+
+type frontPageData struct {
+ Tables [4]clTable
+ Timing [4]time.Duration
+
+ Reviewers []string
+ UserIsReviewer bool
+
+ User, LogoutURL string // actual logged in user
+ IsAdmin bool
+}
+
+type clTable struct {
+ Title string
+ Assignable bool
+ CLs []*CL
+}
+
+var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{
+ "selected": func(a, b string) string {
+ if a == b {
+ return "selected"
+ }
+ return ""
+ },
+ "shortemail": func(s string) string {
+ if i := strings.Index(s, "@"); i >= 0 {
+ s = s[:i]
+ }
+ return s
+ },
+}).Parse(`
+<!doctype html>
+<html>
+ <head>
+ <title>Go code reviews</title>
+ <link rel="icon" type="image/png" href="/static/icon.png" />
+ <style type="text/css">
+ body {
+ font-family: Helvetica, sans-serif;
+ }
+ img#gopherstamp {
+ float: right;
+ height: auto;
+ width: 250px;
+ }
+ h1, h2, h3 {
+ color: #777;
+ margin-bottom: 0;
+ }
+ table {
+ border-spacing: 0;
+ }
+ td {
+ vertical-align: top;
+ padding: 2px 5px;
+ }
+ tr.unreplied td.email {
+ border-left: 2px solid blue;
+ }
+ tr.pending td {
+ background: #fc8;
+ }
+ tr.failed td {
+ background: #f88;
+ }
+ tr.saved td {
+ background: #8f8;
+ }
+ .cls {
+ margin-top: 0;
+ }
+ a {
+ color: blue;
+ text-decoration: none; /* no link underline */
+ }
+ address {
+ font-size: 10px;
+ text-align: right;
+ }
+ .email {
+ font-family: monospace;
+ }
+ </style>
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
+ <head>
+ <body>
+
+<img id="gopherstamp" src="/static/gopherstamp.jpg" />
+<h1>Go code reviews</h1>
+
+<table class="cls">
+{{range $i, $tbl := .Tables}}
+<tr><td colspan="5"><h3>{{$tbl.Title}}</h3></td></tr>
+{{if .CLs}}
+{{range $cl := .CLs}}
+ <tr id="cl-{{$cl.Number}}" class="{{if not $i}}{{if not .Reviewed}}unreplied{{end}}{{end}}">
+ <td class="email">{{$cl.DisplayOwner}}</td>
+ <td>
+ {{if $tbl.Assignable}}
+ <select id="cl-rev-{{$cl.Number}}" {{if not $.UserIsReviewer}}disabled{{end}}>
+ <option></option>
+ {{range $.Reviewers}}
+ <option {{selected . $cl.Reviewer}}>{{.}}</option>
+ {{end}}
+ </select>
+ <script type="text/javascript">
+ $(function() {
+ $('#cl-rev-{{$cl.Number}}').change(function() {
+ var r = $(this).val();
+ var row = $('tr#cl-{{$cl.Number}}');
+ row.addClass('pending');
+ $.post('/assign', {
+ 'cl': '{{$cl.Number}}',
+ 'r': r
+ }).success(function() {
+ row.removeClass('pending');
+ row.addClass('saved');
+ }).error(function() {
+ row.removeClass('pending');
+ row.addClass('failed');
+ });
+ });
+ });
+ </script>
+ {{end}}
+ </td>
+ <td>
+ <a href="http://codereview.appspot.com/{{.Number}}/" title="{{ printf "%s" .Description}}">{{.Number}}: {{.FirstLineHTML}}</a>
+ {{if and .LGTMs $tbl.Assignable}}<br /><span style="font-size: smaller;">LGTMs: {{.LGTMHTML}}</span>{{end}}
+ {{if and .NotLGTMs $tbl.Assignable}}<br /><span style="font-size: smaller; color: #f74545;">NOT LGTMs: {{.NotLGTMHTML}}</span>{{end}}
+ {{if .LastUpdateBy}}<br /><span style="font-size: smaller; color: #777777;">(<span title="{{.LastUpdateBy}}">{{.LastUpdateBy | shortemail}}</span>) {{.LastUpdate}}</span>{{end}}
+ </td>
+ <td title="Last modified">{{.ModifiedAgo}}</td>
+ <td>{{if $.IsAdmin}}<a href="/update-cl?cl={{.Number}}" title="Update this CL">&#x27f3;</a>{{end}}</td>
+ </tr>
+{{end}}
+{{else}}
+<tr><td colspan="5"><em>none</em></td></tr>
+{{end}}
+{{end}}
+</table>
+
+<hr />
+<address>
+You are <span class="email">{{.User}}</span> &middot; <a href="{{.LogoutURL}}">logout</a><br />
+datastore timing: {{range .Timing}} {{.}}{{end}}
+</address>
+
+ </body>
+</html>
+`))
diff --git a/misc/dashboard/codereview/dashboard/gc.go b/misc/dashboard/codereview/dashboard/gc.go
new file mode 100644
index 000000000..a80b375f6
--- /dev/null
+++ b/misc/dashboard/codereview/dashboard/gc.go
@@ -0,0 +1,47 @@
+// Copyright 2012 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.
+
+package dashboard
+
+// This file handles garbage collection of old CLs.
+
+import (
+ "net/http"
+ "time"
+
+ "appengine"
+ "appengine/datastore"
+)
+
+func init() {
+ http.HandleFunc("/gc", handleGC)
+}
+
+func handleGC(w http.ResponseWriter, r *http.Request) {
+ c := appengine.NewContext(r)
+
+ // Delete closed CLs that haven't been modified in 168 hours (7 days).
+ cutoff := time.Now().Add(-168 * time.Hour)
+ q := datastore.NewQuery("CL").
+ Filter("Closed =", true).
+ Filter("Modified <", cutoff).
+ Limit(100).
+ KeysOnly()
+ keys, err := q.GetAll(c, nil)
+ if err != nil {
+ c.Errorf("GetAll failed for old CLs: %v", err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if len(keys) == 0 {
+ return
+ }
+
+ if err := datastore.DeleteMulti(c, keys); err != nil {
+ c.Errorf("DeleteMulti failed for old CLs: %v", err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ c.Infof("Deleted %d old CLs", len(keys))
+}
diff --git a/misc/dashboard/codereview/dashboard/mail.go b/misc/dashboard/codereview/dashboard/mail.go
new file mode 100644
index 000000000..838d08222
--- /dev/null
+++ b/misc/dashboard/codereview/dashboard/mail.go
@@ -0,0 +1,68 @@
+// Copyright 2012 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.
+
+package dashboard
+
+// This file handles receiving mail.
+
+import (
+ "net/http"
+ "net/mail"
+ "regexp"
+ "time"
+
+ "appengine"
+ "appengine/datastore"
+)
+
+func init() {
+ http.HandleFunc("/_ah/mail/", handleMail)
+}
+
+var subjectRegexp = regexp.MustCompile(`.*code review (\d+):.*`)
+
+func handleMail(w http.ResponseWriter, r *http.Request) {
+ c := appengine.NewContext(r)
+
+ defer r.Body.Close()
+ msg, err := mail.ReadMessage(r.Body)
+ if err != nil {
+ c.Errorf("mail.ReadMessage: %v", err)
+ return
+ }
+
+ subj := msg.Header.Get("Subject")
+ m := subjectRegexp.FindStringSubmatch(subj)
+ if len(m) != 2 {
+ c.Debugf("Subject %q did not match /%v/", subj, subjectRegexp)
+ return
+ }
+
+ c.Infof("Found issue %q", m[1])
+
+ // Track the MessageID.
+ key := datastore.NewKey(c, "CL", m[1], 0, nil)
+ err = datastore.RunInTransaction(c, func(c appengine.Context) error {
+ cl := new(CL)
+ err := datastore.Get(c, key, cl)
+ if err != nil && err != datastore.ErrNoSuchEntity {
+ return err
+ }
+ if err == datastore.ErrNoSuchEntity {
+ // Must set sentinel values for time.Time fields
+ // if this is a new entity.
+ cl.Created = time.Unix(0, 0)
+ cl.Modified = time.Unix(0, 0)
+ }
+ cl.LastMessageID = msg.Header.Get("Message-ID")
+ _, err = datastore.Put(c, key, cl)
+ return err
+ }, nil)
+ if err != nil {
+ c.Errorf("datastore transaction failed: %v", err)
+ }
+
+ // Update the CL after a delay to give Rietveld a chance to catch up.
+ UpdateCLLater(c, m[1], 10*time.Second)
+}
diff --git a/misc/dashboard/codereview/dashboard/people.go b/misc/dashboard/codereview/dashboard/people.go
new file mode 100644
index 000000000..facda7baf
--- /dev/null
+++ b/misc/dashboard/codereview/dashboard/people.go
@@ -0,0 +1,42 @@
+// Copyright 2012 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.
+
+package dashboard
+
+// This file handles identities of people.
+
+import (
+ "sort"
+)
+
+var (
+ emailToPerson = make(map[string]string) // email => person
+ preferredEmail = make(map[string]string) // person => email
+ personList []string
+)
+
+func init() {
+ // People we assume have golang.org and google.com accounts,
+ // and prefer to use their golang.org address for code review.
+ gophers := [...]string{
+ "adg",
+ "bradfitz",
+ "campoy",
+ "dsymonds",
+ "gri",
+ "iant",
+ "nigeltao",
+ "r",
+ "rsc",
+ "sameer",
+ }
+ for _, p := range gophers {
+ personList = append(personList, p)
+ emailToPerson[p+"@golang.org"] = p
+ emailToPerson[p+"@google.com"] = p
+ preferredEmail[p] = p + "@golang.org"
+ }
+
+ sort.Strings(personList)
+}
diff --git a/misc/dashboard/codereview/index.yaml b/misc/dashboard/codereview/index.yaml
new file mode 100644
index 000000000..a87073cc4
--- /dev/null
+++ b/misc/dashboard/codereview/index.yaml
@@ -0,0 +1,25 @@
+indexes:
+
+- kind: CL
+ properties:
+ - name: Author
+ - name: Modified
+ direction: desc
+
+- kind: CL
+ properties:
+ - name: Owner
+ - name: Modified
+ direction: desc
+
+- kind: CL
+ properties:
+ - name: Closed
+ - name: Modified
+ direction: desc
+
+- kind: CL
+ properties:
+ - name: Reviewer
+ - name: Modified
+ direction: desc
diff --git a/misc/dashboard/codereview/queue.yaml b/misc/dashboard/codereview/queue.yaml
new file mode 100644
index 000000000..1a35facaf
--- /dev/null
+++ b/misc/dashboard/codereview/queue.yaml
@@ -0,0 +1,4 @@
+queue:
+- name: update-cl
+ rate: 12/m
+ bucket_size: 1
diff --git a/misc/dashboard/codereview/static/gopherstamp.jpg b/misc/dashboard/codereview/static/gopherstamp.jpg
new file mode 100644
index 000000000..b17f3c82a
--- /dev/null
+++ b/misc/dashboard/codereview/static/gopherstamp.jpg
Binary files differ
diff --git a/misc/dashboard/codereview/static/icon.png b/misc/dashboard/codereview/static/icon.png
new file mode 100644
index 000000000..c929ac8e9
--- /dev/null
+++ b/misc/dashboard/codereview/static/icon.png
Binary files differ
diff --git a/misc/dist/bindist.go b/misc/dist/bindist.go
index 16193bdc1..29454c73c 100644
--- a/misc/dist/bindist.go
+++ b/misc/dist/bindist.go
@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// This is a tool for packaging binary releases.
-// It supports FreeBSD, Linux, OS X, and Windows.
+// It supports FreeBSD, Linux, NetBSD, OS X, and Windows.
package main
import (
@@ -13,7 +13,6 @@ import (
"bytes"
"compress/gzip"
"encoding/base64"
- "errors"
"flag"
"fmt"
"io"
@@ -30,8 +29,9 @@ import (
)
var (
- tag = flag.String("tag", "weekly", "mercurial tag to check out")
+ tag = flag.String("tag", "release", "mercurial tag to check out")
repo = flag.String("repo", "https://code.google.com/p/go", "repo URL")
+ tourPath = flag.String("tour", "code.google.com/p/go-tour", "Go tour repo import path")
verbose = flag.Bool("v", false, "verbose output")
upload = flag.Bool("upload", true, "upload resulting files to Google Code")
wxsFile = flag.String("wxs", "", "path to custom installer.wxs")
@@ -41,8 +41,7 @@ var (
)
const (
- packageMaker = "/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker"
- uploadURL = "https://go.googlecode.com/files"
+ uploadURL = "https://go.googlecode.com/files"
)
var preBuildCleanFiles = []string{
@@ -66,6 +65,20 @@ var sourceCleanFiles = []string{
"pkg",
}
+var tourPackages = []string{
+ "pic",
+ "tree",
+ "wc",
+}
+
+var tourContent = []string{
+ "prog",
+ "solutions",
+ "static",
+ "template",
+ "tour.article",
+}
+
var fileRe = regexp.MustCompile(`^go\.([a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+))\.`)
func main() {
@@ -129,6 +142,7 @@ type Build struct {
OS string
Arch string
root string
+ gopath string
}
func (b *Build) Do() error {
@@ -138,6 +152,7 @@ func (b *Build) Do() error {
}
defer os.RemoveAll(work)
b.root = filepath.Join(work, "go")
+ b.gopath = work
// Clone Go distribution and update to tag.
_, err = b.run(work, "hg", "clone", "-q", *repo, b.root)
@@ -173,6 +188,10 @@ func (b *Build) Do() error {
return err
}
+ if err := b.tour(); err != nil {
+ return err
+ }
+
// Get version strings.
var (
version string // "weekly.2012-03-04"
@@ -211,19 +230,31 @@ func (b *Build) Do() error {
}
// Create packages.
- base := fmt.Sprintf("go.%s.%s-%s", version, b.OS, b.Arch)
+ base := fmt.Sprintf("%s.%s-%s", version, b.OS, b.Arch)
+ if !strings.HasPrefix(base, "go") {
+ base = "go." + base
+ }
var targs []string
switch b.OS {
- case "linux", "freebsd", "":
+ case "linux", "freebsd", "netbsd", "":
// build tarball
targ := base
if b.Source {
- targ = fmt.Sprintf("go.%s.src", version)
+ targ = fmt.Sprintf("%s.src", version)
+ if !strings.HasPrefix(targ, "go") {
+ targ = "go." + targ
+ }
}
targ += ".tar.gz"
err = makeTar(targ, work)
targs = append(targs, targ)
case "darwin":
+ // build tarball
+ targ := base + ".tar.gz"
+ err = makeTar(targ, work)
+ targs = append(targs, targ)
+
+ // build pkg
// arrange work so it's laid out as the dest filesystem
etc := filepath.Join(b.root, "misc/dist/darwin/etc")
_, err = b.run(work, "cp", "-r", etc, ".")
@@ -231,7 +262,7 @@ func (b *Build) Do() error {
return err
}
localDir := filepath.Join(work, "usr/local")
- err = os.MkdirAll(localDir, 0744)
+ err = os.MkdirAll(localDir, 0755)
if err != nil {
return err
}
@@ -240,23 +271,30 @@ func (b *Build) Do() error {
return err
}
// build package
- pm := packageMaker
- if !exists(pm) {
- pm = "/Developer" + pm
- if !exists(pm) {
- return errors.New("couldn't find PackageMaker")
- }
+ pkgdest, err := ioutil.TempDir("", "pkgdest")
+ if err != nil {
+ return err
}
- targ := base + ".pkg"
- scripts := filepath.Join(work, "usr/local/go/misc/dist/darwin/scripts")
- _, err = b.run("", pm, "-v",
- "-r", work,
- "-o", targ,
- "--scripts", scripts,
- "--id", "com.googlecode.go",
- "--title", "Go",
+ defer os.RemoveAll(pkgdest)
+ dist := filepath.Join(runtime.GOROOT(), "misc/dist")
+ _, err = b.run("", "pkgbuild",
+ "--identifier", "com.googlecode.go",
"--version", "1.0",
- "--target", "10.6")
+ "--scripts", filepath.Join(dist, "darwin/scripts"),
+ "--root", work,
+ filepath.Join(pkgdest, "com.googlecode.go.pkg"))
+ if err != nil {
+ return err
+ }
+ targ = base + ".pkg"
+ _, err = b.run("", "productbuild",
+ "--distribution", filepath.Join(dist, "darwin/Distribution"),
+ "--resources", filepath.Join(dist, "darwin/Resources"),
+ "--package-path", pkgdest,
+ targ)
+ if err != nil {
+ return err
+ }
targs = append(targs, targ)
case "windows":
// Create ZIP file.
@@ -327,6 +365,33 @@ func (b *Build) Do() error {
return err
}
+func (b *Build) tour() error {
+ // go get the gotour package.
+ _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), "get", *tourPath+"/gotour")
+ if err != nil {
+ return err
+ }
+
+ // Copy all the tour content to $GOROOT/misc/tour.
+ importPath := filepath.FromSlash(*tourPath)
+ tourSrc := filepath.Join(b.gopath, "src", importPath)
+ contentDir := filepath.Join(b.root, "misc", "tour")
+ if err = cpAllDir(contentDir, tourSrc, tourContent...); err != nil {
+ return err
+ }
+
+ // Copy the tour source code so it's accessible with $GOPATH pointing to $GOROOT/misc/tour.
+ if err = cpAllDir(filepath.Join(contentDir, "src", importPath), tourSrc, tourPackages...); err != nil {
+ return err
+ }
+
+ // Copy gotour binary to tool directory as "tour"; invoked as "go tool tour".
+ return cp(
+ filepath.Join(b.root, "pkg", "tool", b.OS+"_"+b.Arch, "tour"),
+ filepath.Join(b.gopath, "bin", "gotour"),
+ )
+}
+
func (b *Build) run(dir, name string, args ...string) ([]byte, error) {
buf := new(bytes.Buffer)
absName, err := lookPath(name)
@@ -358,6 +423,7 @@ var cleanEnv = []string{
"GOOS",
"GOROOT",
"GOROOT_FINAL",
+ "GOPATH",
}
func (b *Build) env() []string {
@@ -380,6 +446,7 @@ func (b *Build) env() []string {
"GOOS="+b.OS,
"GOROOT="+b.root,
"GOROOT_FINAL="+final,
+ "GOPATH="+b.gopath,
)
return env
}
@@ -390,42 +457,51 @@ func (b *Build) Upload(version string, filename string) error {
os_, arch := b.OS, b.Arch
switch b.Arch {
case "386":
- arch = "32-bit"
+ arch = "x86 32-bit"
case "amd64":
- arch = "64-bit"
+ arch = "x86 64-bit"
}
if arch != "" {
labels = append(labels, "Arch-"+b.Arch)
}
+ var opsys, ftype string // labels
switch b.OS {
case "linux":
os_ = "Linux"
- labels = append(labels, "Type-Archive", "OpSys-Linux")
+ opsys = "Linux"
case "freebsd":
os_ = "FreeBSD"
- labels = append(labels, "Type-Archive", "OpSys-FreeBSD")
+ opsys = "FreeBSD"
case "darwin":
os_ = "Mac OS X"
- labels = append(labels, "Type-Installer", "OpSys-OSX")
+ opsys = "OSX"
+ case "netbsd":
+ os_ = "NetBSD"
+ opsys = "NetBSD"
case "windows":
os_ = "Windows"
- labels = append(labels, "OpSys-Windows")
+ opsys = "Windows"
}
summary := fmt.Sprintf("%s %s (%s)", version, os_, arch)
- if b.OS == "windows" {
- switch {
- case strings.HasSuffix(filename, ".msi"):
- labels = append(labels, "Type-Installer")
- summary += " MSI installer"
- case strings.HasSuffix(filename, ".zip"):
- labels = append(labels, "Type-Archive")
- summary += " ZIP archive"
- }
+ switch {
+ case strings.HasSuffix(filename, ".msi"):
+ ftype = "Installer"
+ summary += " MSI installer"
+ case strings.HasSuffix(filename, ".pkg"):
+ ftype = "Installer"
+ summary += " PKG installer"
+ case strings.HasSuffix(filename, ".zip"):
+ ftype = "Archive"
+ summary += " ZIP archive"
+ case strings.HasSuffix(filename, ".tar.gz"):
+ ftype = "Archive"
+ summary += " tarball"
}
if b.Source {
- labels = append(labels, "Type-Source")
+ ftype = "Source"
summary = fmt.Sprintf("%s (source only)", version)
}
+ labels = append(labels, "OpSys-"+opsys, "Type-"+ftype)
if *addLabel != "" {
labels = append(labels, *addLabel)
}
@@ -535,15 +611,45 @@ func cp(dst, src string) error {
return err
}
defer sf.Close()
+ fi, err := sf.Stat()
+ if err != nil {
+ return err
+ }
df, err := os.Create(dst)
if err != nil {
return err
}
defer df.Close()
+ if err := df.Chmod(fi.Mode()); err != nil {
+ return err
+ }
_, err = io.Copy(df, sf)
return err
}
+func cpDir(dst, src string) error {
+ walk := func(srcPath string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ dstPath := filepath.Join(dst, srcPath[len(src):])
+ if info.IsDir() {
+ return os.MkdirAll(dstPath, 0755)
+ }
+ return cp(dstPath, srcPath)
+ }
+ return filepath.Walk(src, walk)
+}
+
+func cpAllDir(dst, basePath string, dirs ...string) error {
+ for _, dir := range dirs {
+ if err := cpDir(filepath.Join(dst, dir), filepath.Join(basePath, dir)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func makeTar(targ, workdir string) error {
f, err := os.Create(targ)
if err != nil {
@@ -552,7 +658,7 @@ func makeTar(targ, workdir string) error {
zout := gzip.NewWriter(f)
tw := tar.NewWriter(zout)
- filepath.Walk(workdir, filepath.WalkFunc(func(path string, fi os.FileInfo, err error) error {
+ err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
if !strings.HasPrefix(path, workdir) {
log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
}
@@ -570,10 +676,8 @@ func makeTar(targ, workdir string) error {
if *verbose {
log.Printf("adding to tar: %s", name)
}
- if fi.IsDir() {
- return nil
- }
- hdr, err := tarFileInfoHeader(fi, path)
+ target, _ := os.Readlink(path)
+ hdr, err := tar.FileInfoHeader(fi, target)
if err != nil {
return err
}
@@ -594,6 +698,9 @@ func makeTar(targ, workdir string) error {
if err != nil {
return fmt.Errorf("Error writing file %q: %v", name, err)
}
+ if fi.IsDir() {
+ return nil
+ }
r, err := os.Open(path)
if err != nil {
return err
@@ -601,8 +708,10 @@ func makeTar(targ, workdir string) error {
defer r.Close()
_, err = io.Copy(tw, r)
return err
- }))
-
+ })
+ if err != nil {
+ return err
+ }
if err := tw.Close(); err != nil {
return err
}
@@ -619,10 +728,7 @@ func makeZip(targ, workdir string) error {
}
zw := zip.NewWriter(f)
- filepath.Walk(workdir, filepath.WalkFunc(func(path string, fi os.FileInfo, err error) error {
- if fi.IsDir() {
- return nil
- }
+ err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
if !strings.HasPrefix(path, workdir) {
log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
}
@@ -649,10 +755,17 @@ func makeZip(targ, workdir string) error {
}
fh.Name = name
fh.Method = zip.Deflate
+ if fi.IsDir() {
+ fh.Name += "/" // append trailing slash
+ fh.Method = zip.Store // no need to deflate 0 byte files
+ }
w, err := zw.CreateHeader(fh)
if err != nil {
return err
}
+ if fi.IsDir() {
+ return nil
+ }
r, err := os.Open(path)
if err != nil {
return err
@@ -660,8 +773,10 @@ func makeZip(targ, workdir string) error {
defer r.Close()
_, err = io.Copy(w, r)
return err
- }))
-
+ })
+ if err != nil {
+ return err
+ }
if err := zw.Close(); err != nil {
return err
}
@@ -733,64 +848,3 @@ func lookPath(prog string) (absPath string, err error) {
}
return
}
-
-// sysStat, if non-nil, populates h from system-dependent fields of fi.
-var sysStat func(fi os.FileInfo, h *tar.Header) error
-
-// Mode constants from the tar spec.
-const (
- c_ISDIR = 040000
- c_ISFIFO = 010000
- c_ISREG = 0100000
- c_ISLNK = 0120000
- c_ISBLK = 060000
- c_ISCHR = 020000
- c_ISSOCK = 0140000
-)
-
-// tarFileInfoHeader creates a partially-populated Header from an os.FileInfo.
-// The filename parameter is used only in the case of symlinks, to call os.Readlink.
-// If fi is a symlink but filename is empty, an error is returned.
-func tarFileInfoHeader(fi os.FileInfo, filename string) (*tar.Header, error) {
- h := &tar.Header{
- Name: fi.Name(),
- ModTime: fi.ModTime(),
- Mode: int64(fi.Mode().Perm()), // or'd with c_IS* constants later
- }
- switch {
- case fi.Mode()&os.ModeType == 0:
- h.Mode |= c_ISREG
- h.Typeflag = tar.TypeReg
- h.Size = fi.Size()
- case fi.IsDir():
- h.Typeflag = tar.TypeDir
- h.Mode |= c_ISDIR
- case fi.Mode()&os.ModeSymlink != 0:
- h.Typeflag = tar.TypeSymlink
- h.Mode |= c_ISLNK
- if filename == "" {
- return h, fmt.Errorf("archive/tar: unable to populate Header.Linkname of symlinks")
- }
- targ, err := os.Readlink(filename)
- if err != nil {
- return h, err
- }
- h.Linkname = targ
- case fi.Mode()&os.ModeDevice != 0:
- if fi.Mode()&os.ModeCharDevice != 0 {
- h.Mode |= c_ISCHR
- h.Typeflag = tar.TypeChar
- } else {
- h.Mode |= c_ISBLK
- h.Typeflag = tar.TypeBlock
- }
- case fi.Mode()&os.ModeSocket != 0:
- h.Mode |= c_ISSOCK
- default:
- return nil, fmt.Errorf("archive/tar: unknown file mode %v", fi.Mode())
- }
- if sysStat != nil {
- return h, sysStat(fi, h)
- }
- return h, nil
-}
diff --git a/misc/dist/darwin/Distribution b/misc/dist/darwin/Distribution
new file mode 100644
index 000000000..8b764b69f
--- /dev/null
+++ b/misc/dist/darwin/Distribution
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<installer-script minSpecVersion="1.000000">
+ <title>Go</title>
+ <background mime-type="image/png" file="bg.png"/>
+ <options customize="never" allow-external-scripts="no"/>
+ <domains enable_localSystem="true" />
+ <installation-check script="installCheck();"/>
+ <script>
+function installCheck() {
+ if(!(system.compareVersions(system.version.ProductVersion, '10.6.0') >= 0)) {
+ my.result.title = 'Unable to install';
+ my.result.message = 'Go requires Mac OS X 10.6 or later.';
+ my.result.type = 'Fatal';
+ return false;
+ }
+ if(system.files.fileExistsAtPath('/usr/local/go/bin/go')) {
+ my.result.title = 'Previous Installation Detected';
+ my.result.message = 'A previous installation of Go exists at /usr/local/go. This installer will remove the previous installation prior to installing. Please back up any data before proceeding.';
+ my.result.type = 'Warning';
+ return false;
+ }
+ return true;
+}
+ </script>
+ <choices-outline>
+ <line choice="com.googlecode.go.choice"/>
+ </choices-outline>
+ <choice id="com.googlecode.go.choice" title="Go">
+ <pkg-ref id="com.googlecode.go.pkg"/>
+ </choice>
+ <pkg-ref id="com.googlecode.go.pkg" auth="Root">com.googlecode.go.pkg</pkg-ref>
+</installer-script>
diff --git a/misc/dist/darwin/Resources/bg.png b/misc/dist/darwin/Resources/bg.png
new file mode 100644
index 000000000..c3d8ea93a
--- /dev/null
+++ b/misc/dist/darwin/Resources/bg.png
Binary files differ
diff --git a/misc/dist/darwin/scripts/postinstall b/misc/dist/darwin/scripts/postinstall
index 3748721c7..4410a3004 100644..100755
--- a/misc/dist/darwin/scripts/postinstall
+++ b/misc/dist/darwin/scripts/postinstall
@@ -9,14 +9,9 @@ find bin -exec chmod ugo+rx \{\} \;
find . -type d -exec chmod ugo+rx \{\} \;
chmod o-w .
-echo "Fixing debuggers via sudo.bash"
-# setgrp procmod the debuggers (sudo.bash)
-cd $GOROOT/src
-./sudo.bash
-
echo "Installing miscellaneous files:"
XCODE_MISC_DIR="/Library/Application Support/Developer/Shared/Xcode/Specifications/"
-if [ -f $XCODE_MISC_DIR ]; then
+if [ -d "$XCODE_MISC_DIR" ]; then
echo " XCode"
cp $GOROOT/misc/xcode/* $XCODE_MISC_DIR
fi
diff --git a/misc/dist/darwin/scripts/preinstall b/misc/dist/darwin/scripts/preinstall
new file mode 100644
index 000000000..4cdaaa4bc
--- /dev/null
+++ b/misc/dist/darwin/scripts/preinstall
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+GOROOT=/usr/local/go
+
+echo "Removing previous installation"
+if [ -d $GOROOT ]; then
+ rm -r $GOROOT
+fi
diff --git a/misc/dist/stat_darwin.go b/misc/dist/stat_darwin.go
deleted file mode 100644
index eb3f76a27..000000000
--- a/misc/dist/stat_darwin.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2012 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.
-
-// +build darwin
-
-package main
-
-import (
- "archive/tar"
- "os"
- "syscall"
- "time"
-)
-
-func init() {
- sysStat = func(fi os.FileInfo, h *tar.Header) error {
- sys, ok := fi.Sys().(*syscall.Stat_t)
- if !ok {
- return nil
- }
- h.Uid = int(sys.Uid)
- h.Gid = int(sys.Gid)
- // TODO(bradfitz): populate username & group. os/user
- // doesn't cache LookupId lookups, and lacks group
- // lookup functions.
- h.AccessTime = time.Unix(sys.Atimespec.Unix())
- h.ChangeTime = time.Unix(sys.Ctimespec.Unix())
- // TODO(bradfitz): major/minor device numbers?
- return nil
- }
-}
diff --git a/misc/dist/stat_linux.go b/misc/dist/stat_linux.go
deleted file mode 100644
index 0ddb8a3bf..000000000
--- a/misc/dist/stat_linux.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2012 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.
-
-// +build linux
-
-package main
-
-import (
- "archive/tar"
- "os"
- "syscall"
- "time"
-)
-
-func init() {
- sysStat = func(fi os.FileInfo, h *tar.Header) error {
- sys, ok := fi.Sys().(*syscall.Stat_t)
- if !ok {
- return nil
- }
- h.Uid = int(sys.Uid)
- h.Gid = int(sys.Gid)
- // TODO(bradfitz): populate username & group. os/user
- // doesn't cache LookupId lookups, and lacks group
- // lookup functions.
- h.AccessTime = time.Unix(sys.Atim.Unix())
- h.ChangeTime = time.Unix(sys.Ctim.Unix())
- // TODO(bradfitz): major/minor device numbers?
- return nil
- }
-}
diff --git a/misc/emacs/go-mode-load.el b/misc/emacs/go-mode-load.el
index d453166a4..3fc35c116 100644
--- a/misc/emacs/go-mode-load.el
+++ b/misc/emacs/go-mode-load.el
@@ -1,50 +1,96 @@
-;;; go-mode-load.el --- Major mode for the Go programming language
-
+;;; go-mode-load.el --- automatically extracted autoloads
;;; Commentary:
;; To install go-mode, add the following lines to your .emacs file:
;; (add-to-list 'load-path "PATH CONTAINING go-mode-load.el" t)
;; (require 'go-mode-load)
+;;
;; After this, go-mode will be used for files ending in '.go'.
-
+;;
;; To compile go-mode from the command line, run the following
;; emacs -batch -f batch-byte-compile go-mode.el
-
+;;
;; See go-mode.el for documentation.
-
-;;; Code:
-
+;;
;; To update this file, evaluate the following form
;; (let ((generated-autoload-file buffer-file-name)) (update-file-autoloads "go-mode.el"))
+;;; Code:
+
-;;;### (autoloads (gofmt-before-save gofmt go-mode) "go-mode" "go-mode.el"
-;;;;;; (19917 17808))
+;;;### (autoloads (go-download-play godoc gofmt-before-save go-mode)
+;;;;;; "go-mode" "go-mode.el" (20767 50749))
;;; Generated autoloads from go-mode.el
(autoload 'go-mode "go-mode" "\
Major mode for editing Go source text.
-This provides basic syntax highlighting for keywords, built-ins,
-functions, and some types. It also provides indentation that is
-\(almost) identical to gofmt.
+This mode provides (not just) basic editing capabilities for
+working with Go code. It offers almost complete syntax
+highlighting, indentation that is almost identical to gofmt,
+proper parsing of the buffer content to allow features such as
+navigation by function, manipulation of comments or detection of
+strings.
+
+Additionally to these core features, it offers various features to
+help with writing Go code. You can directly run buffer content
+through gofmt, read godoc documentation from within Emacs, modify
+and clean up the list of package imports or interact with the
+Playground (uploading and downloading pastes).
+
+The following extra functions are defined:
+
+- `gofmt'
+- `godoc'
+- `go-import-add'
+- `go-remove-unused-imports'
+- `go-goto-imports'
+- `go-play-buffer' and `go-play-region'
+- `go-download-play'
+
+If you want to automatically run `gofmt' before saving a file,
+add the following hook to your emacs configuration:
+
+\(add-hook 'before-save-hook 'gofmt-before-save)
+
+If you're looking for even more integration with Go, namely
+on-the-fly syntax checking, auto-completion and snippets, it is
+recommended to look at goflymake
+\(https://github.com/dougm/goflymake), gocode
+\(https://github.com/nsf/gocode) and yasnippet-go
+\(https://github.com/dominikh/yasnippet-go)
\(fn)" t nil)
-(add-to-list 'auto-mode-alist (cons "\\.go$" #'go-mode))
-
-(autoload 'gofmt "go-mode" "\
-Pipe the current buffer through the external tool `gofmt`.
-Replace the current buffer on success; display errors on failure.
-
-\(fn)" t nil)
+(add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
(autoload 'gofmt-before-save "go-mode" "\
Add this to .emacs to run gofmt on the current buffer when saving:
- (add-hook 'before-save-hook #'gofmt-before-save)
+ (add-hook 'before-save-hook 'gofmt-before-save).
+
+Note that this will cause go-mode to get loaded the first time
+you save any file, kind of defeating the point of autoloading.
\(fn)" t nil)
-;;;***
+(autoload 'godoc "go-mode" "\
+Show go documentation for a query, much like M-x man.
+
+\(fn QUERY)" t nil)
+(autoload 'go-download-play "go-mode" "\
+Downloads a paste from the playground and inserts it in a Go
+buffer. Tries to look for a URL at point.
+
+\(fn URL)" t nil)
+
+;;;***
+
(provide 'go-mode-load)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; go-mode-load.el ends here
diff --git a/misc/emacs/go-mode.el b/misc/emacs/go-mode.el
index 214c19685..8a16d8a4f 100644
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -1,40 +1,54 @@
;;; go-mode.el --- Major mode for the Go programming language
-;;; Commentary:
+;; 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.
-;; For installation instructions, see go-mode-load.el
+(require 'cl)
+(require 'diff-mode)
+(require 'ffap)
+(require 'find-lisp)
+(require 'url)
-;;; To do:
+(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
+(defconst gofmt-stdin-tag "<standard input>")
+(defconst go-identifier-regexp "[[:word:][:multibyte:]_]+")
+(defconst go-label-regexp go-identifier-regexp)
+(defconst go-type-regexp "[[:word:][:multibyte:]_*]+")
+(defconst go-func-regexp (concat "\\<func\\>\\s *\\(" go-identifier-regexp "\\)"))
+(defconst go-func-meth-regexp (concat "\\<func\\>\\s *\\(?:(\\s *" go-identifier-regexp "\\s +" go-type-regexp "\\s *)\\s *\\)?\\(" go-identifier-regexp "\\)("))
+(defconst go-builtins
+ '("append" "cap" "close" "complex" "copy"
+ "delete" "imag" "len" "make" "new"
+ "panic" "print" "println" "real" "recover")
+ "All built-in functions in the Go language. Used for font locking.")
+
+(defconst go-mode-keywords
+ '("break" "default" "func" "interface" "select"
+ "case" "defer" "go" "map" "struct"
+ "chan" "else" "goto" "package" "switch"
+ "const" "fallthrough" "if" "range" "type"
+ "continue" "for" "import" "return" "var")
+ "All keywords in the Go language. Used for font locking.")
-;; * Indentation is *almost* identical to gofmt
-;; ** We think struct literal keys are labels and outdent them
-;; ** We disagree on the indentation of function literals in arguments
-;; ** There are bugs with the close brace of struct literals
-;; * Highlight identifiers according to their syntactic context: type,
-;; variable, function call, or tag
-;; * Command for adding an import
-;; ** Check if it's already there
-;; ** Factor/unfactor the import line
-;; ** Alphabetize
-;; * Remove unused imports
-;; ** This is hard, since I have to be aware of shadowing to do it
-;; right
-;; * Format region using gofmt
+(defconst go-constants '("nil" "true" "false" "iota"))
+(defconst go-type-name-regexp (concat "\\(?:[*(]\\)*\\(?:" go-identifier-regexp "\\.\\)?\\(" go-identifier-regexp "\\)"))
-;;; Code:
+(defvar go-dangling-cache)
-(eval-when-compile (require 'cl))
+(defgroup go nil
+ "Major mode for editing Go code"
+ :group 'languages)
+
+(defcustom go-fontify-function-calls t
+ "Fontify function and method calls if this is non-nil."
+ :type 'boolean
+ :group 'go)
(defvar go-mode-syntax-table
(let ((st (make-syntax-table)))
- ;; Add _ to :word: character class
- (modify-syntax-entry ?_ "w" st)
-
- ;; Operators (punctuation)
(modify-syntax-entry ?+ "." st)
(modify-syntax-entry ?- "." st)
- (modify-syntax-entry ?* ". 23" st) ; also part of comments
- (modify-syntax-entry ?/ (if (featurep 'xemacs) ". 1456" ". 124b") st) ; ditto
(modify-syntax-entry ?% "." st)
(modify-syntax-entry ?& "." st)
(modify-syntax-entry ?| "." st)
@@ -43,80 +57,56 @@
(modify-syntax-entry ?= "." st)
(modify-syntax-entry ?< "." st)
(modify-syntax-entry ?> "." st)
-
- ;; Strings and comments are font-locked separately.
- (modify-syntax-entry ?\" "." st)
- (modify-syntax-entry ?\' "." st)
- (modify-syntax-entry ?` "." st)
- (modify-syntax-entry ?\\ "." st)
-
- ;; Newline is a comment-ender.
+ (modify-syntax-entry ?/ ". 124b" st)
+ (modify-syntax-entry ?* ". 23" st)
(modify-syntax-entry ?\n "> b" st)
+ (modify-syntax-entry ?\" "\"" st)
+ (modify-syntax-entry ?\' "\"" st)
+ (modify-syntax-entry ?` "\"" st)
+ (modify-syntax-entry ?\\ "\\" st)
+ (modify-syntax-entry ?_ "_" st)
st)
"Syntax table for Go mode.")
-(defvar go-mode-keywords
- '("break" "default" "func" "interface" "select"
- "case" "defer" "go" "map" "struct"
- "chan" "else" "goto" "package" "switch"
- "const" "fallthrough" "if" "range" "type"
- "continue" "for" "import" "return" "var")
- "All keywords in the Go language. Used for font locking and
-some syntax analysis.")
-
-(defvar go-mode-font-lock-keywords
- (let ((builtins '("append" "cap" "close" "complex" "copy" "delete" "imag" "len"
- "make" "new" "panic" "print" "println" "real" "recover"))
- (constants '("nil" "true" "false" "iota"))
- (type-name "\\s *\\(?:[*(]\\s *\\)*\\(?:\\w+\\s *\\.\\s *\\)?\\(\\w+\\)")
- )
- `((go-mode-font-lock-cs-comment 0 font-lock-comment-face t)
- (go-mode-font-lock-cs-string 0 font-lock-string-face t)
- (,(regexp-opt go-mode-keywords 'words) . font-lock-keyword-face)
- (,(regexp-opt builtins 'words) . font-lock-builtin-face)
- (,(regexp-opt constants 'words) . font-lock-constant-face)
- ;; Function names in declarations
- ("\\<func\\>\\s *\\(\\w+\\)" 1 font-lock-function-name-face)
- ;; Function names in methods are handled by function call pattern
- ;; Function names in calls
- ;; XXX Doesn't match if function name is surrounded by parens
- ("\\(\\w+\\)\\s *(" 1 font-lock-function-name-face)
- ;; Type names
- ("\\<type\\>\\s *\\(\\w+\\)" 1 font-lock-type-face)
- (,(concat "\\<type\\>\\s *\\w+\\s *" type-name) 1 font-lock-type-face)
- ;; Arrays/slices/map value type
- ;; XXX Wrong. Marks 0 in expression "foo[0] * x"
- ;; (,(concat "]" type-name) 1 font-lock-type-face)
- ;; Map key type
- (,(concat "\\<map\\s *\\[" type-name) 1 font-lock-type-face)
- ;; Channel value type
- (,(concat "\\<chan\\>\\s *\\(?:<-\\)?" type-name) 1 font-lock-type-face)
- ;; new/make type
- (,(concat "\\<\\(?:new\\|make\\)\\>\\(?:\\s \\|)\\)*(" type-name) 1 font-lock-type-face)
- ;; Type conversion
- (,(concat "\\.\\s *(" type-name) 1 font-lock-type-face)
- ;; Method receiver type
- (,(concat "\\<func\\>\\s *(\\w+\\s +" type-name) 1 font-lock-type-face)
- ;; Labels
- ;; XXX Not quite right. Also marks compound literal fields.
- ("^\\s *\\(\\w+\\)\\s *:\\(\\S.\\|$\\)" 1 font-lock-constant-face)
- ("\\<\\(goto\\|break\\|continue\\)\\>\\s *\\(\\w+\\)" 2 font-lock-constant-face)))
- "Basic font lock keywords for Go mode. Highlights keywords,
-built-ins, functions, and some types.")
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Key map
-;;
+(defun go--build-font-lock-keywords ()
+ (append
+ `((,(regexp-opt go-mode-keywords 'symbols) . font-lock-keyword-face)
+ (,(regexp-opt go-builtins 'symbols) . font-lock-builtin-face)
+ (,(regexp-opt go-constants 'symbols) . font-lock-constant-face)
+ (,go-func-regexp 1 font-lock-function-name-face)) ;; function (not method) name
+
+ (if go-fontify-function-calls
+ `((,(concat "\\(" go-identifier-regexp "\\)[[:space:]]*(") 1 font-lock-function-name-face) ;; function call/method name
+ (,(concat "(\\(" go-identifier-regexp "\\))[[:space:]]*(") 1 font-lock-function-name-face)) ;; bracketed function call
+ `((,go-func-meth-regexp 1 font-lock-function-name-face))) ;; method name
+
+ `(
+ ("\\<type\\>[[:space:]]*\\([^[:space:]]+\\)" 1 font-lock-type-face) ;; types
+ (,(concat "\\<type\\>[[:space:]]*" go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) ;; types
+ (,(concat "\\(?:[[:space:]]+\\|\\]\\)\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices
+ (,(concat "map\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) ;; map value type
+ (,(concat "\\(" go-identifier-regexp "\\)" "{") 1 font-lock-type-face)
+ (,(concat "\\<map\\[" go-type-name-regexp) 1 font-lock-type-face) ;; map key type
+ (,(concat "\\<chan\\>[[:space:]]*\\(?:<-\\)?" go-type-name-regexp) 1 font-lock-type-face) ;; channel type
+ (,(concat "\\<\\(?:new\\|make\\)\\>\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) ;; new/make type
+ ;; TODO do we actually need this one or isn't it just a function call?
+ (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type conversion
+ (,(concat "\\<func\\>[[:space:]]+(" go-identifier-regexp "[[:space:]]+" go-type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver
+ ;; Like the original go-mode this also marks compound literal
+ ;; fields. There, it was marked as to fix, but I grew quite
+ ;; accustomed to it, so it'll stay for now.
+ (,(concat "^[[:space:]]*\\(" go-label-regexp "\\)[[:space:]]*:\\(\\S.\\|$\\)") 1 font-lock-constant-face) ;; Labels and compound literal fields
+ (,(concat "\\<\\(goto\\|break\\|continue\\)\\>[[:space:]]*\\(" go-label-regexp "\\)") 2 font-lock-constant-face)))) ;; labels in goto/break/continue
(defvar go-mode-map
(let ((m (make-sparse-keymap)))
- (define-key m "}" #'go-mode-insert-and-indent)
- (define-key m ")" #'go-mode-insert-and-indent)
- (define-key m "," #'go-mode-insert-and-indent)
- (define-key m ":" #'go-mode-delayed-electric)
- ;; In case we get : indentation wrong, correct ourselves
- (define-key m "=" #'go-mode-insert-and-indent)
+ (define-key m "}" 'go-mode-insert-and-indent)
+ (define-key m ")" 'go-mode-insert-and-indent)
+ (define-key m "," 'go-mode-insert-and-indent)
+ (define-key m ":" 'go-mode-insert-and-indent)
+ (define-key m "=" 'go-mode-insert-and-indent)
+ (define-key m (kbd "C-c C-a") 'go-import-add)
m)
"Keymap used by Go mode to implement electric keys.")
@@ -127,606 +117,229 @@ built-ins, functions, and some types.")
(call-interactively (lookup-key (current-global-map) key))
(indent-according-to-mode))
-(defvar go-mode-delayed-point nil
- "The point following the previous insertion if the insertion
-was a delayed electric key. Used to communicate between
-`go-mode-delayed-electric' and `go-mode-delayed-electric-hook'.")
-(make-variable-buffer-local 'go-mode-delayed-point)
-
-(defun go-mode-delayed-electric (p)
- "Perform electric insertion, but delayed by one event.
-
-This inserts P into the buffer, as usual, then waits for another key.
-If that second key causes a buffer modification starting at the
-point after the insertion of P, reindents the line containing P."
-
- (interactive "p")
- (self-insert-command p)
- (setq go-mode-delayed-point (point)))
-
-(defun go-mode-delayed-electric-hook (b e l)
- "An after-change-function that implements `go-mode-delayed-electric'."
-
- (when (and go-mode-delayed-point
- (= go-mode-delayed-point b))
- (save-excursion
- (save-match-data
- (goto-char go-mode-delayed-point)
- (indent-according-to-mode))))
- (setq go-mode-delayed-point nil))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Parser
-;;
-
-(defvar go-mode-mark-cs-end 1
- "The point at which the comment/string cache ends. The buffer
-will be marked from the beginning up to this point (that is, up
-to and including character (1- go-mode-mark-cs-end)).")
-(make-variable-buffer-local 'go-mode-mark-cs-end)
-
-(defvar go-mode-mark-string-end 1
- "The point at which the string cache ends. The buffer
-will be marked from the beginning up to this point (that is, up
-to and including character (1- go-mode-mark-string-end)).")
-(make-variable-buffer-local 'go-mode-mark-string-end)
-
-(defvar go-mode-mark-comment-end 1
- "The point at which the comment cache ends. The buffer
-will be marked from the beginning up to this point (that is, up
-to and including character (1- go-mode-mark-comment-end)).")
-(make-variable-buffer-local 'go-mode-mark-comment-end)
-
-(defvar go-mode-mark-nesting-end 1
- "The point at which the nesting cache ends. The buffer will be
-marked from the beginning up to this point.")
-(make-variable-buffer-local 'go-mode-mark-nesting-end)
-
-(defun go-mode-mark-clear-cs (b e l)
- "An after-change-function that removes the go-mode-cs text property"
- (remove-text-properties b e '(go-mode-cs)))
-
-(defun go-mode-mark-clear-cache (b e)
- "A before-change-function that clears the comment/string and
-nesting caches from the modified point on."
-
- (save-restriction
- (widen)
- (when (<= b go-mode-mark-cs-end)
- ;; Remove the property adjacent to the change position.
- ;; It may contain positions pointing beyond the new end mark.
- (let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-cs)))
- (if cs (car cs) b))))
- (remove-text-properties
- b (min go-mode-mark-cs-end (point-max)) '(go-mode-cs nil))
- (setq go-mode-mark-cs-end b)))
-
- (when (<= b go-mode-mark-string-end)
- ;; Remove the property adjacent to the change position.
- ;; It may contain positions pointing beyond the new end mark.
- (let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-string)))
- (if cs (car cs) b))))
- (remove-text-properties
- b (min go-mode-mark-string-end (point-max)) '(go-mode-string nil))
- (setq go-mode-mark-string-end b)))
- (when (<= b go-mode-mark-comment-end)
- ;; Remove the property adjacent to the change position.
- ;; It may contain positions pointing beyond the new end mark.
- (let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-comment)))
- (if cs (car cs) b))))
- (remove-text-properties
- b (min go-mode-mark-string-end (point-max)) '(go-mode-comment nil))
- (setq go-mode-mark-comment-end b)))
-
- (when (< b go-mode-mark-nesting-end)
- (remove-text-properties b (min go-mode-mark-nesting-end (point-max)) '(go-mode-nesting nil))
- (setq go-mode-mark-nesting-end b))))
-
-(defmacro go-mode-parser (&rest body)
- "Evaluate BODY in an environment set up for parsers that use
-text properties to mark text. This inhibits changes to the undo
-list or the buffer's modification status and inhibits calls to
-the modification hooks. It also saves the excursion and
-restriction and widens the buffer, since most parsers are
-context-sensitive."
-
- (let ((modified-var (make-symbol "modified")))
- `(let ((buffer-undo-list t)
- (,modified-var (buffer-modified-p))
- (inhibit-modification-hooks t)
- (inhibit-read-only t))
- (save-excursion
- (save-restriction
- (widen)
- (unwind-protect
- (progn ,@body)
- (set-buffer-modified-p ,modified-var)))))))
-
-(defun go-mode-cs (&optional pos)
- "Return the comment/string state at point POS. If point is
-inside a comment or string (including the delimiters), this
-returns a pair (START . END) indicating the extents of the
-comment or string."
-
- (unless pos
- (setq pos (point)))
- (when (>= pos go-mode-mark-cs-end)
- (go-mode-mark-cs (1+ pos)))
- (get-text-property pos 'go-mode-cs))
-
-(defun go-mode-mark-cs (end)
- "Mark comments and strings up to point END. Don't call this
-directly; use `go-mode-cs'."
- (setq end (min end (point-max)))
- (go-mode-parser
- (save-match-data
- (let ((pos
- ;; Back up to the last known state.
- (let ((last-cs
- (and (> go-mode-mark-cs-end 1)
- (get-text-property (1- go-mode-mark-cs-end)
- 'go-mode-cs))))
- (if last-cs
- (car last-cs)
- (max 1 (1- go-mode-mark-cs-end))))))
- (while (< pos end)
- (goto-char pos)
- (let ((cs-end ; end of the text property
- (cond
- ((looking-at "//")
- (end-of-line)
- (1+ (point)))
- ((looking-at "/\\*")
- (goto-char (+ pos 2))
- (if (search-forward "*/" (1+ end) t)
- (point)
- end))
- ((looking-at "\"")
- (goto-char (1+ pos))
- (if (looking-at "[^\"\n\\\\]*\\(\\\\.[^\"\n\\\\]*\\)*\"")
- (match-end 0)
- (end-of-line)
- (point)))
- ((looking-at "'")
- (goto-char (1+ pos))
- (if (looking-at "[^'\n\\\\]*\\(\\\\.[^'\n\\\\]*\\)*'")
- (match-end 0)
- (end-of-line)
- (point)))
- ((looking-at "`")
- (goto-char (1+ pos))
- (while (if (search-forward "`" end t)
- (if (eq (char-after) ?`)
- (goto-char (1+ (point))))
- (goto-char end)
- nil))
- (point)))))
- (cond
- (cs-end
- (put-text-property pos cs-end 'go-mode-cs (cons pos cs-end))
- (setq pos cs-end))
- ((re-search-forward "[\"'`]\\|/[/*]" end t)
- (setq pos (match-beginning 0)))
- (t
- (setq pos end)))))
- (setq go-mode-mark-cs-end pos)))))
-
-(defun go-mode-in-comment (&optional pos)
- "Return the comment/string state at point POS. If point is
-inside a comment (including the delimiters), this
-returns a pair (START . END) indicating the extents of the
-comment or string."
-
- (unless pos
- (setq pos (point)))
- (when (> pos go-mode-mark-comment-end)
- (go-mode-mark-comment pos))
- (get-text-property pos 'go-mode-comment))
-
-(defun go-mode-mark-comment (end)
- "Mark comments up to point END. Don't call this directly; use `go-mode-in-comment'."
- (setq end (min end (point-max)))
- (go-mode-parser
- (save-match-data
- (let ((pos
- ;; Back up to the last known state.
- (let ((last-comment
- (and (> go-mode-mark-comment-end 1)
- (get-text-property (1- go-mode-mark-comment-end)
- 'go-mode-comment))))
- (if last-comment
- (car last-comment)
- (max 1 (1- go-mode-mark-comment-end))))))
- (while (< pos end)
- (goto-char pos)
- (let ((comment-end ; end of the text property
- (cond
- ((looking-at "//")
- (end-of-line)
- (1+ (point)))
- ((looking-at "/\\*")
- (goto-char (+ pos 2))
- (if (search-forward "*/" (1+ end) t)
- (point)
- end)))))
- (cond
- (comment-end
- (put-text-property pos comment-end 'go-mode-comment (cons pos comment-end))
- (setq pos comment-end))
- ((re-search-forward "/[/*]" end t)
- (setq pos (match-beginning 0)))
- (t
- (setq pos end)))))
- (setq go-mode-mark-comment-end pos)))))
-
-(defun go-mode-in-string (&optional pos)
- "Return the string state at point POS. If point is
-inside a string (including the delimiters), this
-returns a pair (START . END) indicating the extents of the
-comment or string."
-
- (unless pos
- (setq pos (point)))
- (when (> pos go-mode-mark-string-end)
- (go-mode-mark-string pos))
- (get-text-property pos 'go-mode-string))
-
-(defun go-mode-mark-string (end)
- "Mark strings up to point END. Don't call this
-directly; use `go-mode-in-string'."
- (setq end (min end (point-max)))
- (go-mode-parser
- (save-match-data
- (let ((pos
- ;; Back up to the last known state.
- (let ((last-cs
- (and (> go-mode-mark-string-end 1)
- (get-text-property (1- go-mode-mark-string-end)
- 'go-mode-string))))
- (if last-cs
- (car last-cs)
- (max 1 (1- go-mode-mark-string-end))))))
- (while (< pos end)
- (goto-char pos)
- (let ((cs-end ; end of the text property
- (cond
- ((looking-at "\"")
- (goto-char (1+ pos))
- (if (looking-at "[^\"\n\\\\]*\\(\\\\.[^\"\n\\\\]*\\)*\"")
- (match-end 0)
- (end-of-line)
- (point)))
- ((looking-at "'")
- (goto-char (1+ pos))
- (if (looking-at "[^'\n\\\\]*\\(\\\\.[^'\n\\\\]*\\)*'")
- (match-end 0)
- (end-of-line)
- (point)))
- ((looking-at "`")
- (goto-char (1+ pos))
- (while (if (search-forward "`" end t)
- (if (eq (char-after) ?`)
- (goto-char (1+ (point))))
- (goto-char end)
- nil))
- (point)))))
- (cond
- (cs-end
- (put-text-property pos cs-end 'go-mode-string (cons pos cs-end))
- (setq pos cs-end))
- ((re-search-forward "[\"'`]" end t)
- (setq pos (match-beginning 0)))
- (t
- (setq pos end)))))
- (setq go-mode-mark-string-end pos)))))
-
-(defun go-mode-font-lock-cs (limit comment)
- "Helper function for highlighting comment/strings. If COMMENT is t,
-set match data to the next comment after point, and advance point
-after it. If COMMENT is nil, use the next string. Returns nil
-if no further tokens of the type exist."
- ;; Ensures that `next-single-property-change' below will work properly.
- (go-mode-cs limit)
- (let (cs next (result 'scan))
- (while (eq result 'scan)
- (if (or (>= (point) limit) (eobp))
- (setq result nil)
- (setq cs (go-mode-cs))
- (if cs
- (if (eq (= (char-after (car cs)) ?/) comment)
- ;; If inside the expected comment/string, highlight it.
- (progn
- ;; If the match includes a "\n", we have a
- ;; multi-line construct. Mark it as such.
- (goto-char (car cs))
- (when (search-forward "\n" (cdr cs) t)
- (put-text-property
- (car cs) (cdr cs) 'font-lock-multline t))
- (set-match-data (list (car cs) (copy-marker (cdr cs))))
- (goto-char (cdr cs))
- (setq result t))
- ;; Wrong type. Look for next comment/string after this one.
- (goto-char (cdr cs)))
- ;; Not inside comment/string. Search for next comment/string.
- (setq next (next-single-property-change
- (point) 'go-mode-cs nil limit))
- (if (and next (< next limit))
- (goto-char next)
- (setq result nil)))))
- result))
-
-(defun go-mode-font-lock-cs-string (limit)
- "Font-lock iterator for strings."
- (go-mode-font-lock-cs limit nil))
-
-(defun go-mode-font-lock-cs-comment (limit)
- "Font-lock iterator for comments."
- (go-mode-font-lock-cs limit t))
-
-(defsubst go-mode-nesting (&optional pos)
- "Return the nesting at point POS. The nesting is a list
-of (START . END) pairs for all braces, parens, and brackets
-surrounding POS, starting at the inner-most nesting. START is
-the location of the open character. END is the location of the
-close character or nil if the nesting scanner has not yet
-encountered the close character."
-
- (unless pos
- (setq pos (point)))
- (if (= pos 1)
- '()
- (when (> pos go-mode-mark-nesting-end)
- (go-mode-mark-nesting pos))
- (get-text-property (- pos 1) 'go-mode-nesting)))
-
-(defun go-mode-mark-nesting (pos)
- "Mark nesting up to point END. Don't call this directly; use
-`go-mode-nesting'."
-
- (go-mode-cs pos)
- (go-mode-parser
- ;; Mark depth
- (goto-char go-mode-mark-nesting-end)
- (let ((nesting (go-mode-nesting))
- (last (point)))
- (while (< last pos)
- ;; Find the next depth-changing character
- (skip-chars-forward "^(){}[]" pos)
- ;; Mark everything up to this character with the current
- ;; nesting
- (put-text-property last (point) 'go-mode-nesting nesting)
- (when nil
- (let ((depth (length nesting)))
- (put-text-property last (point) 'face
- `((:background
- ,(format "gray%d" (* depth 10)))))))
- (setq last (point))
- ;; Update nesting
- (unless (eobp)
- (let ((ch (unless (go-mode-cs) (char-after))))
- (forward-char 1)
- (case ch
- ((?\( ?\{ ?\[)
- (setq nesting (cons (cons (- (point) 1) nil)
- nesting)))
- ((?\) ?\} ?\])
- (when nesting
- (setcdr (car nesting) (- (point) 1))
- (setq nesting (cdr nesting))))))))
- ;; Update state
- (setq go-mode-mark-nesting-end last))))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Indentation
-;;
-
-(defvar go-mode-non-terminating-keywords-regexp
- (let* ((kws go-mode-keywords)
- (kws (remove "break" kws))
- (kws (remove "continue" kws))
- (kws (remove "fallthrough" kws))
- (kws (remove "return" kws)))
- (regexp-opt kws 'words))
- "Regular expression matching all Go keywords that *do not*
-implicitly terminate a statement.")
-
-(defun go-mode-semicolon-p ()
- "True iff point immediately follows either an explicit or
-implicit semicolon. Point should immediately follow the last
-token on the line."
-
- ;; #Semicolons
- (case (char-before)
- ((?\;) t)
- ;; String literal
- ((?' ?\" ?`) t)
- ;; One of the operators and delimiters ++, --, ), ], or }
- ((?+) (eq (char-before (1- (point))) ?+))
- ((?-) (eq (char-before (1- (point))) ?-))
- ((?\) ?\] ?\}) t)
- ;; An identifier or one of the keywords break, continue,
- ;; fallthrough, or return or a numeric literal
- (otherwise
- (save-excursion
- (when (/= (skip-chars-backward "[:word:]_") 0)
- (not (looking-at go-mode-non-terminating-keywords-regexp)))))))
-
-(defun go-mode-whitespace-p (char)
- "Is newline, or char whitespace in the syntax table for go."
- (or (eq char ?\n)
- (= (char-syntax char) ?\ )))
-
-(defun go-mode-backward-skip-comments ()
- "Skip backward over comments and whitespace."
- ;; only proceed if point is in a comment or white space
- (if (or (go-mode-in-comment)
- (go-mode-whitespace-p (char-after (point))))
- (let ((loop-guard t))
- (while (and
- loop-guard
- (not (bobp)))
-
- (cond ((go-mode-whitespace-p (char-after (point)))
- ;; moves point back over any whitespace
- (re-search-backward "[^[:space:]]"))
-
- ((go-mode-in-comment)
- ;; move point to char preceeding current comment
- (goto-char (1- (car (go-mode-in-comment)))))
-
- ;; not in a comment or whitespace? we must be done.
- (t (setq loop-guard nil)
- (forward-char 1)))))))
-
-(defun go-mode-indentation ()
- "Compute the ideal indentation level of the current line.
-
-To the first order, this is the brace depth of the current line,
-plus parens that follow certain keywords. case, default, and
-labels are outdented one level, and continuation lines are
-indented one level."
-
+(defmacro go-paren-level ()
+ `(car (syntax-ppss)))
+
+(defmacro go-in-string-or-comment-p ()
+ `(nth 8 (syntax-ppss)))
+
+(defmacro go-in-string-p ()
+ `(nth 3 (syntax-ppss)))
+
+(defmacro go-in-comment-p ()
+ `(nth 4 (syntax-ppss)))
+
+(defmacro go-goto-beginning-of-string-or-comment ()
+ `(goto-char (nth 8 (syntax-ppss))))
+
+(defun go--backward-irrelevant (&optional stop-at-string)
+ "Skips backwards over any characters that are irrelevant for
+indentation and related tasks.
+
+It skips over whitespace, comments, cases and labels and, if
+STOP-AT-STRING is not true, over strings."
+
+ (let (pos (start-pos (point)))
+ (skip-chars-backward "\n[:blank:]")
+ (if (and (save-excursion (beginning-of-line) (go-in-string-p)) (looking-back "`") (not stop-at-string))
+ (backward-char))
+ (if (and (go-in-string-p) (not stop-at-string))
+ (go-goto-beginning-of-string-or-comment))
+ (if (looking-back "\\*/")
+ (backward-char))
+ (if (go-in-comment-p)
+ (go-goto-beginning-of-string-or-comment))
+ (setq pos (point))
+ (beginning-of-line)
+ (if (or (looking-at (concat "^" go-label-regexp ":")) (looking-at "^[[:space:]]*\\(case .+\\|default\\):"))
+ (end-of-line 0)
+ (goto-char pos))
+ (if (/= start-pos (point))
+ (go--backward-irrelevant stop-at-string))
+ (/= start-pos (point))))
+
+(defun go--buffer-narrowed-p ()
+ "Return non-nil if the current buffer is narrowed."
+ (/= (buffer-size)
+ (- (point-max)
+ (point-min))))
+
+(defun go-previous-line-has-dangling-op-p ()
+ "Returns non-nil if the current line is a continuation line."
+ (let* ((cur-line (line-number-at-pos))
+ (val (gethash cur-line go-dangling-cache 'nope)))
+ (if (or (go--buffer-narrowed-p) (equal val 'nope))
+ (save-excursion
+ (beginning-of-line)
+ (go--backward-irrelevant t)
+ (setq val (looking-back go-dangling-operators-regexp))
+ (if (not (go--buffer-narrowed-p))
+ (puthash cur-line val go-dangling-cache))))
+ val))
+
+(defun go-goto-opening-parenthesis (&optional char)
+ (let ((start-nesting (go-paren-level)))
+ (while (and (not (bobp))
+ (>= (go-paren-level) start-nesting))
+ (if (zerop (skip-chars-backward
+ (if char
+ (case char (?\] "^[") (?\} "^{") (?\) "^("))
+ "^[{(")))
+ (if (go-in-string-or-comment-p)
+ (go-goto-beginning-of-string-or-comment)
+ (backward-char))))))
+
+(defun go-indentation-at-point ()
(save-excursion
- (back-to-indentation)
- (let ((cs (go-mode-cs)))
- ;; Treat comments and strings differently only if the beginning
- ;; of the line is contained within them
- (when (and cs (= (point) (car cs)))
- (setq cs nil))
- ;; What type of context am I in?
+ (let (start-nesting (outindent 0))
+ (back-to-indentation)
+ (setq start-nesting (go-paren-level))
+
(cond
- ((and cs (save-excursion
- (goto-char (car cs))
- (looking-at "`")))
- ;; Inside a multi-line string. Don't mess with indentation.
- nil)
- (cs
- ;; Inside a general comment
- (goto-char (car cs))
- (forward-char 1)
- (current-column))
+ ((go-in-string-p)
+ (current-indentation))
+ ((looking-at "[])}]")
+ (go-goto-opening-parenthesis (char-after))
+ (if (go-previous-line-has-dangling-op-p)
+ (- (current-indentation) tab-width)
+ (current-indentation)))
+ ((progn (go--backward-irrelevant t) (looking-back go-dangling-operators-regexp))
+ ;; only one nesting for all dangling operators in one operation
+ (if (go-previous-line-has-dangling-op-p)
+ (current-indentation)
+ (+ (current-indentation) tab-width)))
+ ((zerop (go-paren-level))
+ 0)
+ ((progn (go-goto-opening-parenthesis) (< (go-paren-level) start-nesting))
+ (if (go-previous-line-has-dangling-op-p)
+ (current-indentation)
+ (+ (current-indentation) tab-width)))
(t
- ;; Not in a multi-line string or comment
- (let ((indent 0)
- (inside-indenting-paren nil))
- ;; Count every enclosing brace, plus parens that follow
- ;; import, const, var, or type and indent according to
- ;; depth. This simple rule does quite well, but also has a
- ;; very large extent. It would be better if we could mimic
- ;; some nearby indentation.
- (save-excursion
- (skip-chars-forward "})")
- (let ((first t))
- (dolist (nest (go-mode-nesting))
- (case (char-after (car nest))
- ((?\{)
- (incf indent tab-width))
- ((?\()
- (goto-char (car nest))
- (go-mode-backward-skip-comments)
- (backward-char)
- ;; Really just want the token before
- (when (looking-back "\\<import\\|const\\|var\\|type\\|package"
- (max (- (point) 7) (point-min)))
- (incf indent tab-width)
- (when first
- (setq inside-indenting-paren t)))))
- (setq first nil))))
-
- ;; case, default, and labels are outdented 1 level
- (when (looking-at "\\<case\\>\\|\\<default\\>\\|\\w+\\s *:\\(\\S.\\|$\\)")
- (decf indent tab-width))
-
- (when (looking-at "\\w+\\s *:.+,\\s *$")
- (incf indent tab-width))
-
- ;; Continuation lines are indented 1 level
- (beginning-of-line) ; back up to end of previous line
- (backward-char)
- (go-mode-backward-skip-comments) ; back up past any comments
- (when (case (char-before)
- ((nil ?\{ ?:)
- ;; At the beginning of a block or the statement
- ;; following a label.
- nil)
- ((?\()
- ;; Usually a continuation line in an expression,
- ;; unless this paren is part of a factored
- ;; declaration.
- (not inside-indenting-paren))
- ((?,)
- ;; Could be inside a literal. We're a little
- ;; conservative here and consider any comma within
- ;; curly braces (as opposed to parens) to be a
- ;; literal separator. This will fail to recognize
- ;; line-breaks in parallel assignments as
- ;; continuation lines.
- (let ((depth (go-mode-nesting)))
- (and depth
- (not (eq (char-after (caar depth)) ?\{)))))
- (t
- ;; We're in the middle of a block. Did the
- ;; previous line end with an implicit or explicit
- ;; semicolon?
- (not (go-mode-semicolon-p))))
- (incf indent tab-width))
-
- (max indent 0)))))))
+ (current-indentation))))))
(defun go-mode-indent-line ()
- "Indent the current line according to `go-mode-indentation'."
(interactive)
-
- ;; turn off case folding to distinguish keywords from identifiers
- ;; e.g. "default" is a keyword; "Default" can be a variable name.
- (let ((case-fold-search nil))
- (let ((col (go-mode-indentation)))
- (when col
- (let ((offset (- (current-column) (current-indentation))))
- (indent-line-to col)
- (when (> offset 0)
- (forward-char offset)))))))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Go mode
-;;
+ (let (indent
+ shift-amt
+ end
+ (pos (- (point-max) (point)))
+ (point (point))
+ (beg (line-beginning-position)))
+ (back-to-indentation)
+ (if (go-in-string-or-comment-p)
+ (goto-char point)
+ (setq indent (go-indentation-at-point))
+ (if (looking-at (concat go-label-regexp ":\\([[:space:]]*/.+\\)?$\\|case .+:\\|default:"))
+ (decf indent tab-width))
+ (setq shift-amt (- indent (current-column)))
+ (if (zerop shift-amt)
+ nil
+ (delete-region beg (point))
+ (indent-to indent))
+ ;; If initial point was within line's indentation,
+ ;; position after the indentation. Else stay at same point in text.
+ (if (> (- (point-max) pos) (point))
+ (goto-char (- (point-max) pos))))))
+
+(defun go-beginning-of-defun (&optional count)
+ (unless count (setq count 1))
+ (let ((first t) failure)
+ (dotimes (i (abs count))
+ (while (and (not failure)
+ (or first (go-in-string-or-comment-p)))
+ (if (>= count 0)
+ (progn
+ (go--backward-irrelevant)
+ (if (not (re-search-backward go-func-meth-regexp nil t))
+ (setq failure t)))
+ (if (looking-at go-func-meth-regexp)
+ (forward-char))
+ (if (not (re-search-forward go-func-meth-regexp nil t))
+ (setq failure t)))
+ (setq first nil)))
+ (if (< count 0)
+ (beginning-of-line))
+ (not failure)))
+
+(defun go-end-of-defun ()
+ (let (orig-level)
+ ;; It can happen that we're not placed before a function by emacs
+ (if (not (looking-at "func"))
+ (go-beginning-of-defun -1))
+ (skip-chars-forward "^{")
+ (forward-char)
+ (setq orig-level (go-paren-level))
+ (while (>= (go-paren-level) orig-level)
+ (skip-chars-forward "^}")
+ (forward-char))))
;;;###autoload
-(define-derived-mode go-mode nil "Go"
+(define-derived-mode go-mode fundamental-mode "Go"
"Major mode for editing Go source text.
-This provides basic syntax highlighting for keywords, built-ins,
-functions, and some types. It also provides indentation that is
-\(almost) identical to gofmt."
+This mode provides (not just) basic editing capabilities for
+working with Go code. It offers almost complete syntax
+highlighting, indentation that is almost identical to gofmt and
+proper parsing of the buffer content to allow features such as
+navigation by function, manipulation of comments or detection of
+strings.
+
+In addition to these core features, it offers various features to
+help with writing Go code. You can directly run buffer content
+through gofmt, read godoc documentation from within Emacs, modify
+and clean up the list of package imports or interact with the
+Playground (uploading and downloading pastes).
+
+The following extra functions are defined:
+
+- `gofmt'
+- `godoc'
+- `go-import-add'
+- `go-remove-unused-imports'
+- `go-goto-imports'
+- `go-play-buffer' and `go-play-region'
+- `go-download-play'
+
+If you want to automatically run `gofmt' before saving a file,
+add the following hook to your emacs configuration:
+
+\(add-hook 'before-save-hook 'gofmt-before-save)
+
+If you're looking for even more integration with Go, namely
+on-the-fly syntax checking, auto-completion and snippets, it is
+recommended that you look at goflymake
+\(https://github.com/dougm/goflymake), gocode
+\(https://github.com/nsf/gocode) and yasnippet-go
+\(https://github.com/dominikh/yasnippet-go)"
;; Font lock
(set (make-local-variable 'font-lock-defaults)
- '(go-mode-font-lock-keywords nil nil nil nil))
-
- ;; Remove stale text properties
- (save-restriction
- (widen)
- (let ((modified (buffer-modified-p)))
- (remove-text-properties 1 (point-max)
- '(go-mode-cs nil go-mode-nesting nil))
- ;; remove-text-properties marks the buffer modified. undo that if it
- ;; wasn't originally marked modified.
- (set-buffer-modified-p modified)))
-
- ;; Reset the syntax mark caches
- (setq go-mode-mark-cs-end 1
- go-mode-mark-nesting-end 1)
- (add-hook 'before-change-functions #'go-mode-mark-clear-cache nil t)
- (add-hook 'after-change-functions #'go-mode-mark-clear-cs nil t)
+ '(go--build-font-lock-keywords))
;; Indentation
- (set (make-local-variable 'indent-line-function)
- #'go-mode-indent-line)
- (add-hook 'after-change-functions #'go-mode-delayed-electric-hook nil t)
+ (set (make-local-variable 'indent-line-function) 'go-mode-indent-line)
;; Comments
(set (make-local-variable 'comment-start) "// ")
(set (make-local-variable 'comment-end) "")
+ (set (make-local-variable 'comment-use-syntax) t)
+ (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
+
+ (set (make-local-variable 'beginning-of-defun-function) 'go-beginning-of-defun)
+ (set (make-local-variable 'end-of-defun-function) 'go-end-of-defun)
+
+ (set (make-local-variable 'parse-sexp-lookup-properties) t)
+ (if (boundp 'syntax-propertize-function)
+ (set (make-local-variable 'syntax-propertize-function) 'go-propertize-syntax))
+
+ (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
+ (add-hook 'before-change-functions (lambda (x y) (setq go-dangling-cache (make-hash-table :test 'eql))) t t)
+
+
+ (setq imenu-generic-expression
+ '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
+ ("func" "^func *\\(.*\\) {" 1)))
+ (imenu-add-to-menubar "Index")
;; Go style
(setq indent-tabs-mode t)
@@ -739,24 +352,14 @@ functions, and some types. It also provides indentation that is
;; those alists are traversed in *reverse* order:
;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html
(when (and (boundp 'compilation-error-regexp-alist)
- (boundp 'compilation-error-regexp-alist-alist))
- (add-to-list 'compilation-error-regexp-alist 'go-test t)
- (add-to-list 'compilation-error-regexp-alist-alist
- '(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t)))
+ (boundp 'compilation-error-regexp-alist-alist))
+ (add-to-list 'compilation-error-regexp-alist 'go-test t)
+ (add-to-list 'compilation-error-regexp-alist-alist
+ '(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t)))
;;;###autoload
-(add-to-list 'auto-mode-alist (cons "\\.go$" #'go-mode))
-
-(defun go-mode-reload ()
- "Reload go-mode.el and put the current buffer into Go mode.
-Useful for development work."
+(add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
- (interactive)
- (unload-feature 'go-mode)
- (require 'go-mode)
- (go-mode))
-
-;;;###autoload
(defun gofmt ()
"Pipe the current buffer through the external tool `gofmt`.
Replace the current buffer on success; display errors on failure."
@@ -768,84 +371,103 @@ Replace the current buffer on success; display errors on failure."
(patchbuf (get-buffer-create "*Gofmt patch*")))
(with-current-buffer patchbuf
(let ((errbuf (get-buffer-create "*Gofmt Errors*"))
- (coding-system-for-read 'utf-8) ;; use utf-8 with subprocesses
+ ;; use utf-8 with subprocesses
+ (coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8))
(with-current-buffer errbuf
- (toggle-read-only 0)
+ (setq buffer-read-only nil)
(erase-buffer))
(with-current-buffer srcbuf
(save-restriction
(let (deactivate-mark)
(widen)
- ; If this is a new file, diff-mode can't apply a
- ; patch to a non-exisiting file, so replace the buffer
- ; completely with the output of 'gofmt'.
- ; If the file exists, patch it to keep the 'undo' list happy.
+ ;; If this is a new file, diff-mode can't apply a
+ ;; patch to a non-exisiting file, so replace the buffer
+ ;; completely with the output of 'gofmt'.
+ ;; If the file exists, patch it to keep the 'undo' list happy.
(let* ((newfile (not (file-exists-p filename)))
- (flag (if newfile "" " -d")))
- (if (= 0 (shell-command-on-region (point-min) (point-max)
- (concat "gofmt" flag)
- patchbuf nil errbuf))
- ; gofmt succeeded: replace buffer or apply patch hunks.
+ (flag (if newfile "" " -d")))
+
+ ;; diff-mode doesn't work too well with missing
+ ;; end-of-file newline, so add one
+ (if (/= (char-after (1- (point-max))) ?\n)
+ (save-excursion
+ (goto-char (point-max))
+ (insert ?\n)))
+
+ (if (zerop (shell-command-on-region (point-min) (point-max)
+ (concat "gofmt" flag)
+ patchbuf nil errbuf))
+ ;; gofmt succeeded: replace buffer or apply patch hunks.
(let ((old-point (point))
(old-mark (mark t)))
(kill-buffer errbuf)
(if newfile
- ; New file, replace it (diff-mode won't work)
- (gofmt-replace-buffer srcbuf patchbuf)
- ; Existing file, patch it
- (gofmt-apply-patch filename srcbuf patchbuf))
+ ;; New file, replace it (diff-mode won't work)
+ (gofmt--replace-buffer srcbuf patchbuf)
+ ;; Existing file, patch it
+ (gofmt--apply-patch filename srcbuf patchbuf))
(goto-char (min old-point (point-max)))
;; Restore the mark and point
(if old-mark (push-mark (min old-mark (point-max)) t))
(set-window-configuration currconf))
- ;; gofmt failed: display the errors
- (gofmt-process-errors filename errbuf))))))
+ ;; gofmt failed: display the errors
+ (message "Could not apply gofmt. Check errors for details")
+ (gofmt--process-errors filename errbuf))))))
;; Collapse any window opened on outbuf if shell-command-on-region
;; displayed it.
(delete-windows-on patchbuf)))
(kill-buffer patchbuf))))
-(defun gofmt-replace-buffer (srcbuf patchbuf)
+(defun gofmt--replace-buffer (srcbuf patchbuf)
(with-current-buffer srcbuf
(erase-buffer)
- (insert-buffer-substring patchbuf)))
+ (insert-buffer-substring patchbuf))
+ (message "Applied gofmt"))
-(defconst gofmt-stdin-tag "<standard input>")
-
-(defun gofmt-apply-patch (filename srcbuf patchbuf)
- (require 'diff-mode)
+(defun gofmt--apply-patch (filename srcbuf patchbuf)
;; apply all the patch hunks
- (with-current-buffer patchbuf
- (replace-regexp "^--- /tmp/gofmt[0-9]*" (concat "--- " filename)
- nil (point-min) (point-max))
- (condition-case nil
- (while t
- (diff-hunk-next)
- (diff-apply-hunk))
- ;; When there's no more hunks, diff-hunk-next signals an error, ignore it
- (error nil))))
-
-(defun gofmt-process-errors (filename errbuf)
+ (let (changed)
+ (with-current-buffer patchbuf
+ (goto-char (point-min))
+ ;; The .* is for TMPDIR, but to avoid dealing with TMPDIR
+ ;; having a trailing / or not, it's easier to just search for .*
+ ;; especially as we're only replacing the first instance.
+ (if (re-search-forward "^--- \\(.*/gofmt[0-9]*\\)" nil t)
+ (replace-match filename nil nil nil 1))
+ (condition-case nil
+ (while t
+ (diff-hunk-next)
+ (diff-apply-hunk)
+ (setq changed t))
+ ;; When there's no more hunks, diff-hunk-next signals an error, ignore it
+ (error nil)))
+ (if changed (message "Applied gofmt") (message "Buffer was already gofmted"))))
+
+(defun gofmt--process-errors (filename errbuf)
;; Convert the gofmt stderr to something understood by the compilation mode.
(with-current-buffer errbuf
- (beginning-of-buffer)
+ (goto-char (point-min))
(insert "gofmt errors:\n")
- (replace-string gofmt-stdin-tag (file-name-nondirectory filename) nil (point-min) (point-max))
+ (if (search-forward gofmt-stdin-tag nil t)
+ (replace-match (file-name-nondirectory filename) nil t))
(display-buffer errbuf)
(compilation-mode)))
;;;###autoload
(defun gofmt-before-save ()
"Add this to .emacs to run gofmt on the current buffer when saving:
- (add-hook 'before-save-hook #'gofmt-before-save)"
+ (add-hook 'before-save-hook 'gofmt-before-save).
+
+Note that this will cause go-mode to get loaded the first time
+you save any file, kind of defeating the point of autoloading."
(interactive)
(when (eq major-mode 'go-mode) (gofmt)))
-(defun godoc-read-query ()
+(defun godoc--read-query ()
"Read a godoc query from the minibuffer."
;; Compute the default query as the symbol under the cursor.
;; TODO: This does the wrong thing for e.g. multipart.NewReader (it only grabs
@@ -859,7 +481,7 @@ Replace the current buffer on success; display errors on failure."
"godoc: ")
nil nil symbol)))
-(defun godoc-get-buffer (query)
+(defun godoc--get-buffer (query)
"Get an empty buffer for a godoc query."
(let* ((buffer-name (concat "*godoc " query "*"))
(buffer (get-buffer buffer-name)))
@@ -867,13 +489,14 @@ Replace the current buffer on success; display errors on failure."
(when buffer (kill-buffer buffer))
(get-buffer-create buffer-name)))
-(defun godoc-buffer-sentinel (proc event)
+(defun godoc--buffer-sentinel (proc event)
"Sentinel function run when godoc command completes."
(with-current-buffer (process-buffer proc)
(cond ((string= event "finished\n") ;; Successful exit.
(goto-char (point-min))
- (display-buffer (current-buffer) 'not-this-window))
- ((not (= (process-exit-status proc) 0)) ;; Error exit.
+ (view-mode 1)
+ (display-buffer (current-buffer) t))
+ ((/= (process-exit-status proc) 0) ;; Error exit.
(let ((output (buffer-string)))
(kill-buffer (current-buffer))
(message (concat "godoc: " output)))))))
@@ -881,12 +504,217 @@ Replace the current buffer on success; display errors on failure."
;;;###autoload
(defun godoc (query)
"Show go documentation for a query, much like M-x man."
- (interactive (list (godoc-read-query)))
+ (interactive (list (godoc--read-query)))
(unless (string= query "")
(set-process-sentinel
- (start-process-shell-command "godoc" (godoc-get-buffer query)
+ (start-process-shell-command "godoc" (godoc--get-buffer query)
(concat "godoc " query))
- 'godoc-buffer-sentinel)
+ 'godoc--buffer-sentinel)
nil))
+(defun go-goto-imports ()
+ "Move point to the block of imports.
+
+If using
+
+ import (
+ \"foo\"
+ \"bar\"
+ )
+
+it will move point directly behind the last import.
+
+If using
+
+ import \"foo\"
+ import \"bar\"
+
+it will move point to the next line after the last import.
+
+If no imports can be found, point will be moved after the package
+declaration."
+ (interactive)
+ ;; FIXME if there's a block-commented import before the real
+ ;; imports, we'll jump to that one.
+
+ ;; Generally, this function isn't very forgiving. it'll bark on
+ ;; extra whitespace. It works well for clean code.
+ (let ((old-point (point)))
+ (goto-char (point-min))
+ (cond
+ ((re-search-forward "^import ([^)]+)" nil t)
+ (backward-char 2)
+ 'block)
+ ((re-search-forward "\\(^import \\([^\"]+ \\)?\"[^\"]+\"\n?\\)+" nil t)
+ 'single)
+ ((re-search-forward "^[[:space:]\n]*package .+?\n" nil t)
+ (message "No imports found, moving point after package declaration")
+ 'none)
+ (t
+ (goto-char old-point)
+ (message "No imports or package declaration found. Is this really a Go file?")
+ 'fail))))
+
+(defun go-play-buffer ()
+ "Like `go-play-region', but acts on the entire buffer."
+ (interactive)
+ (go-play-region (point-min) (point-max)))
+
+(defun go-play-region (start end)
+ "Send the region to the Playground and stores the resulting
+link in the kill ring."
+ (interactive "r")
+ (let* ((url-request-method "POST")
+ (url-request-extra-headers
+ '(("Content-Type" . "application/x-www-form-urlencoded")))
+ (url-request-data (buffer-substring-no-properties start end))
+ (content-buf (url-retrieve
+ "http://play.golang.org/share"
+ (lambda (arg)
+ (cond
+ ((equal :error (car arg))
+ (signal 'go-play-error (cdr arg)))
+ (t
+ (re-search-forward "\n\n")
+ (kill-new (format "http://play.golang.org/p/%s" (buffer-substring (point) (point-max))))
+ (message "http://play.golang.org/p/%s" (buffer-substring (point) (point-max)))))))))))
+
+;;;###autoload
+(defun go-download-play (url)
+ "Downloads a paste from the playground and inserts it in a Go
+buffer. Tries to look for a URL at point."
+ (interactive (list (read-from-minibuffer "Playground URL: " (ffap-url-p (ffap-string-at-point 'url)))))
+ (with-current-buffer
+ (let ((url-request-method "GET") url-request-data url-request-extra-headers)
+ (url-retrieve-synchronously (concat url ".go")))
+ (let ((buffer (generate-new-buffer (concat (car (last (split-string url "/"))) ".go"))))
+ (goto-char (point-min))
+ (re-search-forward "\n\n")
+ (copy-to-buffer buffer (point) (point-max))
+ (kill-buffer)
+ (with-current-buffer buffer
+ (go-mode)
+ (switch-to-buffer buffer)))))
+
+(defun go-propertize-syntax (start end)
+ (save-excursion
+ (goto-char start)
+ (while (search-forward "\\" end t)
+ (put-text-property (1- (point)) (point) 'syntax-table (if (= (char-after) ?`) '(1) '(9))))))
+
+;; ;; Commented until we actually make use of this function
+;; (defun go--common-prefix (sequences)
+;; ;; mismatch and reduce are cl
+;; (assert sequences)
+;; (flet ((common-prefix (s1 s2)
+;; (let ((diff-pos (mismatch s1 s2)))
+;; (if diff-pos (subseq s1 0 diff-pos) s1))))
+;; (reduce #'common-prefix sequences)))
+
+(defun go-import-add (arg import)
+ "Add a new import to the list of imports.
+
+When called with a prefix argument asks for an alternative name
+to import the package as.
+
+If no list exists yet, one will be created if possible.
+
+If an identical import has been commented, it will be
+uncommented, otherwise a new import will be added."
+
+ ;; - If there's a matching `// import "foo"`, uncomment it
+ ;; - If we're in an import() block and there's a matching `"foo"`, uncomment it
+ ;; - Otherwise add a new import, with the appropriate syntax
+ (interactive
+ (list
+ current-prefix-arg
+ (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go-packages)))))
+ (save-excursion
+ (let (as line import-start)
+ (if arg
+ (setq as (read-from-minibuffer "Import as: ")))
+ (if as
+ (setq line (format "%s \"%s\"" as import))
+ (setq line (format "\"%s\"" import)))
+
+ (goto-char (point-min))
+ (if (re-search-forward (concat "^[[:space:]]*//[[:space:]]*import " line "$") nil t)
+ (uncomment-region (line-beginning-position) (line-end-position))
+ (case (go-goto-imports)
+ ('fail (message "Could not find a place to add import."))
+ ('block
+ (save-excursion
+ (re-search-backward "^import (")
+ (setq import-start (point)))
+ (if (re-search-backward (concat "^[[:space:]]*//[[:space:]]*" line "$") import-start t)
+ (uncomment-region (line-beginning-position) (line-end-position))
+ (insert "\n\t" line)))
+ ('single (insert "import " line "\n"))
+ ('none (insert "\nimport (\n\t" line "\n)\n")))))))
+
+(defun go-root-and-paths ()
+ (let* ((output (process-lines "go" "env" "GOROOT" "GOPATH"))
+ (root (car output))
+ (paths (split-string (car (cdr output)) ":")))
+ (append (list root) paths)))
+
+(defun go-packages ()
+ (sort
+ (delete-dups
+ (mapcan
+ (lambda (topdir)
+ (let ((pkgdir (concat topdir "/pkg/")))
+ (mapcan (lambda (dir)
+ (mapcar (lambda (file)
+ (let ((sub (substring file (length pkgdir) -2)))
+ (unless (or (string-prefix-p "obj/" sub) (string-prefix-p "tool/" sub))
+ (mapconcat 'identity (cdr (split-string sub "/")) "/"))))
+ (if (file-directory-p dir)
+ (directory-files dir t "\\.a$"))))
+ (if (file-directory-p pkgdir)
+ (find-lisp-find-files-internal pkgdir 'find-lisp-file-predicate-is-directory 'find-lisp-default-directory-predicate)))))
+ (go-root-and-paths)))
+ 'string<))
+
+(defun go-unused-imports-lines ()
+ ;; FIXME Technically, -o /dev/null fails in quite some cases (on
+ ;; Windows, when compiling from within GOPATH). Practically,
+ ;; however, it has the same end result: There won't be a
+ ;; compiled binary/archive, and we'll get our import errors when
+ ;; there are any.
+ (reverse (remove nil
+ (mapcar
+ (lambda (line)
+ (if (string-match "^\\(.+\\):\\([[:digit:]]+\\): imported and not used: \".+\"$" line)
+ (if (string= (file-truename (match-string 1 line)) (file-truename buffer-file-name))
+ (string-to-number (match-string 2 line)))))
+ (split-string (shell-command-to-string
+ (if (string-match "_test\.go$" buffer-file-truename)
+ "go test -c"
+ "go build -o /dev/null")) "\n")))))
+
+(defun go-remove-unused-imports (arg)
+ "Removes all unused imports. If ARG is non-nil, unused imports
+will be commented, otherwise they will be removed completely."
+ (interactive "P")
+ (save-excursion
+ (let ((cur-buffer (current-buffer)) flymake-state lines)
+ (when (boundp 'flymake-mode)
+ (setq flymake-state flymake-mode)
+ (flymake-mode-off))
+ (save-some-buffers nil (lambda () (equal cur-buffer (current-buffer))))
+ (if (buffer-modified-p)
+ (message "Cannot operate on unsaved buffer")
+ (setq lines (go-unused-imports-lines))
+ (dolist (import lines)
+ (goto-char (point-min))
+ (forward-line (1- import))
+ (beginning-of-line)
+ (if arg
+ (comment-region (line-beginning-position) (line-end-position))
+ (let ((kill-whole-line t))
+ (kill-line))))
+ (message "Removed %d imports" (length lines)))
+ (if flymake-state (flymake-mode-on)))))
+
(provide 'go-mode)
diff --git a/misc/git/pre-commit b/misc/git/pre-commit
new file mode 100755
index 000000000..18b7f832f
--- /dev/null
+++ b/misc/git/pre-commit
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright 2012 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.
+
+# git gofmt pre-commit hook
+#
+# To use, store as .git/hooks/pre-commit inside your repository and make sure
+# it has execute permissions.
+#
+# This script does not handle file names that contain spaces.
+
+gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$')
+[ -z "$gofiles" ] && exit 0
+
+unformatted=$(gofmt -l $gofiles)
+[ -z "$unformatted" ] && exit 0
+
+# Some files are not gofmt'd. Print message and fail.
+
+echo >&2 "Go files must be formatted with gofmt. Please run:"
+for fn in $unformatted; do
+ echo >&2 " gofmt -w $PWD/$fn"
+done
+
+exit 1
diff --git a/misc/goplay/doc.go b/misc/goplay/doc.go
index fd4a28e71..61e74a000 100644
--- a/misc/goplay/doc.go
+++ b/misc/goplay/doc.go
@@ -4,20 +4,20 @@
// Goplay is a web interface for experimenting with Go code.
// It is similar to the Go Playground: http://golang.org/doc/play/
-//
+//
// To use goplay:
// $ cd $GOROOT/misc/goplay
// $ go run goplay.go
// and load http://localhost:3999/ in a web browser.
-//
+//
// You should see a Hello World program, which you can compile and run by
// pressing shift-enter. There is also a "compile-on-keypress" feature that can
// be enabled by checking a checkbox.
-//
+//
// WARNING! CUIDADO! ACHTUNG! ATTENZIONE!
// A note on security: anyone with access to the goplay web interface can run
// arbitrary code on your computer. Goplay is not a sandbox, and has no other
-// security mechanisms. Do not deploy it in untrusted environments.
-// By default, goplay listens only on localhost. This can be overridden with
+// security mechanisms. Do not deploy it in untrusted environments.
+// By default, goplay listens only on localhost. This can be overridden with
// the -http parameter. Do so at your own risk.
-package documentation
+package main
diff --git a/misc/goplay/goplay.go b/misc/goplay/goplay.go
index 9ce4f89ae..94d04139d 100644
--- a/misc/goplay/goplay.go
+++ b/misc/goplay/goplay.go
@@ -44,7 +44,7 @@ func main() {
log.Fatal(http.ListenAndServe(*httpListen, nil))
}
-// FrontPage is an HTTP handler that renders the goplay interface.
+// FrontPage is an HTTP handler that renders the goplay interface.
// If a filename is supplied in the path component of the URI,
// its contents will be put in the interface's text area.
// Otherwise, the default "hello, world" program is displayed.
diff --git a/misc/osx/README b/misc/osx/README
deleted file mode 100644
index 2408dc459..000000000
--- a/misc/osx/README
+++ /dev/null
@@ -1,3 +0,0 @@
-Use package.bash to construct a package file (Go.pkg) for installation on OS X.
-
-This script depends on PackageMaker (Developer Tools).
diff --git a/misc/osx/etc/paths.d/go b/misc/osx/etc/paths.d/go
deleted file mode 100644
index 532e5f936..000000000
--- a/misc/osx/etc/paths.d/go
+++ /dev/null
@@ -1 +0,0 @@
-/usr/local/go/bin
diff --git a/misc/osx/package.bash b/misc/osx/package.bash
deleted file mode 100755
index d4ee5f48f..000000000
--- a/misc/osx/package.bash
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/bash
-# 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.
-
-set -e
-
-if ! test -f ../../src/all.bash; then
- echo >&2 "package.bash must be run from $GOROOT/misc/osx"
- exit 1
-fi
-
-echo >&2 "Locating PackageMaker..."
-PM=/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker
-if [ ! -x $PM ]; then
- PM=/Developer$PM
- if [ ! -x $PM ]; then
- echo >&2 "could not find PackageMaker; aborting"
- exit 1
- fi
-fi
-echo >&2 " Found: $PM"
-
-BUILD=/tmp/go.build.tmp
-ROOT=`hg root`
-export GOROOT=$BUILD/root/usr/local/go
-export GOROOT_FINAL=/usr/local/go
-
-echo >&2 "Removing old images"
-rm -f *.pkg *.dmg
-
-echo >&2 "Preparing temporary directory"
-rm -rf $BUILD
-mkdir -p $BUILD
-trap "rm -rf $BUILD" 0
-
-echo >&2 "Copying go source distribution"
-mkdir -p $BUILD/root/usr/local
-cp -r $ROOT $GOROOT
-cp -r etc $BUILD/root/etc
-
-pushd $GOROOT > /dev/null
-
-echo >&2 "Detecting version..."
-pushd src > /dev/null
-./make.bash --dist-tool > /dev/null
-../bin/tool/dist version > /dev/null
-popd > /dev/null
-mv VERSION.cache VERSION
-VERSION="$(cat VERSION | awk '{ print $1 }')"
-echo >&2 " Version: $VERSION"
-
-echo >&2 "Pruning Mercurial metadata"
-rm -rf .hg .hgignore .hgtags
-
-echo >&2 "Building Go"
-pushd src
-./all.bash 2>&1 | sed "s/^/ /" >&2
-popd > /dev/null
-
-popd > /dev/null
-
-echo >&2 "Building package"
-$PM -v -r $BUILD/root -o "go.darwin.$VERSION.pkg" \
- --scripts scripts \
- --id com.googlecode.go \
- --title Go \
- --version "0.1" \
- --target "10.5"
diff --git a/misc/osx/scripts/postinstall b/misc/osx/scripts/postinstall
deleted file mode 100644
index 3748721c7..000000000
--- a/misc/osx/scripts/postinstall
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-GOROOT=/usr/local/go
-
-echo "Fixing permissions"
-cd $GOROOT
-find . -exec chmod ugo+r \{\} \;
-find bin -exec chmod ugo+rx \{\} \;
-find . -type d -exec chmod ugo+rx \{\} \;
-chmod o-w .
-
-echo "Fixing debuggers via sudo.bash"
-# setgrp procmod the debuggers (sudo.bash)
-cd $GOROOT/src
-./sudo.bash
-
-echo "Installing miscellaneous files:"
-XCODE_MISC_DIR="/Library/Application Support/Developer/Shared/Xcode/Specifications/"
-if [ -f $XCODE_MISC_DIR ]; then
- echo " XCode"
- cp $GOROOT/misc/xcode/* $XCODE_MISC_DIR
-fi
-
diff --git a/misc/pprof b/misc/pprof
index 2fe56503c..7c379acbe 100755
--- a/misc/pprof
+++ b/misc/pprof
@@ -634,7 +634,7 @@ sub Main() {
# (only matters when --heapcheck is given but we must be
# compatible with old branches that did not pass --heapcheck always):
if ($total != 0) {
- printf("Total: %s %s\n", Unparse($total), Units());
+ Infof("Total: %s %s\n", Unparse($total), Units());
}
PrintText($symbols, $flat, $cumulative, $total, -1);
} elsif ($main::opt_raw) {
@@ -726,10 +726,8 @@ sub RunWeb {
"firefox",
);
foreach my $b (@alt) {
- if (-f $b) {
- if (system($b, $fname) == 0) {
- return;
- }
+ if (system($b, $fname) == 0) {
+ return;
}
}
@@ -931,7 +929,7 @@ sub ProcessProfile {
if ($focus ne '') {
$profile = FocusProfile($symbols, $profile, $focus);
my $focus_count = TotalProfile($profile);
- printf("After focusing on '%s': %s %s of %s (%0.1f%%)\n",
+ Infof("After focusing on '%s': %s %s of %s (%0.1f%%)\n",
$focus,
Unparse($focus_count), Units(),
Unparse($total_count), ($focus_count*100.0) / $total_count);
@@ -939,7 +937,7 @@ sub ProcessProfile {
if ($ignore ne '') {
$profile = IgnoreProfile($symbols, $profile, $ignore);
my $ignore_count = TotalProfile($profile);
- printf("After ignoring '%s': %s %s of %s (%0.1f%%)\n",
+ Infof("After ignoring '%s': %s %s of %s (%0.1f%%)\n",
$ignore,
Unparse($ignore_count), Units(),
Unparse($total_count),
@@ -1115,6 +1113,15 @@ sub PrintSymbolizedProfile {
}
}
+# Print information conditionally filtered out depending on the output
+# format.
+sub Infof {
+ my $format = shift;
+ my @args = @_;
+ return if $main::opt_svg;
+ printf($format, @args);
+}
+
# Print text output
sub PrintText {
my $symbols = shift;
@@ -2638,6 +2645,8 @@ sub RemoveUninterestingFrames {
'runtime.makemap_c',
'runtime.makeslice',
'runtime.mal',
+ 'runtime.settype',
+ 'runtime.settype_flush',
'runtime.slicebytetostring',
'runtime.sliceinttostring',
'runtime.stringtoslicebyte',
@@ -2971,32 +2980,32 @@ print STDERR "Read $url\n";
sub IsProfileURL {
my $profile_name = shift;
- my ($host, $port, $prefix, $path) = ParseProfileURL($profile_name);
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($profile_name);
return defined($host) and defined($port) and defined($path);
}
sub ParseProfileURL {
my $profile_name = shift;
if (defined($profile_name) &&
- $profile_name =~ m,^(http://|)([^/:]+):(\d+)(|\@\d+)(|/|(.*?)($PROFILE_PAGE|$PMUPROFILE_PAGE|$HEAP_PAGE|$GROWTH_PAGE|$THREAD_PAGE|$CONTENTION_PAGE|$WALL_PAGE|$FILTEREDPROFILE_PAGE))$,o) {
+ $profile_name =~ m,^(?:(https?)://|)([^/:]+):(\d+)(|\@\d+)(|/|(.*?)($PROFILE_PAGE|$PMUPROFILE_PAGE|$HEAP_PAGE|$GROWTH_PAGE|$THREAD_PAGE|$CONTENTION_PAGE|$WALL_PAGE|$FILTEREDPROFILE_PAGE))$,o) {
# $7 is $PROFILE_PAGE/$HEAP_PAGE/etc. $5 is *everything* after
# the hostname, as long as that everything is the empty string,
# a slash, or something ending in $PROFILE_PAGE/$HEAP_PAGE/etc.
# So "$7 || $5" is $PROFILE_PAGE/etc if there, or else it's "/" or "".
- return ($2, $3, $6, $7 || $5);
+ return ($1 || "http", $2, $3, $6, $7 || $5);
}
return ();
}
# We fetch symbols from the first profile argument.
sub SymbolPageURL {
- my ($host, $port, $prefix, $path) = ParseProfileURL($main::pfile_args[0]);
- return "http://$host:$port$prefix$SYMBOL_PAGE";
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($main::pfile_args[0]);
+ return "$scheme://$host:$port$prefix$SYMBOL_PAGE";
}
sub FetchProgramName() {
- my ($host, $port, $prefix, $path) = ParseProfileURL($main::pfile_args[0]);
- my $url = "http://$host:$port$prefix$PROGRAM_NAME_PAGE";
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($main::pfile_args[0]);
+ my $url = "$scheme://$host:$port$prefix$PROGRAM_NAME_PAGE";
my $command_line = "$CURL -s '$url'";
open(CMDLINE, "$command_line |") or error($command_line);
my $cmdline = <CMDLINE>;
@@ -3128,7 +3137,7 @@ sub BaseName {
sub MakeProfileBaseName {
my ($binary_name, $profile_name) = @_;
- my ($host, $port, $prefix, $path) = ParseProfileURL($profile_name);
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($profile_name);
my $binary_shortname = BaseName($binary_name);
return sprintf("%s.%s.%s-port%s",
$binary_shortname, $main::op_time, $host, $port);
@@ -3143,7 +3152,7 @@ sub FetchDynamicProfile {
if (!IsProfileURL($profile_name)) {
return $profile_name;
} else {
- my ($host, $port, $prefix, $path) = ParseProfileURL($profile_name);
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($profile_name);
if ($path eq "" || $path eq "/") {
# Missing type specifier defaults to cpu-profile
$path = $PROFILE_PAGE;
@@ -3155,7 +3164,7 @@ sub FetchDynamicProfile {
my $curl_timeout;
if (($path =~ m/$PROFILE_PAGE/) || ($path =~ m/$PMUPROFILE_PAGE/)) {
if ($path =~ m/$PROFILE_PAGE/) {
- $url = sprintf("http://$host:$port$prefix$path?seconds=%d",
+ $url = sprintf("$scheme://$host:$port$prefix$path?seconds=%d",
$main::opt_seconds);
} else {
if ($profile_name =~ m/[?]/) {
@@ -3163,7 +3172,7 @@ sub FetchDynamicProfile {
} else {
$profile_name .= "?"
}
- $url = sprintf("http://$profile_name" . "seconds=%d",
+ $url = sprintf("$scheme://$profile_name" . "seconds=%d",
$main::opt_seconds);
}
$curl_timeout = sprintf("--max-time %d",
@@ -3174,7 +3183,7 @@ sub FetchDynamicProfile {
my $suffix = $path;
$suffix =~ s,/,.,g;
$profile_file .= "$suffix";
- $url = "http://$host:$port$prefix$path";
+ $url = "$scheme://$host:$port$prefix$path";
$curl_timeout = "";
}
@@ -3201,6 +3210,10 @@ sub FetchDynamicProfile {
}
(system($cmd) == 0) || error("Failed to get profile: $cmd: $!\n");
+ open(TMPPROF, "$tmp_profile") || error("Cannot open $tmp_profile: $!\n");
+ my $line = <TMPPROF>;
+ close(TMPPROF);
+ $line !~ /^Could not enable CPU profiling/ || error($line);
(system("mv $tmp_profile $real_profile") == 0) || error("Unable to rename profile\n");
print STDERR "Wrote profile to $real_profile\n";
$main::collected_profile = $real_profile;
@@ -3753,15 +3766,19 @@ sub ReadHeapProfile {
} else {
# Remote-heap version 1
my $ratio;
- $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
- if ($ratio < 1) {
- $n1 /= $ratio;
- $s1 /= $ratio;
+ if ($n1 > 0) {
+ $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
+ if ($ratio < 1) {
+ $n1 /= $ratio;
+ $s1 /= $ratio;
+ }
}
- $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
- if ($ratio < 1) {
- $n2 /= $ratio;
- $s2 /= $ratio;
+ if ($n2 > 0) {
+ $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
+ if ($ratio < 1) {
+ $n2 /= $ratio;
+ $s2 /= $ratio;
+ }
}
}
}
@@ -4595,6 +4612,7 @@ sub ConfigureObjTools {
# in the same directory as pprof.
$obj_tool_map{"nm_pdb"} = "nm-pdb";
$obj_tool_map{"addr2line_pdb"} = "addr2line-pdb";
+ $obj_tool_map{"is_windows"} = "true";
}
if ($file_type =~ /Mach-O/) {
@@ -4802,16 +4820,13 @@ sub GetProcedureBoundaries {
" $image 2>/dev/null $cppfilt_flag",
"$nm -D -n $flatten_flag $demangle_flag" .
" $image 2>/dev/null $cppfilt_flag",
- # 6nm is for Go binaries
- "6nm $image 2>/dev/null | sort");
-
- # If the executable is an MS Windows PDB-format executable, we'll
- # have set up obj_tool_map("nm_pdb"). In this case, we actually
- # want to use both unix nm and windows-specific nm_pdb, since
- # PDB-format executables can apparently include dwarf .o files.
- if (exists $obj_tool_map{"nm_pdb"}) {
- my $nm_pdb = $obj_tool_map{"nm_pdb"};
- push(@nm_commands, "$nm_pdb --demangle $image 2>/dev/null");
+ # go tool nm is for Go binaries
+ "go tool nm $image 2>/dev/null | sort");
+
+ # If the executable is an MS Windows Go executable, we'll
+ # have set up obj_tool_map("is_windows").
+ if (exists $obj_tool_map{"is_windows"}) {
+ @nm_commands = ("go tool nm $image 2>/dev/null | sort");
}
foreach my $nm_command (@nm_commands) {
diff --git a/misc/swig/callback/Makefile b/misc/swig/callback/Makefile
deleted file mode 100644
index 0ca33ef60..000000000
--- a/misc/swig/callback/Makefile
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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.
-
-include ../../../src/Make.inc
-
-TARG=swig/callback
-SWIGFILES=\
- callback.swigcxx
-
-CLEANFILES+=run
-
-include ../../../src/Make.pkg
-
-%: install %.go
- $(GC) $(GCFLAGS) $(GCIMPORTS) $*.go
- $(LD) $(SWIG_RPATH) -o $@ $*.$O
diff --git a/misc/swig/callback/callback.go b/misc/swig/callback/callback.go
new file mode 100644
index 000000000..39c1719d2
--- /dev/null
+++ b/misc/swig/callback/callback.go
@@ -0,0 +1,11 @@
+// Copyright 2012 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.
+
+package callback
+
+type GoCallback struct{}
+
+func (p *GoCallback) Run() string {
+ return "GoCallback.Run"
+}
diff --git a/misc/swig/callback/callback_test.go b/misc/swig/callback/callback_test.go
new file mode 100644
index 000000000..cf008fb54
--- /dev/null
+++ b/misc/swig/callback/callback_test.go
@@ -0,0 +1,34 @@
+// Copyright 2012 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.
+
+package callback_test
+
+import (
+ "../callback"
+ "testing"
+)
+
+func TestCall(t *testing.T) {
+ c := callback.NewCaller()
+ cb := callback.NewCallback()
+
+ c.SetCallback(cb)
+ s := c.Call()
+ if s != "Callback::run" {
+ t.Errorf("unexpected string from Call: %q", s)
+ }
+ c.DelCallback()
+}
+
+func TestCallback(t *testing.T) {
+ c := callback.NewCaller()
+ cb := callback.NewDirectorCallback(&callback.GoCallback{})
+ c.SetCallback(cb)
+ s := c.Call()
+ if s != "GoCallback.Run" {
+ t.Errorf("unexpected string from Call with callback: %q", s)
+ }
+ c.DelCallback()
+ callback.DeleteDirectorCallback(cb)
+}
diff --git a/misc/swig/callback/run.go b/misc/swig/callback/run.go
deleted file mode 100644
index b3f13ad90..000000000
--- a/misc/swig/callback/run.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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.
-
-package main
-
-import (
- "fmt"
- "swig/callback"
-)
-
-type GoCallback struct{}
-
-func (p *GoCallback) Run() string {
- return "GoCallback.Run"
-}
-
-func main() {
- c := callback.NewCaller()
- cb := callback.NewCallback()
-
- c.SetCallback(cb)
- s := c.Call()
- fmt.Println(s)
- if s != "Callback::run" {
- panic(s)
- }
- c.DelCallback()
-
- cb = callback.NewDirectorCallback(&GoCallback{})
- c.SetCallback(cb)
- s = c.Call()
- fmt.Println(s)
- if s != "GoCallback.Run" {
- panic(s)
- }
- c.DelCallback()
- callback.DeleteDirectorCallback(cb)
-}
diff --git a/misc/swig/stdio/file.swig b/misc/swig/stdio/file.swig
index 57c623f8f..8ba341d08 100644
--- a/misc/swig/stdio/file.swig
+++ b/misc/swig/stdio/file.swig
@@ -6,6 +6,19 @@
%{
#include <stdio.h>
+#include <stdlib.h>
%}
-int puts(const char *);
+%typemap(gotype) const char * "string"
+%typemap(in) const char * %{
+ $1 = malloc($input.n + 1);
+ memcpy($1, $input.p, $input.n);
+ $1[$input.n] = '\0';
+%}
+%typemap(freearg) const char * %{
+ free($1);
+%}
+
+FILE *fopen(const char *name, const char *mode);
+int fclose(FILE *);
+int fgetc(FILE *);
diff --git a/misc/swig/stdio/file_test.go b/misc/swig/stdio/file_test.go
new file mode 100644
index 000000000..6478a7cf3
--- /dev/null
+++ b/misc/swig/stdio/file_test.go
@@ -0,0 +1,22 @@
+// Copyright 2012 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.
+
+package file
+
+import "testing"
+
+// Open this file itself and verify that the first few characters are
+// as expected.
+func TestRead(t *testing.T) {
+ f := Fopen("file_test.go", "r")
+ if f == nil {
+ t.Fatal("fopen failed")
+ }
+ if Fgetc(f) != '/' || Fgetc(f) != '/' || Fgetc(f) != ' ' || Fgetc(f) != 'C' {
+ t.Error("read unexpected characters")
+ }
+ if Fclose(f) != 0 {
+ t.Error("fclose failed")
+ }
+}
diff --git a/misc/swig/stdio/hello.go b/misc/swig/stdio/hello.go
deleted file mode 100644
index eec294278..000000000
--- a/misc/swig/stdio/hello.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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.
-
-package main
-
-import "swig/file"
-
-func main() {
- file.Puts("Hello, world")
-}
diff --git a/misc/vim/autoload/go/complete.vim b/misc/vim/autoload/go/complete.vim
index d4ae3b97f..cc1013b7d 100644
--- a/misc/vim/autoload/go/complete.vim
+++ b/misc/vim/autoload/go/complete.vim
@@ -29,21 +29,43 @@ if len(s:goarch) == 0
endif
function! go#complete#Package(ArgLead, CmdLine, CursorPos)
- let goroot = $GOROOT
- if len(goroot) == 0
- " should not occur.
- return []
+ let dirs = []
+
+ if executable('go')
+ let goroot = substitute(system('go env GOROOT'), '\n', '', 'g')
+ if v:shell_error
+ echo '\'go env GOROOT\' failed'
+ endif
+ else
+ let goroot = $GOROOT
endif
+
+ if len(goroot) != 0 && isdirectory(goroot)
+ let dirs += [ goroot ]
+ endif
+
+ let workspaces = split($GOPATH, ':')
+ if workspaces != []
+ let dirs += workspaces
+ endif
+
+ if len(dirs) == 0
+ " should not happen
+ return []
+ endif
+
let ret = {}
- let root = expand(goroot.'/pkg/'.s:goos.'_'.s:goarch)
- for i in split(globpath(root, a:ArgLead.'*'), "\n")
- if isdirectory(i)
- let i .= '/'
- elseif i !~ '\.a$'
- continue
- endif
- let i = substitute(substitute(i[len(root)+1:], '[\\]', '/', 'g'), '\.a$', '', 'g')
- let ret[i] = i
+ for dir in dirs
+ let root = expand(dir . '/pkg/' . s:goos . '_' . s:goarch)
+ for i in split(globpath(root, a:ArgLead.'*'), "\n")
+ if isdirectory(i)
+ let i .= '/'
+ elseif i !~ '\.a$'
+ continue
+ endif
+ let i = substitute(substitute(i[len(root)+1:], '[\\]', '/', 'g'), '\.a$', '', 'g')
+ let ret[i] = i
+ endfor
endfor
return sort(keys(ret))
endfunction
diff --git a/misc/vim/ftplugin/go/fmt.vim b/misc/vim/ftplugin/go/fmt.vim
index 0ee44cd59..30814fdfd 100644
--- a/misc/vim/ftplugin/go/fmt.vim
+++ b/misc/vim/ftplugin/go/fmt.vim
@@ -12,6 +12,9 @@
" It tries to preserve cursor position and avoids
" replacing the buffer with stderr output.
"
+if exists("b:did_ftplugin_go_fmt")
+ finish
+endif
command! -buffer Fmt call s:GoFormat()
@@ -41,4 +44,6 @@ function! s:GoFormat()
call winrestview(view)
endfunction
+let b:did_ftplugin_go_fmt = 1
+
" vim:ts=4:sw=4:et
diff --git a/misc/vim/ftplugin/go/godoc.vim b/misc/vim/ftplugin/go/godoc.vim
deleted file mode 100644
index 55195a674..000000000
--- a/misc/vim/ftplugin/go/godoc.vim
+++ /dev/null
@@ -1,13 +0,0 @@
-" 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.
-"
-" godoc.vim: Vim command to see godoc.
-
-if exists("b:did_ftplugin")
- finish
-endif
-
-silent! nmap <buffer> <silent> K <Plug>(godoc-keyword)
-
-" vim:ts=4:sw=4:et
diff --git a/misc/vim/ftplugin/go/import.vim b/misc/vim/ftplugin/go/import.vim
index 6705a476b..1d969e460 100644
--- a/misc/vim/ftplugin/go/import.vim
+++ b/misc/vim/ftplugin/go/import.vim
@@ -12,7 +12,7 @@
" in the current Go buffer, using proper style and ordering.
" If {path} is already being imported, an error will be
" displayed and the buffer will be untouched.
-"
+"
" :ImportAs {localname} {path}
"
" Same as Import, but uses a custom local name for the package.
@@ -32,7 +32,7 @@
" The backslash is the default maplocalleader, so it is possible that
" your vim is set to use a different character (:help maplocalleader).
"
-if exists("b:did_ftplugin")
+if exists("b:did_ftplugin_go_import")
finish
endif
@@ -58,6 +58,12 @@ function! s:SwitchImport(enabled, localname, path)
return
endif
+ " Extract any site prefix (e.g. github.com/).
+ " If other imports with the same prefix are grouped separately,
+ " we will add this new import with them.
+ " Only up to and including the first slash is used.
+ let siteprefix = matchstr(path, "^[^/]*/")
+
let qpath = '"' . path . '"'
if a:localname != ''
let qlocalpath = a:localname . ' ' . qpath
@@ -83,16 +89,31 @@ function! s:SwitchImport(enabled, localname, path)
let appendstr = qlocalpath
let indentstr = 1
let appendline = line
+ let firstblank = -1
+ let lastprefix = ""
while line <= line("$")
let line = line + 1
let linestr = getline(line)
let m = matchlist(getline(line), '^\()\|\(\s\+\)\(\S*\s*\)"\(.\+\)"\)')
if empty(m)
+ if siteprefix == "" && a:enabled
+ " must be in the first group
+ break
+ endif
+ " record this position, but keep looking
+ if firstblank < 0
+ let firstblank = line
+ endif
continue
endif
if m[1] == ')'
+ " if there's no match, add it to the first group
+ if appendline < 0 && firstblank >= 0
+ let appendline = firstblank
+ endif
break
endif
+ let lastprefix = matchstr(m[4], "^[^/]*/")
if a:localname != '' && m[3] != ''
let qlocalpath = printf('%-' . (len(m[3])-1) . 's %s', a:localname, qpath)
endif
@@ -103,7 +124,16 @@ function! s:SwitchImport(enabled, localname, path)
let deleteline = line
break
elseif m[4] < path
- let appendline = line
+ " don't set candidate position if we have a site prefix,
+ " we've passed a blank line, and this doesn't share the same
+ " site prefix.
+ if siteprefix == "" || firstblank < 0 || match(m[4], "^" . siteprefix) >= 0
+ let appendline = line
+ endif
+ elseif siteprefix != "" && match(m[4], "^" . siteprefix) >= 0
+ " first entry of site group
+ let appendline = line - 1
+ break
endif
endwhile
break
@@ -198,4 +228,6 @@ function! s:Error(s)
echohl Error | echo a:s | echohl None
endfunction
+let b:did_ftplugin_go_import = 1
+
" vim:ts=4:sw=4:et
diff --git a/misc/vim/ftplugin/go/test.sh b/misc/vim/ftplugin/go/test.sh
new file mode 100755
index 000000000..a6e31d8a3
--- /dev/null
+++ b/misc/vim/ftplugin/go/test.sh
@@ -0,0 +1,78 @@
+#!/bin/bash -e
+#
+# Copyright 2012 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.
+#
+# Tests for import.vim.
+
+cd $(dirname $0)
+
+cat > base.go <<EOF
+package test
+
+import (
+ "bytes"
+ "io"
+ "net"
+
+ "mycorp/foo"
+)
+EOF
+
+fail=0
+
+# usage: test_one command pattern
+# Pattern is a PCRE expression that will match across lines.
+test_one() {
+ echo 2>&1 -n "$1: "
+ vim -e -s -u /dev/null -U /dev/null --noplugin -c "source import.vim" \
+ -c "$1" -c 'wq! test.go' base.go
+ # ensure blank lines are treated correctly
+ if ! gofmt test.go | cmp test.go; then
+ echo 2>&1 "gofmt conflict"
+ gofmt test.go | diff -u test.go - | sed "s/^/ /" 2>&1
+ fail=1
+ return
+ fi
+ if ! grep -P -q "(?s)$2" test.go; then
+ echo 2>&1 "$2 did not match"
+ cat test.go | sed "s/^/ /" 2>&1
+ fail=1
+ return
+ fi
+ echo 2>&1 "ok"
+}
+
+# Tests for Import
+
+test_one "Import baz" '"baz".*"bytes"'
+test_one "Import io/ioutil" '"io".*"io/ioutil".*"net"'
+test_one "Import myc" '"io".*"myc".*"net"' # prefix of a site prefix
+test_one "Import nat" '"io".*"nat".*"net"'
+test_one "Import net/http" '"net".*"net/http".*"mycorp/foo"'
+test_one "Import zoo" '"net".*"zoo".*"mycorp/foo"'
+test_one "Import mycorp/bar" '"net".*"mycorp/bar".*"mycorp/foo"'
+test_one "Import mycorp/goo" '"net".*"mycorp/foo".*"mycorp/goo"'
+
+# Tests for Drop
+
+cat > base.go <<EOF
+package test
+
+import (
+ "foo"
+
+ "something"
+ "zoo"
+)
+EOF
+
+test_one "Drop something" '\([^"]*"foo"[^"]*"zoo"[^"]*\)'
+
+rm -f base.go test.go
+if [ $fail -gt 0 ]; then
+ echo 2>&1 "FAIL"
+ exit 1
+fi
+echo 2>&1 "PASS"
diff --git a/misc/vim/plugin/godoc.vim b/misc/vim/plugin/godoc.vim
index fdb496631..a9abb7ae6 100644
--- a/misc/vim/plugin/godoc.vim
+++ b/misc/vim/plugin/godoc.vim
@@ -72,7 +72,7 @@ function! s:Godoc(...)
if !len(word)
let word = expand('<cword>')
endif
- let word = substitute(word, '[^a-zA-Z0-9\/]', '', 'g')
+ let word = substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')
if !len(word)
return
endif
diff --git a/misc/vim/readme.txt b/misc/vim/readme.txt
index fe15da993..cb3a52073 100644
--- a/misc/vim/readme.txt
+++ b/misc/vim/readme.txt
@@ -1,9 +1,13 @@
Vim plugins for Go (http://golang.org)
======================================
-To use all the Vim plugins, add these lines to your vimrc.
+To use all the Vim plugins, add these lines to your $HOME/.vimrc.
- set rtp+=$GOROOT/misc/vim
+ " Some Linux distributions set filetype in /etc/vimrc.
+ " Clear filetype flags before changing runtimepath to force Vim to reload them.
+ filetype off
+ filetype plugin indent off
+ set runtimepath+=$GOROOT/misc/vim
filetype plugin indent on
syntax on
@@ -72,5 +76,4 @@ To install godoc plugin:
1. Same as 1 above.
2. Copy or link plugin/godoc.vim to $HOME/.vim/plugin/godoc,
syntax/godoc.vim to $HOME/.vim/syntax/godoc.vim,
- ftplugin/go/godoc.vim to $HOME/.vim/ftplugin/go/godoc.vim.
and autoload/go/complete.vim to $HOME/.vim/autoload/go/complete.vim.
diff --git a/misc/zsh/go b/misc/zsh/go
index 23afa9656..dce25547d 100644
--- a/misc/zsh/go
+++ b/misc/zsh/go
@@ -88,7 +88,7 @@ __go_tool_complete() {
"-cpu[values of GOMAXPROCS to use]:number list" \
"-run[run tests and examples matching regexp]:regexp" \
"-bench[run benchmarks matching regexp]:regexp" \
- "-benchtime[run each benchmark during n seconds]:duration" \
+ "-benchtime[run each benchmark until taking this long]:duration" \
"-timeout[kill test after that duration]:duration" \
"-cpuprofile[write CPU profile to file]:file:_files" \
"-memprofile[write heap profile to file]:file:_files" \