diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
commit | 5ff4c17907d5b19510a62e08fd8d3b11e62b431d (patch) | |
tree | c0650497e988f47be9c6f2324fa692a52dea82e1 /misc | |
parent | 80f18fc933cf3f3e829c5455a1023d69f7b86e52 (diff) | |
download | golang-5ff4c17907d5b19510a62e08fd8d3b11e62b431d.tar.gz |
Imported Upstream version 60upstream/60
Diffstat (limited to 'misc')
104 files changed, 7457 insertions, 0 deletions
diff --git a/misc/IntelliJIDEA/Go.xml b/misc/IntelliJIDEA/Go.xml new file mode 100644 index 000000000..09265a2e0 --- /dev/null +++ b/misc/IntelliJIDEA/Go.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. + +Copy this custom language definition & configuration file to + * Mac : ~/Library/Preferences/IntelliJIdea10/filetypes/ + * Linux & Windows : ~/.IntelliJIdea10/config/filetypes/ +--> + +<filetype binary="false" default_extension="" description="Go" name="Go"> + <highlighting> + <options> + <option name="LINE_COMMENT" value="//"/> + <option name="COMMENT_START" value="/*"/> + <option name="COMMENT_END" value="*/"/> + <option name="HEX_PREFIX" value="0x"/> + <option name="NUM_POSTFIXES" value=""/> + <option name="HAS_BRACKETS" value="true"/> + <option name="HAS_BRACES" value="true"/> + <option name="HAS_PARENS" value="true"/> + <option name="HAS_STRING_ESCAPES" value="true"/> + </options> + <keywords ignore_case="false"> + <keyword name="break"/> + <keyword name="case"/> + <keyword name="chan"/> + <keyword name="const"/> + <keyword name="continue"/> + <keyword name="default"/> + <keyword name="defer"/> + <keyword name="else"/> + <keyword name="fallthrough"/> + <keyword name="for"/> + <keyword name="func"/> + <keyword name="go"/> + <keyword name="goto"/> + <keyword name="if"/> + <keyword name="import"/> + <keyword name="interface"/> + <keyword name="map"/> + <keyword name="package"/> + <keyword name="range"/> + <keyword name="return"/> + <keyword name="select"/> + <keyword name="struct"/> + <keyword name="switch"/> + <keyword name="type"/> + <keyword name="var"/> + </keywords> + <keywords2> + <keyword name="bool"/> + <keyword name="byte"/> + <keyword name="complex64"/> + <keyword name="complex128"/> + <keyword name="float32"/> + <keyword name="float64"/> + <keyword name="int"/> + <keyword name="int8"/> + <keyword name="int16"/> + <keyword name="int32"/> + <keyword name="int64"/> + <keyword name="string"/> + <keyword name="uint"/> + <keyword name="uint8"/> + <keyword name="uint16"/> + <keyword name="uint32"/> + <keyword name="uint64"/> + <keyword name="uintptr"/> + </keywords2> + <keywords3> + <keyword name="append"/> + <keyword name="cap"/> + <keyword name="close"/> + <keyword name="complex"/> + <keyword name="copy"/> + <keyword name="imag"/> + <keyword name="len"/> + <keyword name="make"/> + <keyword name="new"/> + <keyword name="panic"/> + <keyword name="print"/> + <keyword name="println"/> + <keyword name="real"/> + <keyword name="recover"/> + </keywords3> + <keywords4> + <keyword name="false"/> + <keyword name="iota"/> + <keyword name="nil"/> + <keyword name="true"/> + </keywords4> + </highlighting> + <extensionMap> + <mapping ext="go"/> + </extensionMap> +</filetype> diff --git a/misc/arm/a b/misc/arm/a new file mode 100755 index 000000000..701f4941f --- /dev/null +++ b/misc/arm/a @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# 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. + +# This is a small script for executing go binaries on the android platform. +# +# example: +# ./a 5.out foo bar baz +# +# The script exports the local values of GOARCH, GOTRACEBACK and GOGC +# to the android environment. +# +# Known issues: +# The script fails unless the last character output by the program is "\n" +# +# TODO(kaib): add gdb bridge support + +exp () +{ + if [ ${!1} ]; then + echo "export $1=\"${!1}\"; " + fi +} + +# adb does not correctly return the exit value of the executed program. use this +# wrapper to manually extract the exit value +rloc=/data/local/tmp/retval +rsize=$(adb shell "ls -l $rloc"|tr -s ' '|cut -d' ' -f4) +rcheck=38 +if [ "$rsize" != "$rcheck" ]; then +# echo "debug: retval size incorrect want $rcheck, got $rsize. uploading" + echo >/tmp/adb.retval '#!/system/bin/sh +"$@" +echo RETVAL: $?' + adb push /tmp/adb.retval $rloc >/dev/null 2>&1 + adb shell chmod 755 $rloc +fi + +# run the main binary +if [ "-g" == "$1" ]; then + adb forward tcp:$2 tcp:$2 + args=$(echo $*| cut -d' ' -f4-) + adb push $3 /data/local/tmp/$3 >/dev/null 2>&1 + adb shell "$(exp GOARCH) $(exp GOTRACEBACK) $(exp GOGC) \ + gdbserver :$2 /data/local/tmp/retval /data/local/tmp/$3 $args" \ + 2>&1|tr -d '\r' |tee /tmp/adb.out|grep -v RETVAL +else + if [ "$*" != "$1" ]; then + args=$(echo $*| cut -d' ' -f2-) + fi + adb push $1 /data/local/tmp/$1 >/dev/null 2>&1 + adb shell "$(exp GOARCH) $(exp GOTRACEBACK) $(exp GOGC) \ + /data/local/tmp/retval /data/local/tmp/$1 $args" \ + 2>&1|tr -d '\r' |tee /tmp/adb.out|grep -v RETVAL +fi +exit $(grep RETVAL /tmp/adb.out|tr -d '\n\r'| cut -d' ' -f2) diff --git a/misc/bash/go b/misc/bash/go new file mode 100644 index 000000000..caced154f --- /dev/null +++ b/misc/bash/go @@ -0,0 +1,6 @@ +# install in /etc/bash_completion.d/ or your personal directory + +complete -f -X '!*.8' 8l +complete -f -X '!*.6' 6l +complete -f -X '!*.5' 5l +complete -f -X '!*.go' 8g 6g 5g gofmt gccgo diff --git a/misc/bbedit/Go.plist b/misc/bbedit/Go.plist new file mode 100755 index 000000000..45535350a --- /dev/null +++ b/misc/bbedit/Go.plist @@ -0,0 +1,101 @@ +// 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. +{ + BBEditDocumentType = "CodelessLanguageModule"; + BBLMColorsSyntax = YES; + BBLMIsCaseSensitive = YES; + BBLMKeywordList = ( + append, + bool, + break, + byte, + cap, + case, + chan, + close, + complex, + complex128, + complex64, + const, + continue, + copy, + default, + defer, + else, + fallthrough, + false, + float32, + float64, + for, + func, + go, + goto, + if, + iota, + imag, + import, + int, + int16, + int32, + int64, + int8, + interface, + len, + make, + map, + new, + nil, + package, + panic, + print, + println, + range, + real, + recover, + return, + select, + string, + struct, + switch, + true, + type, + uint, + uint16, + uint32, + uint64, + uint8, + uintptr, + var, + ); + BBLMLanguageCode = go; + "BBLMLanguageDisplayName" = "Go"; + BBLMScansFunctions = YES; + BBLMSuffixMap = ( + { + BBLMLanguageSuffix = ".go"; + }, + ); + "Language Features" = { + "Close Block Comments" = "*/"; + "Close Parameter Lists" = ")"; + "Close Statement Blocks" = "}"; + "Close Strings 1" = "`"; + "Close Strings 2" = "\""; + "End-of-line Ends Strings 1" = YES; + "End-of-line Ends Strings 2" = YES; + "Escape Char in Strings 1" = "\\"; + "Escape Char in Strings 2" = "\\"; + "Identifier and Keyword Characters" = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; + "Open Block Comments" = "/*"; + "Open Line Comments" = "//"; + "Open Parameter Lists" = "("; + "Open Statement Blocks" = "{"; + "Open Strings 1" = "`"; + "Open Strings 2" = "\""; + "Prefix for Functions" = "func"; + "Prefix for Procedures" = "func"; + "Terminator for Prototypes 1" = ";"; + "Terminator for Prototypes 2" = ""; + }; +} diff --git a/misc/cgo/gmp/Makefile b/misc/cgo/gmp/Makefile new file mode 100644 index 000000000..fc6209f27 --- /dev/null +++ b/misc/cgo/gmp/Makefile @@ -0,0 +1,38 @@ +# 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. + +include ../../../src/Make.inc + +TARG=gmp + +# Can have plain GOFILES too, but this example doesn't. + +CGOFILES=\ + gmp.go + +CGO_LDFLAGS=-lgmp + +# To add flags necessary for locating the library or its include files, +# set CGO_CFLAGS or CGO_LDFLAGS. For example, to use an +# alternate installation of the library: +# CGO_CFLAGS=-I/home/rsc/gmp32/include +# CGO_LDFLAGS+=-L/home/rsc/gmp32/lib +# Note the += on the second line. + +CLEANFILES+=pi fib + +include ../../../src/Make.pkg + +# Simple test programs + +# Computes 1000 digits of pi; single-threaded. +pi: install pi.go + $(GC) pi.go + $(LD) -o $@ pi.$O + +# Computes 200 Fibonacci numbers; multi-threaded. +fib: install fib.go + $(GC) fib.go + $(LD) -o $@ fib.$O + diff --git a/misc/cgo/gmp/fib.go b/misc/cgo/gmp/fib.go new file mode 100644 index 000000000..3eda39e17 --- /dev/null +++ b/misc/cgo/gmp/fib.go @@ -0,0 +1,43 @@ +// 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. + +// Compute Fibonacci numbers with two goroutines +// that pass integers back and forth. No actual +// concurrency, just threads and synchronization +// and foreign code on multiple pthreads. + +package main + +import ( + big "gmp" + "runtime" +) + +func fibber(c chan *big.Int, out chan string, n int64) { + // Keep the fibbers in dedicated operating system + // threads, so that this program tests coordination + // between pthreads and not just goroutines. + runtime.LockOSThread() + + i := big.NewInt(n) + if n == 0 { + c <- i + } + for { + j := <-c + out <- j.String() + i.Add(i, j) + c <- i + } +} + +func main() { + c := make(chan *big.Int) + out := make(chan string) + go fibber(c, out, 0) + go fibber(c, out, 1) + for i := 0; i < 200; i++ { + println(<-out) + } +} diff --git a/misc/cgo/gmp/gmp.go b/misc/cgo/gmp/gmp.go new file mode 100644 index 000000000..3dbc022ce --- /dev/null +++ b/misc/cgo/gmp/gmp.go @@ -0,0 +1,368 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +An example of wrapping a C library in Go. This is the GNU +multiprecision library gmp's integer type mpz_t wrapped to look like +the Go package big's integer type Int. + +This is a syntactically valid Go program—it can be parsed with the Go +parser and processed by godoc—but it is not compiled directly by 6g. +Instead, a separate tool, cgo, processes it to produce three output +files. The first two, 6g.go and 6c.c, are a Go source file for 6g and +a C source file for 6c; both compile as part of the named package +(gmp, in this example). The third, gcc.c, is a C source file for gcc; +it compiles into a shared object (.so) that is dynamically linked into +any 6.out that imports the first two files. + +The stanza + + // #include <gmp.h> + import "C" + +is a signal to cgo. The doc comment on the import of "C" provides +additional context for the C file. Here it is just a single #include +but it could contain arbitrary C definitions to be imported and used. + +Cgo recognizes any use of a qualified identifier C.xxx and uses gcc to +find the definition of xxx. If xxx is a type, cgo replaces C.xxx with +a Go translation. C arithmetic types translate to precisely-sized Go +arithmetic types. A C struct translates to a Go struct, field by +field; unrepresentable fields are replaced with opaque byte arrays. A +C union translates into a struct containing the first union member and +perhaps additional padding. C arrays become Go arrays. C pointers +become Go pointers. C function pointers become Go's uintptr. +C void pointer's become Go's unsafe.Pointer. + +For example, mpz_t is defined in <gmp.h> as: + + typedef unsigned long int mp_limb_t; + + typedef struct + { + int _mp_alloc; + int _mp_size; + mp_limb_t *_mp_d; + } __mpz_struct; + + typedef __mpz_struct mpz_t[1]; + +Cgo generates: + + type _C_int int32 + type _C_mp_limb_t uint64 + type _C___mpz_struct struct { + _mp_alloc _C_int; + _mp_size _C_int; + _mp_d *_C_mp_limb_t; + } + type _C_mpz_t [1]_C___mpz_struct + +and then replaces each occurrence of a type C.xxx with _C_xxx. + +If xxx is data, cgo arranges for C.xxx to refer to the C variable, +with the type translated as described above. To do this, cgo must +introduce a Go variable that points at the C variable (the linker can +be told to initialize this pointer). For example, if the gmp library +provided + + mpz_t zero; + +then cgo would rewrite a reference to C.zero by introducing + + var _C_zero *C.mpz_t + +and then replacing all instances of C.zero with (*_C_zero). + +Cgo's most interesting translation is for functions. If xxx is a C +function, then cgo rewrites C.xxx into a new function _C_xxx that +calls the C xxx in a standard pthread. The new function translates +its arguments, calls xxx, and translates the return value. + +Translation of parameters and the return value follows the type +translation above except that arrays passed as parameters translate +explicitly in Go to pointers to arrays, as they do (implicitly) in C. + +Garbage collection is the big problem. It is fine for the Go world to +have pointers into the C world and to free those pointers when they +are no longer needed. To help, the Go code can define Go objects +holding the C pointers and use runtime.SetFinalizer on those Go objects. + +It is much more difficult for the C world to have pointers into the Go +world, because the Go garbage collector is unaware of the memory +allocated by C. The most important consideration is not to +constrain future implementations, so the rule is that Go code can +hand a Go pointer to C code but must separately arrange for +Go to hang on to a reference to the pointer until C is done with it. +*/ +package gmp + +// #include <gmp.h> +// #include <stdlib.h> +import "C" + +import ( + "os" + "unsafe" +) + +/* + * one of a kind + */ + +// An Int represents a signed multi-precision integer. +// The zero value for an Int represents the value 0. +type Int struct { + i C.mpz_t + init bool +} + +// NewInt returns a new Int initialized to x. +func NewInt(x int64) *Int { return new(Int).SetInt64(x) } + +// Int promises that the zero value is a 0, but in gmp +// the zero value is a crash. To bridge the gap, the +// init bool says whether this is a valid gmp value. +// doinit initializes z.i if it needs it. This is not inherent +// to FFI, just a mismatch between Go's convention of +// making zero values useful and gmp's decision not to. +func (z *Int) doinit() { + if z.init { + return + } + z.init = true + C.mpz_init(&z.i[0]) +} + +// Bytes returns z's representation as a big-endian byte array. +func (z *Int) Bytes() []byte { + b := make([]byte, (z.Len()+7)/8) + n := C.size_t(len(b)) + C.mpz_export(unsafe.Pointer(&b[0]), &n, 1, 1, 1, 0, &z.i[0]) + return b[0:n] +} + +// Len returns the length of z in bits. 0 is considered to have length 1. +func (z *Int) Len() int { + z.doinit() + return int(C.mpz_sizeinbase(&z.i[0], 2)) +} + +// Set sets z = x and returns z. +func (z *Int) Set(x *Int) *Int { + z.doinit() + C.mpz_set(&z.i[0], &x.i[0]) + return z +} + +// SetBytes interprets b as the bytes of a big-endian integer +// and sets z to that value. +func (z *Int) SetBytes(b []byte) *Int { + z.doinit() + if len(b) == 0 { + z.SetInt64(0) + } else { + C.mpz_import(&z.i[0], C.size_t(len(b)), 1, 1, 1, 0, unsafe.Pointer(&b[0])) + } + return z +} + +// SetInt64 sets z = x and returns z. +func (z *Int) SetInt64(x int64) *Int { + z.doinit() + // TODO(rsc): more work on 32-bit platforms + C.mpz_set_si(&z.i[0], C.long(x)) + return z +} + +// SetString interprets s as a number in the given base +// and sets z to that value. The base must be in the range [2,36]. +// SetString returns an error if s cannot be parsed or the base is invalid. +func (z *Int) SetString(s string, base int) os.Error { + z.doinit() + if base < 2 || base > 36 { + return os.EINVAL + } + p := C.CString(s) + defer C.free(unsafe.Pointer(p)) + if C.mpz_set_str(&z.i[0], p, C.int(base)) < 0 { + return os.EINVAL + } + return nil +} + +// String returns the decimal representation of z. +func (z *Int) String() string { + if z == nil { + return "nil" + } + z.doinit() + p := C.mpz_get_str(nil, 10, &z.i[0]) + s := C.GoString(p) + C.free(unsafe.Pointer(p)) + return s +} + +func (z *Int) destroy() { + if z.init { + C.mpz_clear(&z.i[0]) + } + z.init = false +} + +/* + * arithmetic + */ + +// Add sets z = x + y and returns z. +func (z *Int) Add(x, y *Int) *Int { + x.doinit() + y.doinit() + z.doinit() + C.mpz_add(&z.i[0], &x.i[0], &y.i[0]) + return z +} + +// Sub sets z = x - y and returns z. +func (z *Int) Sub(x, y *Int) *Int { + x.doinit() + y.doinit() + z.doinit() + C.mpz_sub(&z.i[0], &x.i[0], &y.i[0]) + return z +} + +// Mul sets z = x * y and returns z. +func (z *Int) Mul(x, y *Int) *Int { + x.doinit() + y.doinit() + z.doinit() + C.mpz_mul(&z.i[0], &x.i[0], &y.i[0]) + return z +} + +// Div sets z = x / y, rounding toward zero, and returns z. +func (z *Int) Div(x, y *Int) *Int { + x.doinit() + y.doinit() + z.doinit() + C.mpz_tdiv_q(&z.i[0], &x.i[0], &y.i[0]) + return z +} + +// Mod sets z = x % y and returns z. +// Like the result of the Go % operator, z has the same sign as x. +func (z *Int) Mod(x, y *Int) *Int { + x.doinit() + y.doinit() + z.doinit() + C.mpz_tdiv_r(&z.i[0], &x.i[0], &y.i[0]) + return z +} + +// Lsh sets z = x << s and returns z. +func (z *Int) Lsh(x *Int, s uint) *Int { + x.doinit() + z.doinit() + C.mpz_mul_2exp(&z.i[0], &x.i[0], C.mp_bitcnt_t(s)) + return z +} + +// Rsh sets z = x >> s and returns z. +func (z *Int) Rsh(x *Int, s uint) *Int { + x.doinit() + z.doinit() + C.mpz_div_2exp(&z.i[0], &x.i[0], C.mp_bitcnt_t(s)) + return z +} + +// Exp sets z = x^y % m and returns z. +// If m == nil, Exp sets z = x^y. +func (z *Int) Exp(x, y, m *Int) *Int { + m.doinit() + x.doinit() + y.doinit() + z.doinit() + if m == nil { + C.mpz_pow_ui(&z.i[0], &x.i[0], C.mpz_get_ui(&y.i[0])) + } else { + C.mpz_powm(&z.i[0], &x.i[0], &y.i[0], &m.i[0]) + } + return z +} + +func (z *Int) Int64() int64 { + if !z.init { + return 0 + } + return int64(C.mpz_get_si(&z.i[0])) +} + +// Neg sets z = -x and returns z. +func (z *Int) Neg(x *Int) *Int { + x.doinit() + z.doinit() + C.mpz_neg(&z.i[0], &x.i[0]) + return z +} + +// Abs sets z to the absolute value of x and returns z. +func (z *Int) Abs(x *Int) *Int { + x.doinit() + z.doinit() + C.mpz_abs(&z.i[0], &x.i[0]) + return z +} + +/* + * functions without a clear receiver + */ + +// CmpInt compares x and y. The result is +// +// -1 if x < y +// 0 if x == y +// +1 if x > y +// +func CmpInt(x, y *Int) int { + x.doinit() + y.doinit() + switch cmp := C.mpz_cmp(&x.i[0], &y.i[0]); { + case cmp < 0: + return -1 + case cmp == 0: + return 0 + } + return +1 +} + +// DivModInt sets q = x / y and r = x % y. +func DivModInt(q, r, x, y *Int) { + q.doinit() + r.doinit() + x.doinit() + y.doinit() + C.mpz_tdiv_qr(&q.i[0], &r.i[0], &x.i[0], &y.i[0]) +} + +// GcdInt sets d to the greatest common divisor of a and b, +// which must be positive numbers. +// If x and y are not nil, GcdInt sets x and y such that d = a*x + b*y. +// If either a or b is not positive, GcdInt sets d = x = y = 0. +func GcdInt(d, x, y, a, b *Int) { + d.doinit() + x.doinit() + y.doinit() + a.doinit() + b.doinit() + C.mpz_gcdext(&d.i[0], &x.i[0], &y.i[0], &a.i[0], &b.i[0]) +} + +// ProbablyPrime performs n Miller-Rabin tests to check whether z is prime. +// If it returns true, z is prime with probability 1 - 1/4^n. +// If it returns false, z is not prime. +func (z *Int) ProbablyPrime(n int) bool { + z.doinit() + return int(C.mpz_probab_prime_p(&z.i[0], C.int(n))) > 0 +} diff --git a/misc/cgo/gmp/pi.go b/misc/cgo/gmp/pi.go new file mode 100644 index 000000000..45f61abbd --- /dev/null +++ b/misc/cgo/gmp/pi.go @@ -0,0 +1,104 @@ +/* +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of "The Computer Language Benchmarks Game" nor the + name of "The Computer Language Shootout Benchmarks" nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +/* The Computer Language Benchmarks Game + * http://shootout.alioth.debian.org/ + * + * contributed by The Go Authors. + * based on pidigits.c (by Paolo Bonzini & Sean Bartlett, + * modified by Michael Mellor) + */ + +package main + +import ( + big "gmp" + "fmt" + "runtime" +) + +var ( + tmp1 = big.NewInt(0) + tmp2 = big.NewInt(0) + numer = big.NewInt(1) + accum = big.NewInt(0) + denom = big.NewInt(1) + ten = big.NewInt(10) +) + +func extractDigit() int64 { + if big.CmpInt(numer, accum) > 0 { + return -1 + } + tmp1.Lsh(numer, 1).Add(tmp1, numer).Add(tmp1, accum) + big.DivModInt(tmp1, tmp2, tmp1, denom) + tmp2.Add(tmp2, numer) + if big.CmpInt(tmp2, denom) >= 0 { + return -1 + } + return tmp1.Int64() +} + +func nextTerm(k int64) { + y2 := k*2 + 1 + accum.Add(accum, tmp1.Lsh(numer, 1)) + accum.Mul(accum, tmp1.SetInt64(y2)) + numer.Mul(numer, tmp1.SetInt64(k)) + denom.Mul(denom, tmp1.SetInt64(y2)) +} + +func eliminateDigit(d int64) { + accum.Sub(accum, tmp1.Mul(denom, tmp1.SetInt64(d))) + accum.Mul(accum, ten) + numer.Mul(numer, ten) +} + +func main() { + i := 0 + k := int64(0) + for { + d := int64(-1) + for d < 0 { + k++ + nextTerm(k) + d = extractDigit() + } + eliminateDigit(d) + fmt.Printf("%c", d+'0') + + if i++; i%50 == 0 { + fmt.Printf("\n") + if i >= 1000 { + break + } + } + } + + fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.Cgocalls(), numer.Len(), accum.Len(), denom.Len()) +} diff --git a/misc/cgo/life/Makefile b/misc/cgo/life/Makefile new file mode 100644 index 000000000..5a10380ed --- /dev/null +++ b/misc/cgo/life/Makefile @@ -0,0 +1,21 @@ +# 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. + +include ../../../src/Make.inc + +TARG=life + +CGOFILES=\ + life.go\ + +CGO_OFILES=\ + c-life.o\ + +CLEANFILES+=life + +include ../../../src/Make.pkg + +life: install main.go + $(GC) main.go + $(LD) -o $@ main.$O diff --git a/misc/cgo/life/c-life.c b/misc/cgo/life/c-life.c new file mode 100644 index 000000000..657245595 --- /dev/null +++ b/misc/cgo/life/c-life.c @@ -0,0 +1,56 @@ +// 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. + +#include <assert.h> +#include "life.h" +#include "_cgo_export.h" + +const int MYCONST = 0; + +// Do the actual manipulation of the life board in C. This could be +// done easily in Go, we are just using C for demonstration +// purposes. +void +Step(int x, int y, int *a, int *n) +{ + struct GoStart_return r; + + // Use Go to start 4 goroutines each of which handles 1/4 of the + // board. + r = GoStart(0, x, y, 0, x / 2, 0, y / 2, a, n); + assert(r.r0 == 0 && r.r1 == 100); // test multiple returns + r = GoStart(1, x, y, x / 2, x, 0, y / 2, a, n); + assert(r.r0 == 1 && r.r1 == 101); // test multiple returns + GoStart(2, x, y, 0, x / 2, y / 2, y, a, n); + GoStart(3, x, y, x / 2, x, y / 2, y, a, n); + GoWait(0); + GoWait(1); + GoWait(2); + GoWait(3); +} + +// The actual computation. This is called in parallel. +void +DoStep(int xdim, int ydim, int xstart, int xend, int ystart, int yend, int *a, int *n) +{ + int x, y, c, i, j; + + for(x = xstart; x < xend; x++) { + for(y = ystart; y < yend; y++) { + c = 0; + for(i = -1; i <= 1; i++) { + for(j = -1; j <= 1; j++) { + if(x+i >= 0 && x+i < xdim && + y+j >= 0 && y+j < ydim && + (i != 0 || j != 0)) + c += a[(x+i)*xdim + (y+j)] != 0; + } + } + if(c == 3 || (c == 2 && a[x*xdim + y] != 0)) + n[x*xdim + y] = 1; + else + n[x*xdim + y] = 0; + } + } +} diff --git a/misc/cgo/life/golden.out b/misc/cgo/life/golden.out new file mode 100644 index 000000000..539d2106d --- /dev/null +++ b/misc/cgo/life/golden.out @@ -0,0 +1,17 @@ +* life + + + XXX XXX + + + + + + + + XXX XXX + + + + + diff --git a/misc/cgo/life/life.go b/misc/cgo/life/life.go new file mode 100644 index 000000000..ec000ce3a --- /dev/null +++ b/misc/cgo/life/life.go @@ -0,0 +1,39 @@ +// 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. + +package life + +// #include "life.h" +import "C" + +import "unsafe" + +func Run(gen, x, y int, a []int) { + n := make([]int, 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) + } +} + +// Keep the channels visible from Go. +var chans [4]chan bool + +//export GoStart +// Double return value is just for testing. +func GoStart(i, xdim, ydim, xstart, xend, ystart, yend C.int, a *C.int, n *C.int) (int, int) { + c := make(chan bool, int(C.MYCONST)) + go func() { + C.DoStep(xdim, ydim, xstart, xend, ystart, yend, a, n) + c <- true + }() + chans[i] = c + return int(i), int(i + 100) +} + +//export GoWait +func GoWait(i C.int) { + <-chans[i] + chans[i] = nil +} diff --git a/misc/cgo/life/life.h b/misc/cgo/life/life.h new file mode 100644 index 000000000..b2011b25f --- /dev/null +++ b/misc/cgo/life/life.h @@ -0,0 +1,7 @@ +// 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. + +extern void Step(int, int, int *, int *); +extern void DoStep(int, int, int, int, int, int, int *, int *); +extern const int MYCONST; diff --git a/misc/cgo/life/main.go b/misc/cgo/life/main.go new file mode 100644 index 000000000..9cfed434b --- /dev/null +++ b/misc/cgo/life/main.go @@ -0,0 +1,44 @@ +// 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. + +// Run the game of life in C using Go for parallelization. + +package main + +import ( + "flag" + "fmt" + "life" +) + +const MAXDIM = 100 + +var dim = flag.Int("dim", 16, "board dimensions") +var gen = flag.Int("gen", 10, "generations") + +func main() { + flag.Parse() + + var a [MAXDIM * MAXDIM]int + for i := 2; i < *dim; i += 8 { + for j := 2; j < *dim-3; j += 8 { + for y := 0; y < 3; y++ { + a[i**dim+j+y] = 1 + } + } + } + + life.Run(*gen, *dim, *dim, a[:]) + + for i := 0; i < *dim; i++ { + for j := 0; j < *dim; j++ { + if a[i**dim+j] == 0 { + fmt.Print(" ") + } else { + fmt.Print("X") + } + } + fmt.Print("\n") + } +} diff --git a/misc/cgo/life/test.bash b/misc/cgo/life/test.bash new file mode 100755 index 000000000..5c5fba1a9 --- /dev/null +++ b/misc/cgo/life/test.bash @@ -0,0 +1,11 @@ +#!/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 +gomake life +echo '*' life >run.out +./life >>run.out +diff run.out golden.out +gomake clean diff --git a/misc/cgo/stdio/Makefile b/misc/cgo/stdio/Makefile new file mode 100644 index 000000000..3f7a4c01c --- /dev/null +++ b/misc/cgo/stdio/Makefile @@ -0,0 +1,17 @@ +# 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. + +include ../../../src/Make.inc + +TARG=stdio +CGOFILES=\ + file.go\ + +CLEANFILES+=hello fib chain run.out + +include ../../../src/Make.pkg + +%: install %.go + $(GC) $*.go + $(LD) -o $@ $*.$O diff --git a/misc/cgo/stdio/chain.go b/misc/cgo/stdio/chain.go new file mode 100644 index 000000000..c188b2dd9 --- /dev/null +++ b/misc/cgo/stdio/chain.go @@ -0,0 +1,43 @@ +// 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. + +// Pass numbers along a chain of threads. + +package main + +import ( + "runtime" + "stdio" + "strconv" +) + +const N = 10 +const R = 5 + +func link(left chan<- int, right <-chan int) { + // Keep the links in dedicated operating system + // threads, so that this program tests coordination + // between pthreads and not just goroutines. + runtime.LockOSThread() + for { + v := <-right + stdio.Stdout.WriteString(strconv.Itoa(v) + "\n") + left <- 1 + v + } +} + +func main() { + leftmost := make(chan int) + var left chan int + right := leftmost + for i := 0; i < N; i++ { + left, right = right, make(chan int) + go link(left, right) + } + for i := 0; i < R; i++ { + right <- 0 + x := <-leftmost + stdio.Stdout.WriteString(strconv.Itoa(x) + "\n") + } +} diff --git a/misc/cgo/stdio/fib.go b/misc/cgo/stdio/fib.go new file mode 100644 index 000000000..c02e31fd8 --- /dev/null +++ b/misc/cgo/stdio/fib.go @@ -0,0 +1,47 @@ +// 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. + +// Compute Fibonacci numbers with two goroutines +// that pass integers back and forth. No actual +// concurrency, just threads and synchronization +// and foreign code on multiple pthreads. + +package main + +import ( + "runtime" + "stdio" + "strconv" +) + +func fibber(c, out chan int64, i int64) { + // Keep the fibbers in dedicated operating system + // threads, so that this program tests coordination + // between pthreads and not just goroutines. + runtime.LockOSThread() + + if i == 0 { + c <- i + } + for { + j := <-c + stdio.Stdout.WriteString(strconv.Itoa64(j) + "\n") + out <- j + <-out + i += j + c <- i + } +} + +func main() { + c := make(chan int64) + out := make(chan int64) + go fibber(c, out, 0) + go fibber(c, out, 1) + <-out + for i := 0; i < 90; i++ { + out <- 1 + <-out + } +} diff --git a/misc/cgo/stdio/file.go b/misc/cgo/stdio/file.go new file mode 100644 index 000000000..ab1e88436 --- /dev/null +++ b/misc/cgo/stdio/file.go @@ -0,0 +1,45 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +A trivial example of wrapping a C library in Go. +For a more complex example and explanation, +see ../gmp/gmp.go. +*/ + +package stdio + +/* +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <errno.h> + +char* greeting = "hello, world"; +*/ +import "C" +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 myerr = C.sys_errlist + +func (f *File) WriteString(s string) { + p := C.CString(s) + C.fputs(p, (*C.FILE)(f)) + C.free(unsafe.Pointer(p)) + f.Flush() +} + +func (f *File) Flush() { + C.fflush((*C.FILE)(f)) +} + +var Greeting = C.GoString(C.greeting) +var Gbytes = C.GoBytes(unsafe.Pointer(C.greeting), C.int(len(Greeting))) diff --git a/misc/cgo/stdio/golden.out b/misc/cgo/stdio/golden.out new file mode 100644 index 000000000..c0e496547 --- /dev/null +++ b/misc/cgo/stdio/golden.out @@ -0,0 +1,150 @@ +* hello +hello, world +* fib +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 +* chain +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/hello.go b/misc/cgo/stdio/hello.go new file mode 100644 index 000000000..58fc6d574 --- /dev/null +++ b/misc/cgo/stdio/hello.go @@ -0,0 +1,11 @@ +// 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 main + +import "stdio" + +func main() { + stdio.Stdout.WriteString(stdio.Greeting + "\n") +} diff --git a/misc/cgo/stdio/test.bash b/misc/cgo/stdio/test.bash new file mode 100755 index 000000000..82e3f7b45 --- /dev/null +++ b/misc/cgo/stdio/test.bash @@ -0,0 +1,15 @@ +#!/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 +gomake hello fib chain +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 +gomake clean diff --git a/misc/cgo/test/Makefile b/misc/cgo/test/Makefile new file mode 100644 index 000000000..d4309be3c --- /dev/null +++ b/misc/cgo/test/Makefile @@ -0,0 +1,26 @@ +# 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\ + issue1222.go\ + issue1328.go\ + issue1560.go\ + duplicate_symbol.go\ + +CGO_OFILES=\ + callback_c.o\ + +OFILES=\ + runtime.$O\ + +include ../../../src/Make.pkg diff --git a/misc/cgo/test/align.go b/misc/cgo/test/align.go new file mode 100644 index 000000000..07ab9ef50 --- /dev/null +++ b/misc/cgo/test/align.go @@ -0,0 +1,72 @@ +package cgotest + +/* +#include <stdio.h> + +typedef unsigned char Uint8; +typedef unsigned short Uint16; + +typedef enum { + MOD1 = 0x0000, + MODX = 0x8000 +} SDLMod; + +typedef enum { + A = 1, + B = 322, + SDLK_LAST +} SDLKey; + +typedef struct SDL_keysym { + Uint8 scancode; + SDLKey sym; + SDLMod mod; + Uint16 unicode; +} SDL_keysym; + +typedef struct SDL_KeyboardEvent { + Uint8 typ; + Uint8 which; + Uint8 state; + SDL_keysym keysym; +} SDL_KeyboardEvent; + +void makeEvent(SDL_KeyboardEvent *event) { + unsigned char *p; + int i; + + p = (unsigned char*)event; + for (i=0; i<sizeof *event; i++) { + p[i] = i; + } +} + +int same(SDL_KeyboardEvent* e, Uint8 typ, Uint8 which, Uint8 state, Uint8 scan, SDLKey sym, SDLMod mod, Uint16 uni) { + return e->typ == typ && e->which == which && e->state == state && e->keysym.scancode == scan && e->keysym.sym == sym && e->keysym.mod == mod && e->keysym.unicode == uni; +} + +void cTest(SDL_KeyboardEvent *event) { + printf("C: %#x %#x %#x %#x %#x %#x %#x\n", event->typ, event->which, event->state, + event->keysym.scancode, event->keysym.sym, event->keysym.mod, event->keysym.unicode); + fflush(stdout); +} + +*/ +import "C" + +import ( + "testing" +) + +func testAlign(t *testing.T) { + var evt C.SDL_KeyboardEvent + C.makeEvent(&evt) + if C.same(&evt, evt.typ, evt.which, evt.state, evt.keysym.scancode, evt.keysym.sym, evt.keysym.mod, evt.keysym.unicode) == 0 { + t.Error("*** bad alignment") + C.cTest(&evt) + t.Errorf("Go: %#x %#x %#x %#x %#x %#x %#x\n", + evt.typ, evt.which, evt.state, evt.keysym.scancode, + evt.keysym.sym, evt.keysym.mod, evt.keysym.unicode) + t.Error(evt) + } +} diff --git a/misc/cgo/test/basic.go b/misc/cgo/test/basic.go new file mode 100644 index 000000000..b9d0953bd --- /dev/null +++ b/misc/cgo/test/basic.go @@ -0,0 +1,134 @@ +// 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. + +// Basic test cases for cgo. + +package cgotest + +/* +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <errno.h> + +#define SHIFT(x, y) ((x)<<(y)) +#define KILO SHIFT(1, 10) + +enum E { + Enum1 = 1, + Enum2 = 2, +}; + +typedef unsigned char uuid_t[20]; + +void uuid_generate(uuid_t x) { + x[0] = 0; +} + +struct S { + int x; +}; + +extern enum E myConstFunc(struct S* const ctx, int const id, struct S **const filter); + +enum E myConstFunc(struct S *const ctx, int const id, struct S **const filter) { return 0; } + +// issue 1222 +typedef union { + long align; +} xxpthread_mutex_t; + +struct ibv_async_event { + union { + int x; + } element; +}; + +struct ibv_context { + xxpthread_mutex_t mutex; +}; +*/ +import "C" +import ( + "os" + "testing" + "unsafe" +) + +const EINVAL = C.EINVAL /* test #define */ + +var KILO = C.KILO + +func uuidgen() { + var uuid C.uuid_t + C.uuid_generate(&uuid[0]) +} + +func Size(name string) (int64, os.Error) { + var st C.struct_stat + p := C.CString(name) + _, err := C.stat(p, &st) + C.free(unsafe.Pointer(p)) + if err != nil { + return 0, err + } + return int64(C.ulong(st.st_size)), nil +} + +func Strtol(s string, base int) (int, os.Error) { + p := C.CString(s) + n, err := C.strtol(p, nil, C.int(base)) + C.free(unsafe.Pointer(p)) + return int(n), err +} + +func Atol(s string) int { + p := C.CString(s) + n := C.atol(p) + C.free(unsafe.Pointer(p)) + return int(n) +} + +func testConst(t *testing.T) { + C.myConstFunc(nil, 0, nil) +} + +func testEnum(t *testing.T) { + if C.Enum1 != 1 || C.Enum2 != 2 { + t.Error("bad enum", C.Enum1, C.Enum2) + } +} + +func testAtol(t *testing.T) { + l := Atol("123") + if l != 123 { + t.Error("Atol 123: ", l) + } +} + +func testErrno(t *testing.T) { + n, err := Strtol("asdf", 123) + if n != 0 || err != os.EINVAL { + t.Error("Strtol: ", n, err) + } +} + +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 { + t.Fatal("Strtol x2: ", n, m) + } + C.free(unsafe.Pointer(p)) +} + +var ( + uint = (C.uint)(0) + ulong C.ulong + char C.char +) + +type Context struct { + ctx *C.struct_ibv_context +} diff --git a/misc/cgo/test/callback.go b/misc/cgo/test/callback.go new file mode 100644 index 000000000..3edee9758 --- /dev/null +++ b/misc/cgo/test/callback.go @@ -0,0 +1,136 @@ +package cgotest + +/* +void callback(void *f); +void callGoFoo(void) { + extern void goFoo(void); + goFoo(); +} +*/ +import "C" + +import ( + "runtime" + "testing" + "unsafe" +) + +// nestedCall calls into C, back into Go, and finally to f. +func nestedCall(f func()) { + // NOTE: Depends on representation of f. + // callback(x) calls goCallback(x) + C.callback(*(*unsafe.Pointer)(unsafe.Pointer(&f))) +} + +//export goCallback +func goCallback(p unsafe.Pointer) { + (*(*func())(unsafe.Pointer(&p)))() +} + +func testCallback(t *testing.T) { + var x = false + nestedCall(func() { x = true }) + if !x { + t.Fatal("nestedCall did not call func") + } +} + +func testCallbackGC(t *testing.T) { + nestedCall(runtime.GC) +} + +func lockedOSThread() bool // in runtime.c + +func testCallbackPanic(t *testing.T) { + // Make sure panic during callback unwinds properly. + if lockedOSThread() { + t.Fatal("locked OS thread on entry to TestCallbackPanic") + } + defer func() { + s := recover() + if s == nil { + t.Fatal("did not panic") + } + if s.(string) != "callback panic" { + t.Fatal("wrong panic:", s) + } + if lockedOSThread() { + t.Fatal("locked OS thread on exit from TestCallbackPanic") + } + }() + nestedCall(func() { panic("callback panic") }) + panic("nestedCall returned") +} + +func testCallbackPanicLoop(t *testing.T) { + // Make sure we don't blow out m->g0 stack. + for i := 0; i < 100000; i++ { + TestCallbackPanic(t) + } +} + +func testCallbackPanicLocked(t *testing.T) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if !lockedOSThread() { + t.Fatal("runtime.LockOSThread didn't") + } + defer func() { + s := recover() + if s == nil { + t.Fatal("did not panic") + } + if s.(string) != "callback panic" { + t.Fatal("wrong panic:", s) + } + if !lockedOSThread() { + t.Fatal("lost lock on OS thread after panic") + } + }() + nestedCall(func() { panic("callback panic") }) + panic("nestedCall returned") +} + +// Callback with zero arguments used to make the stack misaligned, +// which broke the garbage collector and other things. +func testZeroArgCallback(t *testing.T) { + defer func() { + s := recover() + if s != nil { + t.Fatal("panic during callback:", s) + } + }() + C.callGoFoo() +} + +//export goFoo +func goFoo() { + x := 1 + for i := 0; i < 10000; i++ { + // variadic call mallocs + writes to + variadic(x, x, x) + if x != 1 { + panic("bad x") + } + } +} + +func variadic(x ...interface{}) {} + +func testBlocking(t *testing.T) { + c := make(chan int) + go func() { + for i := 0; i < 10; i++ { + c <- <-c + } + }() + nestedCall(func() { + for i := 0; i < 10; i++ { + c <- i + if j := <-c; j != i { + t.Errorf("out of sync %d != %d", j, i) + } + } + }) +} diff --git a/misc/cgo/test/callback_c.c b/misc/cgo/test/callback_c.c new file mode 100644 index 000000000..5983a5e11 --- /dev/null +++ b/misc/cgo/test/callback_c.c @@ -0,0 +1,12 @@ +// 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 <sys/types.h> +#include "_cgo_export.h" + +void +callback(void *f) +{ + goCallback(f); +} diff --git a/misc/cgo/test/cgo_test.go b/misc/cgo/test/cgo_test.go new file mode 100644 index 000000000..94fba15db --- /dev/null +++ b/misc/cgo/test/cgo_test.go @@ -0,0 +1,28 @@ +// 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 cgotest + +import "testing" + +// 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. + +func TestAlign(t *testing.T) { testAlign(t) } +func TestConst(t *testing.T) { testConst(t) } +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 TestCallback(t *testing.T) { testCallback(t) } +func TestCallbackGC(t *testing.T) { testCallbackGC(t) } +func TestCallbackPanic(t *testing.T) { testCallbackPanic(t) } +func TestCallbackPanicLoop(t *testing.T) { testCallbackPanicLoop(t) } +func TestCallbackPanicLocked(t *testing.T) { testCallbackPanicLocked(t) } +func TestZeroArgCallback(t *testing.T) { testZeroArgCallback(t) } +func TestBlocking(t *testing.T) { testBlocking(t) } +func Test1328(t *testing.T) { test1328(t) } +func TestParallelSleep(t *testing.T) { testParallelSleep(t) } +func TestSetEnv(t *testing.T) { testSetEnv(t) } diff --git a/misc/cgo/test/duplicate_symbol.go b/misc/cgo/test/duplicate_symbol.go new file mode 100644 index 000000000..69600de9c --- /dev/null +++ b/misc/cgo/test/duplicate_symbol.go @@ -0,0 +1,21 @@ +// 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. + +// This file contains test cases for cgo. + +package cgotest + +/* +int base_symbol = 0; + +#define alias_one base_symbol +#define alias_two base_symbol +*/ +import "C" + +import "fmt" + +func duplicateSymbols() { + fmt.Printf("%v %v %v\n", C.base_symbol, C.alias_one, C.alias_two) +} diff --git a/misc/cgo/test/env.go b/misc/cgo/test/env.go new file mode 100644 index 000000000..1fb4e684c --- /dev/null +++ b/misc/cgo/test/env.go @@ -0,0 +1,32 @@ +// 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 cgotest + +/* +#include <stdlib.h> +*/ +import "C" +import ( + "os" + "testing" + "unsafe" +) + +// This is really an os package test but here for convenience. +func testSetEnv(t *testing.T) { + const key = "CGO_OS_TEST_KEY" + const val = "CGO_OS_TEST_VALUE" + os.Setenv(key, val) + keyc := C.CString(key) + defer C.free(unsafe.Pointer(keyc)) + v := C.getenv(keyc) + if v == (*C.char)(unsafe.Pointer(uintptr(0))) { + t.Fatal("getenv returned NULL") + } + vs := C.GoString(v) + if vs != val { + t.Fatalf("getenv() = %q; want %q", vs, val) + } +} diff --git a/misc/cgo/test/exports.go b/misc/cgo/test/exports.go new file mode 100644 index 000000000..f96c60b00 --- /dev/null +++ b/misc/cgo/test/exports.go @@ -0,0 +1,12 @@ +// 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 cgotest + +import "C" + +//export ReturnIntLong +func ReturnIntLong() (int, C.long) { + return 1, 2 +} diff --git a/misc/cgo/test/issue1222.go b/misc/cgo/test/issue1222.go new file mode 100644 index 000000000..c396a0c41 --- /dev/null +++ b/misc/cgo/test/issue1222.go @@ -0,0 +1,29 @@ +// 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. + +// This file contains test cases for cgo. + +package cgotest + +/* +// issue 1222 +typedef union { + long align; +} xxpthread_mutex_t; + +struct ibv_async_event { + union { + int x; + } element; +}; + +struct ibv_context { + xxpthread_mutex_t mutex; +}; +*/ +import "C" + +type AsyncEvent struct { + event C.struct_ibv_async_event +} diff --git a/misc/cgo/test/issue1328.go b/misc/cgo/test/issue1328.go new file mode 100644 index 000000000..e01207dd9 --- /dev/null +++ b/misc/cgo/test/issue1328.go @@ -0,0 +1,30 @@ +// 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 cgotest + +import "testing" + +// extern void BackIntoGo(void); +// void IntoC() { BackIntoGo(); } +import "C" + +//export BackIntoGo +func BackIntoGo() { + x := 1 + + for i := 0; i < 10000; i++ { + xvariadic(x) + if x != 1 { + panic("x is not 1?") + } + } +} + +func xvariadic(x ...interface{}) { +} + +func test1328(t *testing.T) { + C.IntoC() +} diff --git a/misc/cgo/test/issue1560.go b/misc/cgo/test/issue1560.go new file mode 100644 index 000000000..e534cce47 --- /dev/null +++ b/misc/cgo/test/issue1560.go @@ -0,0 +1,46 @@ +// 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 cgotest + +/* +#include <unistd.h> + +extern void BackgroundSleep(int); +void twoSleep(int n) { + BackgroundSleep(n); + sleep(n); +} +*/ +import "C" + +import ( + "testing" + "time" +) + +var sleepDone = make(chan bool) + +func parallelSleep(n int) { + C.twoSleep(C.int(n)) + <-sleepDone +} + +//export BackgroundSleep +func BackgroundSleep(n int) { + go func() { + C.sleep(C.uint(n)) + sleepDone <- true + }() +} + +func testParallelSleep(t *testing.T) { + dt := -time.Nanoseconds() + parallelSleep(1) + dt += time.Nanoseconds() + // bug used to run sleeps in serial, producing a 2-second delay. + if dt >= 1.3e9 { + t.Fatalf("parallel 1-second sleeps slept for %f seconds", float64(dt)/1e9) + } +} diff --git a/misc/cgo/test/runtime.c b/misc/cgo/test/runtime.c new file mode 100644 index 000000000..e087c7622 --- /dev/null +++ b/misc/cgo/test/runtime.c @@ -0,0 +1,21 @@ +// 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. + +// Expose some runtime functions for testing. + +typedef char bool; + +bool runtime·lockedOSThread(void); + +static void +FLUSH(void*) +{ +} + +void +·lockedOSThread(bool b) +{ + b = runtime·lockedOSThread(); + FLUSH(&b); +} diff --git a/misc/chrome/gophertool/README.txt b/misc/chrome/gophertool/README.txt new file mode 100644 index 000000000..a7c0b4b26 --- /dev/null +++ b/misc/chrome/gophertool/README.txt @@ -0,0 +1,8 @@ +To install: + +1) chrome://extensions/ +2) click "[+] Developer Mode" in top right +3) "Load unpacked extension..." +4) pick $GOROOT/misc/chrome/gophertool + +Done. It'll now auto-reload from source. diff --git a/misc/chrome/gophertool/background.html b/misc/chrome/gophertool/background.html new file mode 100644 index 000000000..058c18142 --- /dev/null +++ b/misc/chrome/gophertool/background.html @@ -0,0 +1,24 @@ +<html> +<!-- + Copyright 2011 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +--> +<head> +<script src="gopher.js"></script> +<script> + +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> +</head> +</html> + diff --git a/misc/chrome/gophertool/gopher.js b/misc/chrome/gophertool/gopher.js new file mode 100644 index 000000000..847c1c70d --- /dev/null +++ b/misc/chrome/gophertool/gopher.js @@ -0,0 +1,34 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +var numericRE = /^\d+$/; +var commitRE = /^(?:\d+:)?([0-9a-f]{6,20})$/; // e.g "8486:ab29d2698a47" or "ab29d2698a47" +var pkgRE = /^[a-z0-9_\/]+$/; + +function urlForInput(t) { + if (!t) { + return null; + } + + if (numericRE.test(t)) { + if (t < 1000000) { + return "http://code.google.com/p/go/issues/detail?id=" + t; + } + return "http://codereview.appspot.com/" + t + "/"; + } + + var match = commitRE.exec(t); + if (match) { + return "http://code.google.com/p/go/source/detail?r=" + match[1]; + } + + if (pkgRE.test(t)) { + // TODO: make this smarter, using a list of packages + substring matches. + // Get the list from godoc itself in JSON format? + // TODO: prefer localhost:6060 to golang.org if localhost:6060 is responding. + return "http://golang.org/pkg/" + t; + } + + return null; +} diff --git a/misc/chrome/gophertool/gopher.png b/misc/chrome/gophertool/gopher.png Binary files differnew file mode 100644 index 000000000..0d1abb741 --- /dev/null +++ b/misc/chrome/gophertool/gopher.png diff --git a/misc/chrome/gophertool/manifest.json b/misc/chrome/gophertool/manifest.json new file mode 100644 index 000000000..3a2540a86 --- /dev/null +++ b/misc/chrome/gophertool/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "Hacking Gopher", + "version": "1.0", + "description": "Go Hacking utility", + "background_page": "background.html", + "browser_action": { + "default_icon": "gopher.png", + "popup": "popup.html" + }, + "omnibox": { "keyword": "golang" }, + "icons": { + "16": "gopher.png" + }, + "permissions": [ + "tabs" + ] +} diff --git a/misc/chrome/gophertool/popup.html b/misc/chrome/gophertool/popup.html new file mode 100644 index 000000000..ebbc71f3a --- /dev/null +++ b/misc/chrome/gophertool/popup.html @@ -0,0 +1,54 @@ +<html> +<!-- + Copyright 2011 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +--> +<head> +<script src="gopher.js"></script> +<script> + +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> +</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://godashboard.appspot.com/')">buildbots</small> +</body> +</html> diff --git a/misc/dashboard/README b/misc/dashboard/README new file mode 100644 index 000000000..c00311ef7 --- /dev/null +++ b/misc/dashboard/README @@ -0,0 +1,26 @@ +// 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. + +The files in this directory constitute the continuous builder: + +godashboard/: an AppEngine server +builder/: gobuilder, a Go continuous build client + +If you wish to run a Go builder, please email golang-dev@googlegroups.com + +To run a builder: + +* Write the key ~gobuild/.gobuildkey + You need to get it from someone who knows the key. + You may also use a filename of the form .gobuildkey-$BUILDER if you + wish to run builders for multiple targets. + +* Append your username and password googlecode.com credentials from + https://code.google.com/hosting/settings + to the buildkey file in the format "Username\nPassword\n". + (This is for uploading tarballs to the project downloads section, + and is an optional step.) + +* Build and run gobuilder (see its documentation for command-line options). + diff --git a/misc/dashboard/builder/Makefile b/misc/dashboard/builder/Makefile new file mode 100644 index 000000000..f1d9c5497 --- /dev/null +++ b/misc/dashboard/builder/Makefile @@ -0,0 +1,14 @@ +# 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. + +include ../../../src/Make.inc + +TARG=gobuilder +GOFILES=\ + exec.go\ + http.go\ + main.go\ + package.go\ + +include ../../../src/Make.cmd diff --git a/misc/dashboard/builder/doc.go b/misc/dashboard/builder/doc.go new file mode 100644 index 000000000..30d8fe948 --- /dev/null +++ b/misc/dashboard/builder/doc.go @@ -0,0 +1,58 @@ +// 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. + +/* + +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. + +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. + +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 +Go Google Code project's downloads section. + +Usage: + + gobuilder goos-goarch... + + Several goos-goarch combinations can be provided, and the builder will + build them in serial. + +Optional flags: + + -dashboard="godashboard.appspot.com": Go Dashboard Host + The location of the Go Dashboard application to which Go Builder will + report its results. + + -release: Build and deliver binary release archive + + -rev=N: Build revision N and exit + + -cmd="./all.bash": Build command (specify absolute or relative to go/src) + + -v: Verbose logging + + -external: External package builder mode (will not report Go build + state to dashboard or issue releases) + +The key file should be located at $HOME/.gobuildkey or, for a builder-specific +key, $HOME/.gobuildkey-$BUILDER (eg, $HOME/.gobuildkey-linux-amd64). + +The build key file is a text file of the format: + + godashboard-key + googlecode-username + googlecode-password + +If the Google Code credentials are not provided the archival step +will be skipped. + +*/ +package documentation diff --git a/misc/dashboard/builder/exec.go b/misc/dashboard/builder/exec.go new file mode 100644 index 000000000..a042c5699 --- /dev/null +++ b/misc/dashboard/builder/exec.go @@ -0,0 +1,74 @@ +// 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 ( + "bytes" + "exec" + "io" + "log" + "os" + "strings" +) + +// run is a simple wrapper for exec.Run/Close +func run(envv []string, dir string, argv ...string) os.Error { + if *verbose { + log.Println("run", argv) + } + argv = useBash(argv) + cmd := exec.Command(argv[0], argv[1:]...) + cmd.Dir = dir + cmd.Env = envv + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// 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 0. +func runLog(envv []string, logfile, dir string, argv ...string) (string, int, os.Error) { + if *verbose { + log.Println("runLog", argv) + } + argv = useBash(argv) + + 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) + } + + cmd := exec.Command(argv[0], argv[1:]...) + cmd.Dir = dir + cmd.Env = envv + cmd.Stdout = w + cmd.Stderr = w + + err := cmd.Run() + if err != nil { + if ws, ok := err.(*os.Waitmsg); ok { + return b.String(), ws.ExitStatus(), nil + } + } + return b.String(), 0, nil +} + +// useBash prefixes a list of args with 'bash' if the first argument +// is a bash script. +func useBash(argv []string) []string { + // TODO(brainman): choose a more reliable heuristic here. + if strings.HasSuffix(argv[0], ".bash") { + argv = append([]string{"bash"}, argv...) + } + return argv +} diff --git a/misc/dashboard/builder/http.go b/misc/dashboard/builder/http.go new file mode 100644 index 000000000..abef8faa4 --- /dev/null +++ b/misc/dashboard/builder/http.go @@ -0,0 +1,148 @@ +// 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 ( + "bytes" + "fmt" + "http" + "json" + "log" + "os" + "strconv" + "url" +) + +type param map[string]string + +// dash runs the given method and command on the dashboard. +// If args is not nil, it is the query or post parameters. +// If resp is not nil, dash unmarshals the body as JSON into resp. +func dash(meth, cmd string, resp interface{}, args param) os.Error { + var r *http.Response + var err os.Error + if *verbose { + log.Println("dash", cmd, args) + } + cmd = "http://" + *dashboard + "/" + cmd + vals := make(url.Values) + for k, v := range args { + vals.Add(k, v) + } + switch meth { + case "GET": + if q := vals.Encode(); q != "" { + cmd += "?" + q + } + r, err = http.Get(cmd) + case "POST": + r, err = http.PostForm(cmd, vals) + default: + return fmt.Errorf("unknown method %q", meth) + } + if err != nil { + return err + } + defer r.Body.Close() + var buf bytes.Buffer + buf.ReadFrom(r.Body) + if resp != nil { + if err = json.Unmarshal(buf.Bytes(), resp); err != nil { + log.Printf("json unmarshal %#q: %s\n", buf.Bytes(), err) + return err + } + } + return nil +} + +func dashStatus(meth, cmd string, args param) os.Error { + var resp struct { + Status string + Error string + } + err := dash(meth, cmd, &resp, args) + if err != nil { + return err + } + if resp.Status != "OK" { + return os.NewError("/build: " + resp.Error) + } + return nil +} + +// todo returns the next hash to build. +func (b *Builder) todo() (rev string, err os.Error) { + var resp []struct { + Hash string + } + if err = dash("GET", "todo", &resp, param{"builder": b.name}); err != nil { + return + } + if len(resp) > 0 { + rev = resp[0].Hash + } + return +} + +// recordResult sends build results to the dashboard +func (b *Builder) recordResult(buildLog string, hash string) os.Error { + return dash("POST", "build", nil, param{ + "builder": b.name, + "key": b.key, + "node": hash, + "log": buildLog, + }) +} + +// packages fetches a list of package paths from the dashboard +func packages() (pkgs []string, err os.Error) { + var resp struct { + Packages []struct { + Path string + } + } + err = dash("GET", "package", &resp, param{"fmt": "json"}) + if err != nil { + return + } + for _, p := range resp.Packages { + pkgs = append(pkgs, p.Path) + } + return +} + +// updatePackage sends package build results and info dashboard +func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) os.Error { + return dash("POST", "package", nil, param{ + "builder": b.name, + "key": b.key, + "path": pkg, + "ok": strconv.Btoa(ok), + "log": buildLog, + "info": info, + }) +} + +// postCommit informs the dashboard of a new commit +func postCommit(key string, l *HgLog) os.Error { + return dashStatus("POST", "commit", param{ + "key": key, + "node": l.Hash, + "date": l.Date, + "user": l.Author, + "parent": l.Parent, + "desc": l.Desc, + }) +} + +// dashboardCommit returns true if the dashboard knows about hash. +func dashboardCommit(hash string) bool { + err := dashStatus("GET", "commit", param{"node": hash}) + if err != nil { + log.Printf("check %s: %s", hash, err) + return false + } + return true +} diff --git a/misc/dashboard/builder/main.go b/misc/dashboard/builder/main.go new file mode 100644 index 000000000..989965bc4 --- /dev/null +++ b/misc/dashboard/builder/main.go @@ -0,0 +1,624 @@ +// 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 ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path" + "regexp" + "runtime" + "strconv" + "strings" + "time" + "xml" +) + +const ( + codeProject = "go" + codePyScript = "misc/dashboard/googlecode_upload.py" + hgUrl = "https://go.googlecode.com/hg/" + waitInterval = 30e9 // time to wait before checking for new revs + mkdirPerm = 0750 + pkgBuildInterval = 1e9 * 60 * 60 * 24 // rebuild packages every 24 hours +) + +// These variables are copied from the gobuilder's environment +// to the envv of its subprocesses. +var extraEnv = []string{ + "GOHOSTOS", + "GOHOSTARCH", + "PATH", + "DISABLE_NET_TESTS", + "MAKEFLAGS", + "GOARM", +} + +type Builder struct { + name string + goos, goarch string + key string + codeUsername string + codePassword string +} + +var ( + buildroot = flag.String("buildroot", path.Join(os.TempDir(), "gobuilder"), "Directory under which to build") + commitFlag = flag.Bool("commit", false, "upload information about new commits") + dashboard = flag.String("dashboard", "godashboard.appspot.com", "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", "./all.bash", "Build command (specify absolute or relative to go/src/)") + external = flag.Bool("external", false, "Build external packages") + parallel = flag.Bool("parallel", false, "Build multiple targets in parallel") + verbose = flag.Bool("v", false, "verbose") +) + +var ( + goroot string + binaryTagRe = regexp.MustCompile(`^(release\.r|weekly\.)[0-9\-.]+`) + releaseRe = regexp.MustCompile(`^release\.r[0-9\-.]+`) +) + +func main() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "usage: %s goos-goarch...\n", os.Args[0]) + flag.PrintDefaults() + os.Exit(2) + } + flag.Parse() + if len(flag.Args()) == 0 && !*commitFlag { + flag.Usage() + } + goroot = path.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 + } + + // set up work environment + if err := os.RemoveAll(*buildroot); err != nil { + log.Fatalf("Error removing build root (%s): %s", *buildroot, err) + } + if err := os.Mkdir(*buildroot, mkdirPerm); err != nil { + log.Fatalf("Error making build root (%s): %s", *buildroot, err) + } + if err := run(nil, *buildroot, "hg", "clone", hgUrl, goroot); err != nil { + log.Fatal("Error cloning repository:", err) + } + + if *commitFlag { + if len(flag.Args()) == 0 { + commitWatcher() + return + } + go commitWatcher() + } + + // if specified, build revision and return + if *buildRevision != "" { + hash, err := fullHash(*buildRevision) + if err != nil { + log.Fatal("Error finding revision: ", err) + } + for _, b := range builders { + if err := b.buildHash(hash); err != nil { + log.Println(err) + } + } + return + } + + // external package build mode + if *external { + if len(builders) != 1 { + log.Fatal("only one goos-goarch should be specified with -external") + } + builders[0].buildExternal() + } + + // go continuous build mode (default) + // check for new commits and build them + for { + built := false + t := time.Nanoseconds() + if *parallel { + done := make(chan bool) + for _, b := range builders { + go func(b *Builder) { + done <- b.build() + }(b) + } + for _ = range builders { + built = <-done || built + } + } else { + for _, b := range builders { + built = b.build() || built + } + } + // sleep if there was nothing to build + if !built { + time.Sleep(waitInterval) + } + // sleep if we're looping too fast. + t1 := time.Nanoseconds() - t + if t1 < waitInterval { + time.Sleep(waitInterval - t1) + } + } +} + +func NewBuilder(builder string) (*Builder, os.Error) { + b := &Builder{name: builder} + + // get goos/goarch from builder string + s := strings.SplitN(builder, "-", 3) + if len(s) >= 2 { + b.goos, b.goarch = s[0], s[1] + } else { + return nil, fmt.Errorf("unsupported builder form: %s", builder) + } + + // read keys from keyfile + fn := path.Join(os.Getenv("HOME"), ".gobuildkey") + if s := fn + "-" + b.name; isFile(s) { // builder-specific file + fn = s + } + c, err := ioutil.ReadFile(fn) + if err != nil { + return nil, fmt.Errorf("readKeys %s (%s): %s", b.name, fn, err) + } + v := strings.Split(string(c), "\n") + b.key = v[0] + if len(v) >= 3 { + b.codeUsername, b.codePassword = v[1], v[2] + } + + return b, nil +} + +// buildExternal downloads and builds external packages, and +// reports their build status to the dashboard. +// It will re-build all packages after pkgBuildInterval nanoseconds or +// a new release tag is found. +func (b *Builder) buildExternal() { + var prevTag string + var nextBuild int64 + for { + time.Sleep(waitInterval) + err := run(nil, goroot, "hg", "pull", "-u") + if err != nil { + log.Println("hg pull failed:", err) + continue + } + hash, tag, err := firstTag(releaseRe) + if err != nil { + log.Println(err) + continue + } + if *verbose { + log.Println("latest release:", tag) + } + // don't rebuild if there's no new release + // and it's been less than pkgBuildInterval + // nanoseconds since the last build. + if tag == prevTag && time.Nanoseconds() < nextBuild { + continue + } + // build will also build the packages + if err := b.buildHash(hash); err != nil { + log.Println(err) + continue + } + prevTag = tag + nextBuild = time.Nanoseconds() + pkgBuildInterval + } +} + +// build checks for a new commit for this builder +// and builds it if one is found. +// It returns true if a build was attempted. +func (b *Builder) build() bool { + defer func() { + err := recover() + if err != nil { + log.Println(b.name, "build:", err) + } + }() + hash, err := b.todo() + if err != nil { + log.Println(err) + return false + } + if hash == "" { + return false + } + // Look for hash locally before running hg pull. + + if _, err := fullHash(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 { + log.Println(err) + } + return true +} + +func (b *Builder) buildHash(hash string) (err os.Error) { + defer func() { + if err != nil { + err = fmt.Errorf("%s build: %s: %s", b.name, hash, err) + } + }() + + log.Println(b.name, "building", hash) + + // create place in which to do work + workpath := path.Join(*buildroot, b.name+"-"+hash[:12]) + err = os.Mkdir(workpath, mkdirPerm) + if err != nil { + return + } + defer os.RemoveAll(workpath) + + // clone repo + err = run(nil, workpath, "hg", "clone", goroot, "go") + if err != nil { + return + } + + // update to specified revision + err = run(nil, path.Join(workpath, "go"), + "hg", "update", hash) + if err != nil { + return + } + + srcDir := path.Join(workpath, "go", "src") + + // build + logfile := path.Join(workpath, "build.log") + buildLog, status, err := runLog(b.envv(), logfile, srcDir, *buildCmd) + if err != nil { + return fmt.Errorf("%s: %s", *buildCmd, err) + } + + // if we're in external mode, build all packages and return + if *external { + if status != 0 { + return os.NewError("go build failed") + } + return b.buildPackages(workpath, hash) + } + + if status != 0 { + // record failure + return b.recordResult(buildLog, hash) + } + + // record success + if err = b.recordResult("", hash); err != nil { + return fmt.Errorf("recordResult: %s", err) + } + + // finish here if codeUsername and codePassword aren't set + if b.codeUsername == "" || b.codePassword == "" || !*buildRelease { + return + } + + // if this is a release, create tgz and upload to google code + releaseHash, release, err := firstTag(binaryTagRe) + if hash == releaseHash { + // clean out build state + err = run(b.envv(), srcDir, "./clean.bash", "--nopkg") + if err != nil { + return fmt.Errorf("clean.bash: %s", err) + } + // upload binary release + fn := fmt.Sprintf("go.%s.%s-%s.tar.gz", release, b.goos, b.goarch) + err = run(nil, workpath, "tar", "czf", fn, "go") + if err != nil { + return fmt.Errorf("tar: %s", err) + } + err = run(nil, workpath, path.Join(goroot, codePyScript), + "-s", release, + "-p", codeProject, + "-u", b.codeUsername, + "-w", b.codePassword, + "-l", fmt.Sprintf("%s,%s", b.goos, b.goarch), + fn) + } + + return +} + +// envv returns an environment for build/bench execution +func (b *Builder) envv() []string { + if runtime.GOOS == "windows" { + return b.envvWindows() + } + e := []string{ + "GOOS=" + b.goos, + "GOARCH=" + b.goarch, + "GOROOT_FINAL=/usr/local/go", + } + for _, k := range extraEnv { + s, err := os.Getenverror(k) + if err == nil { + e = append(e, k+"="+s) + } + } + return e +} + +// windows version of envv +func (b *Builder) envvWindows() []string { + start := map[string]string{ + "GOOS": b.goos, + "GOARCH": b.goarch, + "GOROOT_FINAL": "/c/go", + // TODO(brainman): remove once we find make that does not hang. + "MAKEFLAGS": "-j1", + } + for _, name := range extraEnv { + s, err := os.Getenverror(name) + if err == nil { + start[name] = s + } + } + skip := map[string]bool{ + "GOBIN": true, + "GOROOT": true, + "INCLUDE": true, + "LIB": true, + } + var e []string + for name, v := range start { + e = append(e, name+"="+v) + skip[name] = true + } + for _, kv := range os.Environ() { + s := strings.SplitN(kv, "=", 2) + name := strings.ToUpper(s[0]) + switch { + case name == "": + // variables, like "=C:=C:\", just copy them + e = append(e, kv) + case !skip[name]: + e = append(e, kv) + skip[name] = true + } + } + return e +} + +func isDirectory(name string) bool { + s, err := os.Stat(name) + return err == nil && s.IsDirectory() +} + +func isFile(name string) bool { + s, err := os.Stat(name) + return err == nil && (s.IsRegular() || s.IsSymlink()) +} + +// commitWatcher polls hg for new commits and tells the dashboard about them. +func commitWatcher() { + // Create builder just to get master key. + b, err := NewBuilder("mercurial-commit") + if err != nil { + log.Fatal(err) + } + for { + if *verbose { + log.Printf("poll...") + } + commitPoll(b.key) + if *verbose { + log.Printf("sleep...") + } + time.Sleep(60e9) + } +} + +// 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}</date> + <desc>{desc|escape}</desc> + </log> +` + +// commitPoll pulls any new revisions from the hg server +// and tells the server about them. +func commitPoll(key string) { + // Catch unexpected panics. + defer func() { + if err := recover(); err != nil { + log.Printf("commitPoll panic: %s", err) + } + }() + + if err := run(nil, goroot, "hg", "pull"); err != nil { + log.Printf("hg pull: %v", err) + return + } + + const N = 20 // how many revisions to grab + + data, _, err := runLog(nil, "", goroot, "hg", "log", + "--encoding=utf-8", + "--limit="+strconv.Itoa(N), + "--template="+xmlLogTemplate, + ) + if err != nil { + log.Printf("hg log: %v", err) + return + } + + var logStruct struct { + Log []HgLog + } + err = xml.Unmarshal(strings.NewReader("<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. + for i := range logs { + l := &logs[i] + log.Printf("hg log: %s < %s\n", l.Hash, l.Parent) + if l.Parent == "" && i+1 < len(logs) { + l.Parent = logs[i+1].Hash + } else if l.Parent != "" { + l.Parent, _ = fullHash(l.Parent) + } + if l.Parent == "" { + // Can't create node without parent. + continue + } + + if logByHash[l.Hash] == nil { + // Make copy to avoid pinning entire slice when only one entry is new. + t := *l + logByHash[t.Hash] = &t + } + } + + for i := range logs { + l := &logs[i] + if l.Parent == "" { + continue + } + addCommit(l.Hash, key) + } +} + +// addCommit adds the commit with the named hash to the dashboard. +// key is the secret key for authentication to the dashboard. +// It avoids duplicate effort. +func addCommit(hash, key string) bool { + l := logByHash[hash] + if l == nil { + return false + } + if l.added { + return true + } + + // Check for already added, perhaps in an earlier run. + if dashboardCommit(hash) { + log.Printf("%s already on dashboard\n", hash) + // Record that this hash is on the dashboard, + // as must be all its parents. + for l != nil { + l.added = true + l = logByHash[l.Parent] + } + return true + } + + // Create parent first, to maintain some semblance of order. + if !addCommit(l.Parent, key) { + return false + } + + // Create commit. + if err := postCommit(key, l); err != nil { + log.Printf("failed to add %s to dashboard: %v", key, err) + return false + } + return true +} + +// fullHash returns the full hash for the given Mercurial revision. +func fullHash(rev string) (hash string, err os.Error) { + defer func() { + if err != nil { + err = fmt.Errorf("fullHash: %s: %s", rev, err) + } + }() + s, _, err := runLog(nil, "", goroot, + "hg", "log", + "--encoding=utf-8", + "--rev="+rev, + "--limit=1", + "--template={node}", + ) + if err != nil { + return + } + 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 revisionRe = regexp.MustCompile(`^([^ ]+) +[0-9]+:([0-9a-f]+)$`) + +// firstTag returns the hash and tag of the most recent tag matching re. +func firstTag(re *regexp.Regexp) (hash string, tag string, err os.Error) { + o, _, err := runLog(nil, "", goroot, "hg", "tags") + for _, l := range strings.Split(o, "\n") { + if l == "" { + continue + } + s := revisionRe.FindStringSubmatch(l) + if s == nil { + err = os.NewError("couldn't find revision number") + return + } + if !re.MatchString(s[1]) { + continue + } + tag = s[1] + hash, err = fullHash(s[2]) + return + } + err = os.NewError("no matching tag found") + return +} diff --git a/misc/dashboard/builder/package.go b/misc/dashboard/builder/package.go new file mode 100644 index 000000000..b2a83fa13 --- /dev/null +++ b/misc/dashboard/builder/package.go @@ -0,0 +1,94 @@ +// 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 ( + "go/doc" + "go/parser" + "go/token" + "log" + "os" + "path/filepath" + "strings" +) + +const MaxCommentLength = 500 // App Engine won't store more in a StringProperty. + +func (b *Builder) buildPackages(workpath string, hash string) os.Error { + pkgs, err := packages() + if err != nil { + return err + } + for _, p := range pkgs { + goroot := filepath.Join(workpath, "go") + gobin := filepath.Join(goroot, "bin") + goinstall := filepath.Join(gobin, "goinstall") + envv := append(b.envv(), "GOROOT="+goroot) + + // add GOBIN to path + for i, v := range envv { + if strings.HasPrefix(v, "PATH=") { + p := filepath.SplitList(v[5:]) + p = append([]string{gobin}, p...) + s := strings.Join(p, string(filepath.ListSeparator)) + envv[i] = "PATH=" + s + } + } + + // goinstall + buildLog, code, err := runLog(envv, "", goroot, goinstall, "-dashboard=false", p) + if err != nil { + log.Printf("goinstall %v: %v", p, err) + } + + // get doc comment from package source + info, err := packageComment(p, filepath.Join(goroot, "src", "pkg", p)) + if err != nil { + log.Printf("packageComment %v: %v", p, err) + } + + // update dashboard with build state + info + err = b.updatePackage(p, code == 0, buildLog, info) + if err != nil { + log.Printf("updatePackage %v: %v", p, err) + } + } + return nil +} + +func isGoFile(fi *os.FileInfo) bool { + return fi.IsRegular() && // exclude directories + !strings.HasPrefix(fi.Name, ".") && // ignore .files + filepath.Ext(fi.Name) == ".go" +} + +func packageComment(pkg, pkgpath string) (info string, err os.Error) { + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, pkgpath, isGoFile, parser.PackageClauseOnly|parser.ParseComments) + if err != nil { + return + } + for name := range pkgs { + if name == "main" { + continue + } + if info != "" { + return "", os.NewError("multiple non-main package docs") + } + pdoc := doc.NewPackageDoc(pkgs[name], pkg) + info = pdoc.Doc + } + // grab only first paragraph + if parts := strings.SplitN(info, "\n\n", 2); len(parts) > 1 { + info = parts[0] + } + // replace newlines with spaces + info = strings.Replace(info, "\n", " ", -1) + // truncate + if len(info) > MaxCommentLength { + info = info[:MaxCommentLength] + } + return +} diff --git a/misc/dashboard/godashboard/_multiprocessing.py b/misc/dashboard/godashboard/_multiprocessing.py new file mode 100644 index 000000000..8c66c0659 --- /dev/null +++ b/misc/dashboard/godashboard/_multiprocessing.py @@ -0,0 +1,5 @@ +# 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. + +import multiprocessing diff --git a/misc/dashboard/godashboard/app.yaml b/misc/dashboard/godashboard/app.yaml new file mode 100644 index 000000000..7b77a85cc --- /dev/null +++ b/misc/dashboard/godashboard/app.yaml @@ -0,0 +1,25 @@ +application: godashboard +version: 7 +runtime: python +api_version: 1 + +handlers: +- url: /favicon\.ico + static_files: static/favicon.ico + upload: static/favicon\.ico + +- url: /static + static_dir: static + +- url: /package + script: package.py + +- url: /package/daily + script: package.py + login: admin + +- url: /project.* + script: package.py + +- url: /.* + script: gobuild.py diff --git a/misc/dashboard/godashboard/auth.py b/misc/dashboard/godashboard/auth.py new file mode 100644 index 000000000..73a54c0d4 --- /dev/null +++ b/misc/dashboard/godashboard/auth.py @@ -0,0 +1,13 @@ +# 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. + +import hmac + +# local imports +import key + +def auth(req): + k = req.get('key') + return k == hmac.new(key.accessKey, req.get('builder')).hexdigest() or k == key.accessKey + diff --git a/misc/dashboard/godashboard/const.py b/misc/dashboard/godashboard/const.py new file mode 100644 index 000000000..b0110c635 --- /dev/null +++ b/misc/dashboard/godashboard/const.py @@ -0,0 +1,13 @@ +# 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. + +mail_from = "Go Dashboard <builder@golang.org>" + +mail_submit_to = "adg@golang.org" +mail_submit_subject = "New Project Submitted" + +mail_fail_to = "golang-dev@googlegroups.com" +mail_fail_reply_to = "golang-dev@googlegroups.com" +mail_fail_subject = "%s broken by %s" + diff --git a/misc/dashboard/godashboard/cron.yaml b/misc/dashboard/godashboard/cron.yaml new file mode 100644 index 000000000..953b6a1cd --- /dev/null +++ b/misc/dashboard/godashboard/cron.yaml @@ -0,0 +1,4 @@ +cron: +- description: daily package maintenance + url: /package/daily + schedule: every 24 hours diff --git a/misc/dashboard/godashboard/fail-notify.txt b/misc/dashboard/godashboard/fail-notify.txt new file mode 100644 index 000000000..a699005ea --- /dev/null +++ b/misc/dashboard/godashboard/fail-notify.txt @@ -0,0 +1,6 @@ +Change {{node}} broke the {{builder}} build: +http://godashboard.appspot.com/log/{{loghash}} + +{{desc}} + +http://code.google.com/p/go/source/detail?r={{node}} diff --git a/misc/dashboard/godashboard/gobuild.py b/misc/dashboard/godashboard/gobuild.py new file mode 100644 index 000000000..685dc83a9 --- /dev/null +++ b/misc/dashboard/godashboard/gobuild.py @@ -0,0 +1,558 @@ +# 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. + +# This is the server part of the continuous build system for Go. It must be run +# by AppEngine. + +from django.utils import simplejson +from google.appengine.api import mail +from google.appengine.api import memcache +from google.appengine.ext import db +from google.appengine.ext import webapp +from google.appengine.ext.webapp import template +from google.appengine.ext.webapp.util import run_wsgi_app +import datetime +import hashlib +import logging +import os +import re +import bz2 + +# local imports +from auth import auth +import const + +# The majority of our state are commit objects. One of these exists for each of +# the commits known to the build system. Their key names are of the form +# <commit number (%08x)> "-" <hg hash>. This means that a sorting by the key +# name is sufficient to order the commits. +# +# The commit numbers are purely local. They need not match up to the commit +# numbers in an hg repo. When inserting a new commit, the parent commit must be +# given and this is used to generate the new commit number. In order to create +# the first Commit object, a special command (/init) is used. +class Commit(db.Model): + num = db.IntegerProperty() # internal, monotonic counter. + node = db.StringProperty() # Hg hash + parentnode = db.StringProperty() # Hg hash + user = db.StringProperty() + date = db.DateTimeProperty() + desc = db.BlobProperty() + + # This is the list of builds. Each element is a string of the form <builder + # name> '`' <log hash>. If the log hash is empty, then the build was + # successful. + builds = db.StringListProperty() + + fail_notification_sent = db.BooleanProperty() + +# A CompressedLog contains the textual build log of a failed build. +# The key name is the hex digest of the SHA256 hash of the contents. +# The contents is bz2 compressed. +class CompressedLog(db.Model): + log = db.BlobProperty() + +N = 30 + +def builderInfo(b): + f = b.split('-', 3) + goos = f[0] + goarch = f[1] + note = "" + if len(f) > 2: + note = f[2] + return {'name': b, 'goos': goos, 'goarch': goarch, 'note': note} + +def builderset(): + q = Commit.all() + q.order('-__key__') + results = q.fetch(N) + builders = set() + for c in results: + builders.update(set(parseBuild(build)['builder'] for build in c.builds)) + return builders + +class MainPage(webapp.RequestHandler): + def get(self): + self.response.headers['Content-Type'] = 'text/html; charset=utf-8' + + try: + page = int(self.request.get('p', 1)) + if not page > 0: + raise + except: + page = 1 + + try: + num = int(self.request.get('n', N)) + if num <= 0 or num > 200: + raise + except: + num = N + + offset = (page-1) * num + + q = Commit.all() + q.order('-__key__') + results = q.fetch(num, offset) + + revs = [toRev(r) for r in results] + builders = {} + + for r in revs: + for b in r['builds']: + builders[b['builder']] = builderInfo(b['builder']) + + for r in revs: + have = set(x['builder'] for x in r['builds']) + need = set(builders.keys()).difference(have) + for n in need: + r['builds'].append({'builder': n, 'log':'', 'ok': False}) + r['builds'].sort(cmp = byBuilder) + + builders = list(builders.items()) + builders.sort() + values = {"revs": revs, "builders": [v for k,v in builders]} + + values['num'] = num + values['prev'] = page - 1 + if len(results) == num: + values['next'] = page + 1 + + path = os.path.join(os.path.dirname(__file__), 'main.html') + self.response.out.write(template.render(path, values)) + +# A DashboardHandler is a webapp.RequestHandler but provides +# authenticated_post - called by post after authenticating +# json - writes object in json format to response output +class DashboardHandler(webapp.RequestHandler): + def post(self): + if not auth(self.request): + self.response.set_status(403) + return + self.authenticated_post() + + def authenticated_post(self): + return + + def json(self, obj): + self.response.set_status(200) + simplejson.dump(obj, self.response.out) + return + +# Todo serves /todo. It tells the builder which commits need to be built. +class Todo(DashboardHandler): + def get(self): + builder = self.request.get('builder') + key = 'todo-%s' % builder + response = memcache.get(key) + if response is None: + # Fell out of memcache. Rebuild from datastore results. + # We walk the commit list looking for nodes that have not + # been built by this builder. + q = Commit.all() + q.order('-__key__') + todo = [] + first = None + for c in q.fetch(N+1): + if first is None: + first = c + if not built(c, builder): + todo.append({'Hash': c.node}) + response = simplejson.dumps(todo) + memcache.set(key, response, 3600) + self.response.set_status(200) + self.response.out.write(response) + +def built(c, builder): + for b in c.builds: + if b.startswith(builder+'`'): + return True + return False + +# Log serves /log/. It retrieves log data by content hash. +class LogHandler(DashboardHandler): + def get(self): + self.response.headers['Content-Type'] = 'text/plain; charset=utf-8' + hash = self.request.path[5:] + l = CompressedLog.get_by_key_name(hash) + if l is None: + self.response.set_status(404) + return + log = bz2.decompress(l.log) + self.response.set_status(200) + self.response.out.write(log) + +# Init creates the commit with id 0. Since this commit doesn't have a parent, +# it cannot be created by Build. +class Init(DashboardHandler): + def authenticated_post(self): + date = parseDate(self.request.get('date')) + node = self.request.get('node') + if not validNode(node) or date is None: + logging.error("Not valid node ('%s') or bad date (%s %s)", node, date, self.request.get('date')) + self.response.set_status(500) + return + + commit = Commit(key_name = '00000000-%s' % node) + commit.num = 0 + commit.node = node + commit.parentnode = '' + commit.user = self.request.get('user').encode('utf8') + commit.date = date + commit.desc = self.request.get('desc').encode('utf8') + + commit.put() + + self.response.set_status(200) + +# The last commit when we switched to using entity groups. +# This is the root of the new commit entity group. +RootCommitKeyName = '00000f26-f32c6f1038207c55d5780231f7484f311020747e' + +# CommitHandler serves /commit. +# A GET of /commit retrieves information about the specified commit. +# A POST of /commit creates a node for the given commit. +# If the commit already exists, the POST silently succeeds (like mkdir -p). +class CommitHandler(DashboardHandler): + def get(self): + node = self.request.get('node') + if not validNode(node): + return self.json({'Status': 'FAIL', 'Error': 'malformed node hash'}) + n = nodeByHash(node) + if n is None: + return self.json({'Status': 'FAIL', 'Error': 'unknown revision'}) + return self.json({'Status': 'OK', 'Node': nodeObj(n)}) + + def authenticated_post(self): + # Require auth with the master key, not a per-builder key. + if self.request.get('builder'): + self.response.set_status(403) + return + + node = self.request.get('node') + date = parseDate(self.request.get('date')) + user = self.request.get('user').encode('utf8') + desc = self.request.get('desc').encode('utf8') + parenthash = self.request.get('parent') + + if not validNode(node) or not validNode(parenthash) or date is None: + return self.json({'Status': 'FAIL', 'Error': 'malformed node, parent, or date'}) + + n = nodeByHash(node) + if n is None: + p = nodeByHash(parenthash) + if p is None: + return self.json({'Status': 'FAIL', 'Error': 'unknown parent'}) + + # Want to create new node in a transaction so that multiple + # requests creating it do not collide and so that multiple requests + # creating different nodes get different sequence numbers. + # All queries within a transaction must include an ancestor, + # but the original datastore objects we used for the dashboard + # have no common ancestor. Instead, we use a well-known + # root node - the last one before we switched to entity groups - + # as the as the common ancestor. + root = Commit.get_by_key_name(RootCommitKeyName) + + def add_commit(): + if nodeByHash(node, ancestor=root) is not None: + return + + # Determine number for this commit. + # Once we have created one new entry it will be lastRooted.num+1, + # but the very first commit created in this scheme will have to use + # last.num's number instead (last is likely not rooted). + q = Commit.all() + q.order('-__key__') + q.ancestor(root) + last = q.fetch(1)[0] + num = last.num+1 + + n = Commit(key_name = '%08x-%s' % (num, node), parent = root) + n.num = num + n.node = node + n.parentnode = parenthash + n.user = user + n.date = date + n.desc = desc + n.put() + db.run_in_transaction(add_commit) + n = nodeByHash(node) + if n is None: + return self.json({'Status': 'FAIL', 'Error': 'failed to create commit node'}) + + return self.json({'Status': 'OK', 'Node': nodeObj(n)}) + +# Build serves /build. +# A POST to /build records a new build result. +class Build(webapp.RequestHandler): + def post(self): + if not auth(self.request): + self.response.set_status(403) + return + + builder = self.request.get('builder') + log = self.request.get('log').encode('utf-8') + + loghash = '' + if len(log) > 0: + loghash = hashlib.sha256(log).hexdigest() + l = CompressedLog(key_name=loghash) + l.log = bz2.compress(log) + l.put() + + node = self.request.get('node') + if not validNode(node): + logging.error('Invalid node %s' % (node)) + self.response.set_status(500) + return + + n = nodeByHash(node) + if n is None: + logging.error('Cannot find node %s' % (node)) + self.response.set_status(404) + return + nn = n + + def add_build(): + n = nodeByHash(node, ancestor=nn) + if n is None: + logging.error('Cannot find hash in add_build: %s %s' % (builder, node)) + return + + s = '%s`%s' % (builder, loghash) + for i, b in enumerate(n.builds): + if b.split('`', 1)[0] == builder: + # logging.error('Found result for %s %s already' % (builder, node)) + n.builds[i] = s + break + else: + # logging.error('Added result for %s %s' % (builder, node)) + n.builds.append(s) + n.put() + + db.run_in_transaction(add_build) + + key = 'todo-%s' % builder + memcache.delete(key) + + c = getBrokenCommit(node, builder) + if c is not None and not c.fail_notification_sent: + notifyBroken(c, builder) + + self.response.set_status(200) + + +def getBrokenCommit(node, builder): + """ + getBrokenCommit returns a Commit that breaks the build. + The Commit will be either the one specified by node or the one after. + """ + + # Squelch mail if already fixed. + head = firstResult(builder) + if broken(head, builder) == False: + return + + # Get current node and node before, after. + cur = nodeByHash(node) + if cur is None: + return + before = nodeBefore(cur) + after = nodeAfter(cur) + + if broken(before, builder) == False and broken(cur, builder): + return cur + if broken(cur, builder) == False and broken(after, builder): + return after + + return + +def firstResult(builder): + q = Commit.all().order('-__key__') + for c in q.fetch(20): + for i, b in enumerate(c.builds): + p = b.split('`', 1) + if p[0] == builder: + return c + return None + +def nodeBefore(c): + return nodeByHash(c.parentnode) + +def nodeAfter(c): + return Commit.all().filter('parenthash', c.node).get() + +def notifyBroken(c, builder): + def send(): + n = Commit.get(c.key()) + if n is None: + logging.error("couldn't retrieve Commit '%s'" % c.key()) + return False + if n.fail_notification_sent: + return False + n.fail_notification_sent = True + return n.put() + if not db.run_in_transaction(send): + return + + subject = const.mail_fail_subject % (builder, c.desc.split('\n')[0]) + path = os.path.join(os.path.dirname(__file__), 'fail-notify.txt') + body = template.render(path, { + "builder": builder, + "node": c.node, + "user": c.user, + "desc": c.desc, + "loghash": logHash(c, builder) + }) + mail.send_mail( + sender=const.mail_from, + to=const.mail_fail_to, + subject=subject, + body=body + ) + +def logHash(c, builder): + for i, b in enumerate(c.builds): + p = b.split('`', 1) + if p[0] == builder: + return p[1] + return "" + +def broken(c, builder): + """ + broken returns True if commit c breaks the build for the specified builder, + False if it is a good build, and None if no results exist for this builder. + """ + if c is None: + return None + for i, b in enumerate(c.builds): + p = b.split('`', 1) + if p[0] == builder: + return len(p[1]) > 0 + return None + +def node(num): + q = Commit.all() + q.filter('num =', num) + n = q.get() + return n + +def nodeByHash(hash, ancestor=None): + q = Commit.all() + q.filter('node =', hash) + if ancestor is not None: + q.ancestor(ancestor) + n = q.get() + return n + +# nodeObj returns a JSON object (ready to be passed to simplejson.dump) describing node. +def nodeObj(n): + return { + 'Hash': n.node, + 'ParentHash': n.parentnode, + 'User': n.user, + 'Date': n.date.strftime('%Y-%m-%d %H:%M %z'), + 'Desc': n.desc, + } + +class FixedOffset(datetime.tzinfo): + """Fixed offset in minutes east from UTC.""" + + def __init__(self, offset): + self.__offset = datetime.timedelta(seconds = offset) + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return None + + def dst(self, dt): + return datetime.timedelta(0) + +def validNode(node): + if len(node) != 40: + return False + for x in node: + o = ord(x) + if (o < ord('0') or o > ord('9')) and (o < ord('a') or o > ord('f')): + return False + return True + +def parseDate(date): + if '-' in date: + (a, offset) = date.split('-', 1) + try: + return datetime.datetime.fromtimestamp(float(a), FixedOffset(0-int(offset))) + except ValueError: + return None + if '+' in date: + (a, offset) = date.split('+', 1) + try: + return datetime.datetime.fromtimestamp(float(a), FixedOffset(int(offset))) + except ValueError: + return None + try: + return datetime.datetime.utcfromtimestamp(float(date)) + except ValueError: + return None + +email_re = re.compile('^[^<]+<([^>]*)>$') + +def toUsername(user): + r = email_re.match(user) + if r is None: + return user + email = r.groups()[0] + return email.replace('@golang.org', '') + +def dateToShortStr(d): + return d.strftime('%a %b %d %H:%M') + +def parseBuild(build): + [builder, logblob] = build.split('`') + return {'builder': builder, 'log': logblob, 'ok': len(logblob) == 0} + +def nodeInfo(c): + return { + "node": c.node, + "user": toUsername(c.user), + "date": dateToShortStr(c.date), + "desc": c.desc, + "shortdesc": c.desc.split('\n', 2)[0] + } + +def toRev(c): + b = nodeInfo(c) + b['builds'] = [parseBuild(build) for build in c.builds] + return b + +def byBuilder(x, y): + return cmp(x['builder'], y['builder']) + +# Give old builders work; otherwise they pound on the web site. +class Hwget(DashboardHandler): + def get(self): + self.response.out.write("8000\n") + +# This is the URL map for the server. The first three entries are public, the +# rest are only used by the builders. +application = webapp.WSGIApplication( + [('/', MainPage), + ('/hw-get', Hwget), + ('/log/.*', LogHandler), + ('/commit', CommitHandler), + ('/init', Init), + ('/todo', Todo), + ('/build', Build), + ], debug=True) + +def main(): + run_wsgi_app(application) + +if __name__ == "__main__": + main() + diff --git a/misc/dashboard/godashboard/index.yaml b/misc/dashboard/godashboard/index.yaml new file mode 100644 index 000000000..f39299d5d --- /dev/null +++ b/misc/dashboard/godashboard/index.yaml @@ -0,0 +1,51 @@ +indexes: + +- kind: BenchmarkResult + ancestor: yes + properties: + - name: builder + - name: __key__ + direction: desc + +- kind: BenchmarkResult + ancestor: yes + properties: + - name: __key__ + direction: desc + +- kind: BenchmarkResults + properties: + - name: builder + - name: benchmark + +- kind: Commit + properties: + - name: __key__ + direction: desc + +- kind: Commit + ancestor: yes + properties: + - name: __key__ + direction: desc + +- kind: Project + properties: + - name: approved + - name: category + - name: name + +- kind: Project + properties: + - name: category + - name: name + +# AUTOGENERATED + +# This index.yaml is automatically updated whenever the dev_appserver +# detects that a new type of query is run. If you want to manage the +# index.yaml file manually, remove the above marker line (the line +# saying "# AUTOGENERATED"). If you want to manage some indexes +# manually, move them above the marker line. The index.yaml file is +# automatically uploaded to the admin console when you next deploy +# your application using appcfg.py. diff --git a/misc/dashboard/godashboard/key.py.dummy b/misc/dashboard/godashboard/key.py.dummy new file mode 100644 index 000000000..5b8bab186 --- /dev/null +++ b/misc/dashboard/godashboard/key.py.dummy @@ -0,0 +1,10 @@ +# 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. + +# Copy this file to key.py after substituting the real key. + +# accessKey controls private access to the build server (i.e. to record new +# builds). It's tranmitted in the clear but, given the low value of the target, +# this should be sufficient. +accessKey = "this is not the real key" diff --git a/misc/dashboard/godashboard/main.html b/misc/dashboard/godashboard/main.html new file mode 100644 index 000000000..5390afce6 --- /dev/null +++ b/misc/dashboard/godashboard/main.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Build Status - Go Dashboard</title> + <link rel="stylesheet" type="text/css" href="static/style.css"> + </head> + + <body> + <a id="top"></a> + + <ul class="menu"> + <li>Build Status</li> + <li><a href="/package">Packages</a></li> + <li><a href="/project">Projects</a></li> + <li><a href="http://golang.org/">golang.org</a></li> + </ul> + + <h1>Go Dashboard</h1> + + <h2>Build Status</h2> + <table class="alternate" cellpadding="0" cellspacing="0"> + <tr> + <th></th> + {% for b in builders %} + <th class="builder">{{b.goos}}<br>{{b.goarch}}<br>{{b.note}}</th> + {% endfor %} + <th></th> + <th></th> + <th></th> + </tr> + + {% for r in revs %} + <tr> + <td class="revision"><span class="hash"><a href="https://code.google.com/p/go/source/detail?r={{r.node}}">{{r.node|slice:":12"}}</a></span></td> + + {% for b in r.builds %} + <td class="result"> + {% if b.ok %} + <span class="ok">ok</span> + {% else %} + {% if b.log %} + <a class="fail" href="/log/{{b.log}}">fail</a> + {% else %} + + {% endif %} + {% endif %} + </td> + {% endfor %} + + <td class="user">{{r.user|escape}}</td> + <td class="date">{{r.date|escape}}</td> + <td class="desc">{{r.shortdesc|escape}}</td> + </tr> + {% endfor %} + </table> + <div class="paginate"> + <a{% if prev %} href="?n={{num}}&p={{prev}}"{% else %} class="inactive"{% endif %}>prev</a> + <a{% if next %} href="?n={{num}}&p={{next}}"{% else %} class="inactive"{% endif %}>next</a> + <a{% if prev %} href="?n={{num}}&p=1"{% else %} class="inactive"{% endif %}>top</a> + </div> + </body> +</html> diff --git a/misc/dashboard/godashboard/package.html b/misc/dashboard/godashboard/package.html new file mode 100644 index 000000000..8a9d0a3a0 --- /dev/null +++ b/misc/dashboard/godashboard/package.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Packages - Go Dashboard</title> + <link rel="stylesheet" type="text/css" href="static/style.css"> + </head> + + <body> + <ul class="menu"> + <li><a href="/">Build Status</a></li> + <li>Packages</li> + <li><a href="/project">Projects</a></li> + <li><a href="http://golang.org/">golang.org</a></li> + </ul> + + <h1>Go Dashboard</h1> + + <p> + Packages listed on this page are written by third parties and + may or may not build or be safe to use. + </p> + + <p> + An "ok" in the <b>build</b> column indicates that the package is + <a href="http://golang.org/cmd/goinstall/">goinstallable</a> + with the latest + <a href="http://golang.org/doc/devel/release.html">release</a> of Go. + </p> + + <p> + The <b>info</b> column shows the first paragraph from the + <a href="http://blog.golang.org/2011/03/godoc-documenting-go-code.html">package doc comment</a>. + </p> + + <h2>Most Installed Packages (this week)</h2> + <table class="alternate" cellpadding="0" cellspacing="0"> + <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr> + {% for r in by_week_count %} + <tr> + <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td> + <td class="count">{{r.week_count}}</td> + <td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %} {% endif %}</td> + <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td> + <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td> + </tr> + {% endfor %} + </table> + + <h2>Recently Installed Packages</h2> + <table class="alternate" cellpadding="0" cellspacing="0"> + <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr> + {% for r in by_time %} + <tr> + <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td> + <td class="count">{{r.count}}</td> + <td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %} {% endif %}</td> + <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td> + <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td> + </tr> + {% endfor %} + </table> + + <h2>Most Installed Packages (all time)</h2> + <table class="alternate" cellpadding="0" cellspacing="0"> + <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr> + {% for r in by_count %} + <tr> + <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td> + <td class="count">{{r.count}}</td> + <td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %} {% endif %}</td> + <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td> + <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td> + </tr> + {% endfor %} + </table> + </body> +</html> diff --git a/misc/dashboard/godashboard/package.py b/misc/dashboard/godashboard/package.py new file mode 100644 index 000000000..5cc2d2404 --- /dev/null +++ b/misc/dashboard/godashboard/package.py @@ -0,0 +1,429 @@ +# 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. + +# This is the server part of the package dashboard. +# It must be run by App Engine. + +from google.appengine.api import mail +from google.appengine.api import memcache +from google.appengine.api import taskqueue +from google.appengine.api import urlfetch +from google.appengine.api import users +from google.appengine.ext import db +from google.appengine.ext import webapp +from google.appengine.ext.webapp import template +from google.appengine.ext.webapp.util import run_wsgi_app +import datetime +import logging +import os +import re +import sets +import urllib2 + +# local imports +from auth import auth +import toutf8 +import const + +template.register_template_library('toutf8') + +# Storage model for package info recorded on server. +class Package(db.Model): + path = db.StringProperty() + web_url = db.StringProperty() # derived from path + count = db.IntegerProperty() # grand total + week_count = db.IntegerProperty() # rolling weekly count + day_count = db.TextProperty(default='') # daily count + last_install = db.DateTimeProperty() + + # data contributed by gobuilder + info = db.StringProperty() + ok = db.BooleanProperty() + last_ok = db.DateTimeProperty() + + def get_day_count(self): + counts = {} + if not self.day_count: + return counts + for d in str(self.day_count).split('\n'): + date, count = d.split(' ') + counts[date] = int(count) + return counts + + def set_day_count(self, count): + days = [] + for day, count in count.items(): + days.append('%s %d' % (day, count)) + days.sort(reverse=True) + days = days[:28] + self.day_count = '\n'.join(days) + + def inc(self): + count = self.get_day_count() + today = str(datetime.date.today()) + count[today] = count.get(today, 0) + 1 + self.set_day_count(count) + self.update_week_count(count) + self.count += 1 + + def update_week_count(self, count=None): + if count is None: + count = self.get_day_count() + total = 0 + today = datetime.date.today() + for i in range(7): + day = str(today - datetime.timedelta(days=i)) + if day in count: + total += count[day] + self.week_count = total + + +# PackageDaily kicks off the daily package maintenance cron job +# and serves the associated task queue. +class PackageDaily(webapp.RequestHandler): + + def get(self): + # queue a task to update each package with a week_count > 0 + keys = Package.all(keys_only=True).filter('week_count >', 0) + for key in keys: + taskqueue.add(url='/package/daily', params={'key': key.name()}) + + def post(self): + # update a single package (in a task queue) + def update(key): + p = Package.get_by_key_name(key) + if not p: + return + p.update_week_count() + p.put() + key = self.request.get('key') + if not key: + return + db.run_in_transaction(update, key) + + +class Project(db.Model): + name = db.StringProperty(indexed=True) + descr = db.StringProperty() + web_url = db.StringProperty() + package = db.ReferenceProperty(Package) + category = db.StringProperty(indexed=True) + tags = db.ListProperty(str) + approved = db.BooleanProperty(indexed=True) + + +re_bitbucket = re.compile(r'^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-zA-Z0-9_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$') +re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg|git)(/[a-z0-9A-Z_.\-/]+)?$') +re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)+$') +re_launchpad = re.compile(r'^launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$') + +def vc_to_web(path): + if re_bitbucket.match(path): + m = re_bitbucket.match(path) + check_url = 'http://' + m.group(1) + '/?cmd=heads' + web = 'http://' + m.group(1) + '/' + elif re_github.match(path): + m = re_github_web.match(path) + check_url = 'https://raw.github.com/' + m.group(1) + '/' + m.group(2) + '/master/' + web = 'http://github.com/' + m.group(1) + '/' + m.group(2) + '/' + elif re_googlecode.match(path): + m = re_googlecode.match(path) + check_url = 'http://'+path + if not m.group(2): # append / after bare '/hg' or '/git' + check_url += '/' + web = 'http://code.google.com/p/' + path[:path.index('.')] + elif re_launchpad.match(path): + check_url = web = 'https://'+path + else: + return False, False + return web, check_url + +re_bitbucket_web = re.compile(r'bitbucket\.org/([a-z0-9A-Z_.\-]+)/([a-z0-9A-Z_.\-]+)') +re_googlecode_web = re.compile(r'code.google.com/p/([a-z0-9\-]+)') +re_github_web = re.compile(r'github\.com/([a-z0-9A-Z_.\-]+)/([a-z0-9A-Z_.\-]+)') +re_launchpad_web = re.compile(r'launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?') +re_striphttp = re.compile(r'https?://(www\.)?') + +def find_googlecode_vcs(path): + # Perform http request to path/hg or path/git to check if they're + # using mercurial or git. Otherwise, assume svn. + for vcs in ['git', 'hg']: + try: + response = urlfetch.fetch('http://'+path+vcs, deadline=1) + if response.status_code == 200: + return vcs + except: pass + return 'svn' + +def web_to_vc(url): + url = re_striphttp.sub('', url) + m = re_bitbucket_web.match(url) + if m: + return 'bitbucket.org/'+m.group(1)+'/'+m.group(2) + m = re_github_web.match(url) + if m: + return 'github.com/'+m.group(1)+'/'+m.group(2) + m = re_googlecode_web.match(url) + if m: + path = m.group(1)+'.googlecode.com/' + vcs = find_googlecode_vcs(path) + return path + vcs + m = re_launchpad_web.match(url) + if m: + return m.group(0) + return False + +MaxPathLength = 100 +CacheTimeout = 3600 + +class PackagePage(webapp.RequestHandler): + def get(self): + if self.request.get('fmt') == 'json': + return self.json() + + html = memcache.get('view-package') + if not html: + tdata = {} + + q = Package.all().filter('week_count >', 0) + q.order('-week_count') + tdata['by_week_count'] = q.fetch(50) + + q = Package.all() + q.order('-last_install') + tdata['by_time'] = q.fetch(20) + + q = Package.all() + q.order('-count') + tdata['by_count'] = q.fetch(100) + + path = os.path.join(os.path.dirname(__file__), 'package.html') + html = template.render(path, tdata) + memcache.set('view-package', html, time=CacheTimeout) + + self.response.headers['Content-Type'] = 'text/html; charset=utf-8' + self.response.out.write(html) + + def json(self): + json = memcache.get('view-package-json') + if not json: + q = Package.all() + s = '{"packages": [' + sep = '' + for r in q.fetch(1000): + s += '%s\n\t{"path": "%s", "last_install": "%s", "count": "%s"}' % (sep, r.path, r.last_install, r.count) + sep = ',' + s += '\n]}\n' + json = s + memcache.set('view-package-json', json, time=CacheTimeout) + self.response.set_status(200) + self.response.headers['Content-Type'] = 'text/plain; charset=utf-8' + self.response.out.write(json) + + def can_get_url(self, url): + try: + urllib2.urlopen(urllib2.Request(url)) + return True + except: + return False + + def is_valid_package_path(self, path): + return (re_bitbucket.match(path) or + re_googlecode.match(path) or + re_github.match(path) or + re_launchpad.match(path)) + + def record_pkg(self, path): + # sanity check string + if not path or len(path) > MaxPathLength or not self.is_valid_package_path(path): + return False + + # look in datastore + key = 'pkg-' + path + p = Package.get_by_key_name(key) + if p is None: + # not in datastore - verify URL before creating + web, check_url = vc_to_web(path) + if not web: + logging.error('unrecognized path: %s', path) + return False + if not self.can_get_url(check_url): + logging.error('cannot get %s', check_url) + return False + p = Package(key_name = key, path = path, count = 0, web_url = web) + + if auth(self.request): + # builder updating package metadata + p.info = self.request.get('info') + p.ok = self.request.get('ok') == "true" + if p.ok: + p.last_ok = datetime.datetime.utcnow() + else: + # goinstall reporting an install + p.inc() + p.last_install = datetime.datetime.utcnow() + + # update package object + p.put() + return True + + def post(self): + path = self.request.get('path') + ok = db.run_in_transaction(self.record_pkg, path) + if ok: + self.response.set_status(200) + self.response.out.write('ok') + else: + logging.error('invalid path in post: %s', path) + self.response.set_status(500) + self.response.out.write('not ok') + +class ProjectPage(webapp.RequestHandler): + + def get(self): + admin = users.is_current_user_admin() + if self.request.path == "/project/login": + self.redirect(users.create_login_url("/project")) + elif self.request.path == "/project/logout": + self.redirect(users.create_logout_url("/project")) + elif self.request.path == "/project/edit" and admin: + self.edit() + elif self.request.path == "/project/assoc" and admin: + self.assoc() + else: + self.list() + + def assoc(self): + projects = Project.all() + for p in projects: + if p.package: + continue + path = web_to_vc(p.web_url) + if not path: + continue + pkg = Package.get_by_key_name("pkg-"+path) + if not pkg: + self.response.out.write('no: %s %s<br>' % (p.web_url, path)) + continue + p.package = pkg + p.put() + self.response.out.write('yes: %s %s<br>' % (p.web_url, path)) + + def post(self): + if self.request.path == "/project/edit": + self.edit(True) + else: + data = dict(map(lambda x: (x, self.request.get(x)), ["name","descr","web_url"])) + if reduce(lambda x, y: x or not y, data.values(), False): + data["submitMsg"] = "You must complete all the fields." + self.list(data) + return + p = Project.get_by_key_name("proj-"+data["name"]) + if p is not None: + data["submitMsg"] = "A project by this name already exists." + self.list(data) + return + p = Project(key_name="proj-"+data["name"], **data) + p.put() + + path = os.path.join(os.path.dirname(__file__), 'project-notify.txt') + mail.send_mail( + sender=const.mail_from, + to=const.mail_submit_to, + subject=const.mail_submit_subject, + body=template.render(path, {'project': p})) + + self.list({"submitMsg": "Your project has been submitted."}) + + def list(self, additional_data={}): + cache_key = 'view-project-data' + tag = self.request.get('tag', None) + if tag: + cache_key += '-'+tag + data = memcache.get(cache_key) + admin = users.is_current_user_admin() + if admin or not data: + projects = Project.all().order('category').order('name') + if not admin: + projects = projects.filter('approved =', True) + projects = list(projects) + + tags = sets.Set() + for p in projects: + for t in p.tags: + tags.add(t) + + if tag: + projects = filter(lambda x: tag in x.tags, projects) + + data = {} + data['tag'] = tag + data['tags'] = tags + data['projects'] = projects + data['admin']= admin + if not admin: + memcache.set(cache_key, data, time=CacheTimeout) + + for k, v in additional_data.items(): + data[k] = v + + self.response.headers['Content-Type'] = 'text/html; charset=utf-8' + path = os.path.join(os.path.dirname(__file__), 'project.html') + self.response.out.write(template.render(path, data)) + + def edit(self, save=False): + if save: + name = self.request.get("orig_name") + else: + name = self.request.get("name") + + p = Project.get_by_key_name("proj-"+name) + if not p: + self.response.out.write("Couldn't find that Project.") + return + + if save: + if self.request.get("do") == "Delete": + p.delete() + else: + pkg_name = self.request.get("package", None) + if pkg_name: + pkg = Package.get_by_key_name("pkg-"+pkg_name) + if pkg: + p.package = pkg.key() + for f in ['name', 'descr', 'web_url', 'category']: + setattr(p, f, self.request.get(f, None)) + p.approved = self.request.get("approved") == "1" + p.tags = filter(lambda x: x, self.request.get("tags", "").split(",")) + p.put() + memcache.delete('view-project-data') + self.redirect('/project') + return + + # get all project categories and tags + cats, tags = sets.Set(), sets.Set() + for r in Project.all(): + cats.add(r.category) + for t in r.tags: + tags.add(t) + + self.response.headers['Content-Type'] = 'text/html; charset=utf-8' + path = os.path.join(os.path.dirname(__file__), 'project-edit.html') + self.response.out.write(template.render(path, { + "taglist": tags, "catlist": cats, "p": p, "tags": ",".join(p.tags) })) + + def redirect(self, url): + self.response.set_status(302) + self.response.headers.add_header("Location", url) + +def main(): + app = webapp.WSGIApplication([ + ('/package', PackagePage), + ('/package/daily', PackageDaily), + ('/project.*', ProjectPage), + ], debug=True) + run_wsgi_app(app) + +if __name__ == '__main__': + main() diff --git a/misc/dashboard/godashboard/project-edit.html b/misc/dashboard/godashboard/project-edit.html new file mode 100644 index 000000000..ce18fb3fb --- /dev/null +++ b/misc/dashboard/godashboard/project-edit.html @@ -0,0 +1,47 @@ +<html> +<head> +<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="http://www.google.com/jsapi"></script> +<script> +google.load("jquery", "1"); +google.load("jqueryui", "1.8.2"); +</script> +</head> +<body> +<form action="/project/edit?orig_name={{p.name}}" method="POST"> +Name:<br/> +<input type="text" name="name" value="{{p.name|escape}}"><br/> +Description:<br/> +<input type="text" name="descr" value="{{p.descr|escape}}"><br/> +Category:<br/> +<input type="text" id="cats" name="category" value="{{p.category|escape}}"><br/> +Tags: (comma-separated)<br/> +<input type="text" id="tags" name="tags" value="{{tags}}"><br/> +Web URL:<br/> +<input type="text" name="web_url" value="{{p.web_url|escape}}"><br/> +Package URL: (to link to a goinstall'd package)<br/> +<input type="text" name="package" value="{{p.package.path|escape}}"><br/> +Approved: <input type="checkbox" name="approved" value="1" {% if p.approved %}checked{% endif %}><br/> +<br/> +<input type="submit" name="do" value="Save"> +<input type="submit" name="do" value="Delete" onClick="javascript:return confirm('Delete this?');"> +</form> +<script> +var tags = [ +{% for t in taglist %} + "{{t}}"{% if not forloop.last %},{% endif %} +{% endfor %} +]; +var cats = [ +{% for c in catlist %} + "{{c}}"{% if not forloop.last %},{% endif %} +{% endfor %} +]; + +google.setOnLoadCallback(function() { + $('#tags').autocomplete({source:tags}); + $('#cats').autocomplete({source:cats}); +}); +</script> +</body> +</html> diff --git a/misc/dashboard/godashboard/project-notify.txt b/misc/dashboard/godashboard/project-notify.txt new file mode 100644 index 000000000..f55bf6421 --- /dev/null +++ b/misc/dashboard/godashboard/project-notify.txt @@ -0,0 +1,9 @@ +A new project has been submitted: + +Name: {{project.name}} +Description: {{project.descr}} +URL: {{project.web_url}} + +To edit/approve/delete: +http://godashboard.appspot.com/project/edit?name={{project.name|toutf8|urlencode}} + diff --git a/misc/dashboard/godashboard/project.html b/misc/dashboard/godashboard/project.html new file mode 100644 index 000000000..4fe1741c6 --- /dev/null +++ b/misc/dashboard/godashboard/project.html @@ -0,0 +1,85 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Projects - Go Dashboard</title> + <link rel="stylesheet" type="text/css" href="static/style.css"> + <style> + .unapproved a.name { color: red } + .tag { font-size: 0.8em; color: #666 } + </style> + </head> + + <body> + <ul class="menu"> + <li><a href="/">Build Status</a></li> + <li><a href="/package">Packages</a></li> + <li>Projects</li> + <li><a href="http://golang.org/">golang.org</a></li> + </ul> + + <h1>Go Dashboard</h1> + + <p> + These are external projects and not endorsed or supported by the Go project. + </p> + + <h2>Projects</h2> + + <div class="submit"> + <h3>Submit a Project</h3> + <p> + Using this form you can submit a project to be included in the list. + </p> + <form action="/project" method="POST"> + <table> + <tr><td>Name:<td><input type="text" name="name"> + <tr><td>Description:<td><input type="text" name="descr"> + <tr><td>URL:<td><input type="text" name="web_url"> + <tr><td> <td><input type="submit" value="Send"> + {% if submitMsg %} + <tr><td class="msg" colspan="2">{{ submitMsg }}</td></tr> + {% endif %} + </table> + </form> + </div> + + <p> + Filter by tag: + {% if tag %} + <a href="/project">all</a> + {% else %} + <b>all</b> + {% endif %} + {% for t in tags %} + {% ifequal t tag %} + <b>{{t}}</b> + {% else %} + <a href="?tag={{t}}">{{t}}</a> + {% endifequal %} + {% endfor %} + </p> + + {% for r in projects %} + {% ifchanged r.category %} + {% if not forloop.first %} + </ul> + {% endif %} + <h3>{{r.category}}</h3> + <ul> + {% endifchanged %} + <li{% if not r.approved %} class="unapproved"{% endif %}> + {% if admin %}[<a href="/project/edit?name={{r.name}}">edit</a>]{% endif %} + <a class="name" href="{{r.web_url}}">{{r.name}}</a> - {{r.descr}} + {% for tag in r.tags %} + <span class="tag">{{tag}}</span> + {% endfor %} + </li> + {% if forloop.last %} + </ul> + {% endif %} + {% endfor %} + </ul> + + + </body> +</html> diff --git a/misc/dashboard/godashboard/static/favicon.ico b/misc/dashboard/godashboard/static/favicon.ico Binary files differnew file mode 100644 index 000000000..48854ff3b --- /dev/null +++ b/misc/dashboard/godashboard/static/favicon.ico diff --git a/misc/dashboard/godashboard/static/style.css b/misc/dashboard/godashboard/static/style.css new file mode 100644 index 000000000..d6d23b536 --- /dev/null +++ b/misc/dashboard/godashboard/static/style.css @@ -0,0 +1,118 @@ +body { + font-family: sans-serif; + margin: 0; + padding: 0; +} +h1, h2, h3, ul.menu, table, p { + padding: 0 0.5em; +} +h1, h2 { + margin: 0; + background: #eee; +} +h1 { + border-bottom: 1px solid #ccc; + font-size: 1em; + padding: 0.5em; + margin-bottom: 0.5em; + text-align: right; +} +h2 { + border-top: 1px solid #ccc; + padding-left: 0.2em; +} +.submit { + float: right; + border: 1px solid #ccc; + width: 350px; + padding-bottom: 1em; + margin: 0.5em; + background: #eee; +} +.submit table { + width: 100%; +} +.submit input[type=text] { + width: 200px; +} +.submit .msg { + text-align: center; + color: red; +} +table.alternate { + white-space: nowrap; + margin: 0.5em 0; +} +table.alternate td, +table.alternate th { + padding: 0.1em 0.25em; + font-size: small; +} +table.alternate tr td:last-child { + padding-right: 0; +} +table.alternate tr:nth-child(2n) { + background-color: #f0f0f0; +} +td.result { + text-align: center; +} +span.hash { + font-family: monospace; + font-size: small; + color: #aaa; +} +td.date { + color: #aaa; +} +td.ok { + text-align: center; + color: #060; + font-weight: bold; +} +td.ok a { + cursor: help; +} +th { + text-align: left; +} +th.builder { + text-align: center; + font-weight: bold; +} +a.fail { + color: #F00; +} +a.fail:visited { + color: #900; +} +ul.menu { + margin: 0; + padding: 0; + list-style-type: none; +} +ul.menu li { + float: left; + display: block; + font-size: 1em; + padding: 0.5em; + background: #EEF; + margin-left: 0.5em; + border-left: 1px solid #999; + border-right: 1px solid #999; +} +div.paginate { + padding: 0.5em; +} +div.paginate a { + padding: 0.5em; + margin-right: 0.5em; + background: #eee; + color: blue; +} +div.paginate a.inactive { + color: #999; +} +td.time { + font-family: monospace; +} diff --git a/misc/dashboard/godashboard/toutf8.py b/misc/dashboard/godashboard/toutf8.py new file mode 100644 index 000000000..544c681b6 --- /dev/null +++ b/misc/dashboard/godashboard/toutf8.py @@ -0,0 +1,14 @@ +# 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. + +# This is a Django custom template filter to work around the +# fact that GAE's urlencode filter doesn't handle unicode strings. + +from google.appengine.ext import webapp + +register = webapp.template.create_template_register() + +@register.filter +def toutf8(value): + return value.encode("utf-8") diff --git a/misc/dashboard/googlecode_upload.py b/misc/dashboard/googlecode_upload.py new file mode 100755 index 000000000..e87db884a --- /dev/null +++ b/misc/dashboard/googlecode_upload.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python2 +# +# Copyright 2006, 2007 Google Inc. All Rights Reserved. +# Author: danderson@google.com (David Anderson) +# +# Script for uploading files to a Google Code project. +# +# This is intended to be both a useful script for people who want to +# streamline project uploads and a reference implementation for +# uploading files to Google Code projects. +# +# To upload a file to Google Code, you need to provide a path to the +# file on your local machine, a small summary of what the file is, a +# project name, and a valid account that is a member or owner of that +# project. You can optionally provide a list of labels that apply to +# the file. The file will be uploaded under the same name that it has +# in your local filesystem (that is, the "basename" or last path +# component). Run the script with '--help' to get the exact syntax +# and available options. +# +# Note that the upload script requests that you enter your +# googlecode.com password. This is NOT your Gmail account password! +# This is the password you use on googlecode.com for committing to +# Subversion and uploading files. You can find your password by going +# to http://code.google.com/hosting/settings when logged in with your +# Gmail account. If you have already committed to your project's +# Subversion repository, the script will automatically retrieve your +# credentials from there (unless disabled, see the output of '--help' +# for details). +# +# If you are looking at this script as a reference for implementing +# your own Google Code file uploader, then you should take a look at +# the upload() function, which is the meat of the uploader. You +# basically need to build a multipart/form-data POST request with the +# right fields and send it to https://PROJECT.googlecode.com/files . +# Authenticate the request using HTTP Basic authentication, as is +# shown below. +# +# Licensed under the terms of the Apache Software License 2.0: +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Questions, comments, feature requests and patches are most welcome. +# Please direct all of these to the Google Code users group: +# http://groups.google.com/group/google-code-hosting + +"""Google Code file uploader script. +""" + +__author__ = 'danderson@google.com (David Anderson)' + +import httplib +import os.path +import optparse +import getpass +import base64 +import sys + + +def upload(file, project_name, user_name, password, summary, labels=None): + """Upload a file to a Google Code project's file server. + + Args: + file: The local path to the file. + project_name: The name of your project on Google Code. + user_name: Your Google account name. + password: The googlecode.com password for your account. + Note that this is NOT your global Google Account password! + summary: A small description for the file. + labels: an optional list of label strings with which to tag the file. + + Returns: a tuple: + http_status: 201 if the upload succeeded, something else if an + error occurred. + http_reason: The human-readable string associated with http_status + file_url: If the upload succeeded, the URL of the file on Google + Code, None otherwise. + """ + # The login is the user part of user@gmail.com. If the login provided + # is in the full user@domain form, strip it down. + if user_name.endswith('@gmail.com'): + user_name = user_name[:user_name.index('@gmail.com')] + + form_fields = [('summary', summary)] + if labels is not None: + form_fields.extend([('label', l.strip()) for l in labels]) + + content_type, body = encode_upload_request(form_fields, file) + + upload_host = '%s.googlecode.com' % project_name + upload_uri = '/files' + auth_token = base64.b64encode('%s:%s'% (user_name, password)) + headers = { + 'Authorization': 'Basic %s' % auth_token, + 'User-Agent': 'Googlecode.com uploader v0.9.4', + 'Content-Type': content_type, + } + + server = httplib.HTTPSConnection(upload_host) + server.request('POST', upload_uri, body, headers) + resp = server.getresponse() + server.close() + + if resp.status == 201: + location = resp.getheader('Location', None) + else: + location = None + return resp.status, resp.reason, location + + +def encode_upload_request(fields, file_path): + """Encode the given fields and file into a multipart form body. + + fields is a sequence of (name, value) pairs. file is the path of + the file to upload. The file will be uploaded to Google Code with + the same file name. + + Returns: (content_type, body) ready for httplib.HTTP instance + """ + BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla' + CRLF = '\r\n' + + body = [] + + # Add the metadata about the upload first + for key, value in fields: + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="%s"' % key, + '', + value, + ]) + + # Now add the file itself + file_name = os.path.basename(file_path) + f = open(file_path, 'rb') + file_content = f.read() + f.close() + + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="filename"; filename="%s"' + % file_name, + # The upload server determines the mime-type, no need to set it. + 'Content-Type: application/octet-stream', + '', + file_content, + ]) + + # Finalize the form body + body.extend(['--' + BOUNDARY + '--', '']) + + return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body) + + +def upload_find_auth(file_path, project_name, summary, labels=None, + user_name=None, password=None, tries=3): + """Find credentials and upload a file to a Google Code project's file server. + + file_path, project_name, summary, and labels are passed as-is to upload. + + Args: + file_path: The local path to the file. + project_name: The name of your project on Google Code. + summary: A small description for the file. + labels: an optional list of label strings with which to tag the file. + config_dir: Path to Subversion configuration directory, 'none', or None. + user_name: Your Google account name. + tries: How many attempts to make. + """ + + while tries > 0: + if user_name is None: + # Read username if not specified or loaded from svn config, or on + # subsequent tries. + sys.stdout.write('Please enter your googlecode.com username: ') + sys.stdout.flush() + user_name = sys.stdin.readline().rstrip() + if password is None: + # Read password if not loaded from svn config, or on subsequent tries. + print 'Please enter your googlecode.com password.' + print '** Note that this is NOT your Gmail account password! **' + print 'It is the password you use to access Subversion repositories,' + print 'and can be found here: http://code.google.com/hosting/settings' + password = getpass.getpass() + + status, reason, url = upload(file_path, project_name, user_name, password, + summary, labels) + # Returns 403 Forbidden instead of 401 Unauthorized for bad + # credentials as of 2007-07-17. + if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]: + # Rest for another try. + user_name = password = None + tries = tries - 1 + else: + # We're done. + break + + return status, reason, url + + +def main(): + parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY ' + '-p PROJECT [options] FILE') + parser.add_option('-s', '--summary', dest='summary', + help='Short description of the file') + parser.add_option('-p', '--project', dest='project', + help='Google Code project name') + parser.add_option('-u', '--user', dest='user', + help='Your Google Code username') + parser.add_option('-w', '--password', dest='password', + help='Your Google Code password') + parser.add_option('-l', '--labels', dest='labels', + help='An optional list of comma-separated labels to attach ' + 'to the file') + + options, args = parser.parse_args() + + if not options.summary: + parser.error('File summary is missing.') + elif not options.project: + parser.error('Project name is missing.') + elif len(args) < 1: + parser.error('File to upload not provided.') + elif len(args) > 1: + parser.error('Only one file may be specified.') + + file_path = args[0] + + if options.labels: + labels = options.labels.split(',') + else: + labels = None + + status, reason, url = upload_find_auth(file_path, options.project, + options.summary, labels, + options.user, options.password) + if url: + print 'The file was uploaded successfully.' + print 'URL: %s' % url + return 0 + else: + print 'An error occurred. Your file was not uploaded.' + print 'Google Code upload server said: %s (%s)' % (reason, status) + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/misc/emacs/go-mode-load.el b/misc/emacs/go-mode-load.el new file mode 100644 index 000000000..d453166a4 --- /dev/null +++ b/misc/emacs/go-mode-load.el @@ -0,0 +1,50 @@ +;;; go-mode-load.el --- Major mode for the Go programming language + +;;; 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")) + + +;;;### (autoloads (gofmt-before-save gofmt go-mode) "go-mode" "go-mode.el" +;;;;;; (19917 17808)) +;;; 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. + +\(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) + +(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) + +\(fn)" t nil) + +;;;*** + +(provide 'go-mode-load) diff --git a/misc/emacs/go-mode.el b/misc/emacs/go-mode.el new file mode 100644 index 000000000..ba7f72397 --- /dev/null +++ b/misc/emacs/go-mode.el @@ -0,0 +1,544 @@ +;;; go-mode.el --- Major mode for the Go programming language + +;;; Commentary: + +;; For installation instructions, see go-mode-load.el + +;;; To do: + +;; * 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 + +;;; Code: + +(eval-when-compile (require 'cl)) + +(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 ?* "." st) + (modify-syntax-entry ?/ "." st) + (modify-syntax-entry ?% "." st) + (modify-syntax-entry ?& "." st) + (modify-syntax-entry ?| "." st) + (modify-syntax-entry ?^ "." st) + (modify-syntax-entry ?! "." st) + (modify-syntax-entry ?= "." st) + (modify-syntax-entry ?< "." st) + (modify-syntax-entry ?> "." st) + + ;; Strings + (modify-syntax-entry ?\" "\"" st) + (modify-syntax-entry ?\' "\"" st) + (modify-syntax-entry ?` "\"" st) + (modify-syntax-entry ?\\ "\\" st) + + ;; Comments + (modify-syntax-entry ?/ ". 124b" st) + (modify-syntax-entry ?* ". 23" st) + (modify-syntax-entry ?\n "> b" st) + (modify-syntax-entry ?\^m "> b" 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" "imag" "len" + "make" "new" "panic" "print" "println" "real" "recover")) + (constants '("nil" "true" "false" "iota")) + (type-name "\\s *\\(?:[*(]\\s *\\)*\\(?:\\w+\\s *\\.\\s *\\)?\\(\\w+\\)") + ) + `((,(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 +;; + +(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-delayed-electric) + ;; In case we get : indentation wrong, correct ourselves + (define-key m "=" #'go-mode-insert-and-indent) + m) + "Keymap used by Go mode to implement electric keys.") + +(defun go-mode-insert-and-indent (key) + "Invoke the global binding of KEY, then reindent the line." + + (interactive (list (this-command-keys))) + (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-cs-state nil + "The `parse-partial-sexp' state of the comment/string parser as +of the point `go-mode-mark-cs-end'.") +(make-variable-buffer-local 'go-mode-mark-cs-state) + +(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-cache (b e l) + "An after-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-text-properties b (min go-mode-mark-cs-end (point-max)) '(go-mode-cs nil)) + (setq go-mode-mark-cs-end b + go-mode-mark-cs-state nil)) + + (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))))))) + +(defsubst 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))) + (if (= pos 1) + nil + (when (> pos go-mode-mark-cs-end) + (go-mode-mark-cs pos)) + (get-text-property (- pos 1) '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 + (let* ((pos go-mode-mark-cs-end) + (state (or go-mode-mark-cs-state (syntax-ppss pos)))) + ;; Mark comments and strings + (when (nth 8 state) + ;; Get to the beginning of the comment/string + (setq pos (nth 8 state) + state nil)) + (while (> end pos) + ;; Find beginning of comment/string + (while (and (> end pos) + (progn + (setq state (parse-partial-sexp pos end nil nil state 'syntax-table) + pos (point)) + (not (nth 8 state))))) + ;; Find end of comment/string + (let ((start (nth 8 state))) + (when start + (setq state (parse-partial-sexp pos (point-max) nil nil state 'syntax-table) + pos (point)) + ;; Mark comment + (put-text-property start (- pos 1) 'go-mode-cs (cons start pos)) + (when nil + (put-text-property start (- pos 1) 'face + `((:background "midnight blue"))))))) + ;; Update state + (setq go-mode-mark-cs-end pos + go-mode-mark-cs-state state)))) + +(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-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." + + (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? + (cond + ((and cs (save-excursion + (goto-char (car cs)) + (looking-at "\\s\""))) + ;; 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)) + (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)) + (forward-comment (- (buffer-size))) + ;; Really just want the token before + (when (looking-back "\\<import\\|const\\|var\\|type" + (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)) + + ;; Continuation lines are indented 1 level + (forward-comment (- (buffer-size))) + (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))))))) + +(defun go-mode-indent-line () + "Indent the current line according to `go-mode-indentation'." + (interactive) + + (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 +;; + +;;;###autoload +(define-derived-mode go-mode nil "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." + + ;; 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) + (remove-text-properties 1 (point-max) + '(go-mode-cs nil go-mode-nesting nil))) + + ;; Reset the syntax mark caches + (setq go-mode-mark-cs-end 1 + go-mode-mark-cs-state nil + go-mode-mark-nesting-end 1) + (add-hook 'after-change-functions #'go-mode-mark-clear-cache nil t) + + ;; Indentation + (set (make-local-variable 'indent-line-function) + #'go-mode-indent-line) + (add-hook 'after-change-functions #'go-mode-delayed-electric-hook nil t) + + ;; Comments + (set (make-local-variable 'comment-start) "// ") + (set (make-local-variable 'comment-end) "") + + ;; Go style + (setq indent-tabs-mode 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." + + (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." + + (interactive) + (let ((srcbuf (current-buffer))) + (with-temp-buffer + (let ((outbuf (current-buffer)) + (errbuf (get-buffer-create "*Gofmt Errors*")) + (coding-system-for-read 'utf-8) ;; use utf-8 with subprocesses + (coding-system-for-write 'utf-8)) + (with-current-buffer errbuf (erase-buffer)) + (with-current-buffer srcbuf + (save-restriction + (let (deactivate-mark) + (widen) + (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" + outbuf nil errbuf)) + ;; gofmt succeeded: replace the current buffer with outbuf, + ;; restore the mark and point, and discard errbuf. + (let ((old-mark (mark t)) (old-point (point))) + (erase-buffer) + (insert-buffer-substring outbuf) + (goto-char (min old-point (point-max))) + (if old-mark (push-mark (min old-mark (point-max)) t)) + (kill-buffer errbuf)) + + ;; gofmt failed: display the errors + (display-buffer errbuf))))) + + ;; Collapse any window opened on outbuf if shell-command-on-region + ;; displayed it. + (delete-windows-on outbuf))))) + +;;;###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)" + + (interactive) + (when (eq major-mode 'go-mode) (gofmt))) + +(provide 'go-mode) diff --git a/misc/fraise/go.plist b/misc/fraise/go.plist new file mode 100644 index 000000000..17f416221 --- /dev/null +++ b/misc/fraise/go.plist @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>beginCommand</key> + <string></string> + <key>endCommand</key> + <string></string> + <key>beginInstruction</key> + <string></string> + <key>endInstruction</key> + <string></string> + <key>beginVariable</key> + <string></string> + <key>endVariable</key> + <string></string> + <key>firstString</key> + <string>"</string> + <key>secondString</key> + <string>'</string> + <key>firstSingleLineComment</key> + <string>//</string> + <key>secondSingleLineComment</key> + <string></string> + <key>beginFirstMultiLineComment</key> + <string>/*</string> + <key>endFirstMultiLineComment</key> + <string>*/</string> + <key>beginSecondMultiLineComment</key> + <string></string> + <key>endSecondMultiLineComment</key> + <string></string> + <key>functionDefinition</key> + <string>^func\s*.*\(.*\)\s?\{</string> + <key>removeFromFunction</key> + <string></string> + <key>keywordsCaseSensitive</key> + <true/> + <key>recolourKeywordIfAlreadyColoured</key> + <true/> + <key>keywords</key> + <array> + <string>break</string> + <string>case</string> + <string>chan</string> + <string>const</string> + <string>continue</string> + <string>default</string> + <string>defer</string> + <string>else</string> + <string>fallthrough</string> + <string>for</string> + <string>func</string> + <string>go</string> + <string>goto</string> + <string>if</string> + <string>import</string> + <string>interface</string> + <string>map</string> + <string>package</string> + <string>range</string> + <string>return</string> + <string>select</string> + <string>struct</string> + <string>switch</string> + <string>type</string> + <string>var</string> + <string>bool</string> + <string>byte</string> + <string>chan</string> + <string>complex64</string> + <string>complex128</string> + <string>float32</string> + <string>float64</string> + <string>int</string> + <string>int8</string> + <string>int16</string> + <string>int32</string> + <string>int64</string> + <string>map</string> + <string>string</string> + <string>uint</string> + <string>uintptr</string> + <string>uint8</string> + <string>uint16</string> + <string>uint32</string> + <string>uint64</string> + </array> + <key>autocompleteWords</key> + <array/> +</dict> +</plist> diff --git a/misc/fraise/readme.txt b/misc/fraise/readme.txt new file mode 100644 index 000000000..fb0f2c8c1 --- /dev/null +++ b/misc/fraise/readme.txt @@ -0,0 +1,16 @@ +##Instructions for enabling Go syntax highlighting in Fraise.app## +1. Move go.plist to /Applications/Fraise.app/Contents/Resources/Syntax\ Definitions/ +2. Open /Applications/Fraise.app/Contents/Resources/SyntaxDefinitions.plist and add + + <dict> + <key>name</key> + <string>GoogleGo</string> + <key>file</key> + <string>go</string> + <key>extensions</key> + <string>go</string> + </dict> + +before </array> + +3. Restart Fraise and you're good to Go!
\ No newline at end of file diff --git a/misc/godoc/README b/misc/godoc/README new file mode 100644 index 000000000..3c8d830e4 --- /dev/null +++ b/misc/godoc/README @@ -0,0 +1,22 @@ +Instructions to get an initial godoc running on a local app engine emulator +--------------------------------------------------------------------------- + +To run godoc under the app engine emulator, create a ("goroot") godoc +directory that contains the app.yaml file, the doc and lib directories +from the Go distribution, as well as a godoc directory with the godoc +sources from src/cmd/godoc. In the godoc source directory, replace +main.go with init.go. The directory structure should look as follows: + +godoc // "goroot" directory + app.yaml // app engine control file + doc // goroot/doc directory + favicon.ico + godoc // contains godoc sources + godoc.go // unchanged godoc file + init.go // this file instead of godoc/main.go + ... // remaining godoc files + lib // goroot/lib directory + +Run app engine emulator locally: dev_appserver.py -a <hostname> godoc +where godoc is the top-level "goroot" directory. The godoc home page +is then served at: <hostname>:8080 . diff --git a/misc/godoc/app.yaml b/misc/godoc/app.yaml new file mode 100644 index 000000000..f8b46db31 --- /dev/null +++ b/misc/godoc/app.yaml @@ -0,0 +1,12 @@ +# 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. + +application: godoc +version: 1 +runtime: go +api_version: 1 + +handlers: +- url: /.* + script: _go_app diff --git a/misc/godoc/init.go b/misc/godoc/init.go new file mode 100644 index 000000000..0fd0bd542 --- /dev/null +++ b/misc/godoc/init.go @@ -0,0 +1,35 @@ +// 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. + +// This file replaces main.go when running godoc under the app engine emulator. +// See the README file for instructions. + +package main + +import ( + "http" + "log" + "os" + "path/filepath" +) + +func serveError(w http.ResponseWriter, r *http.Request, relpath string, err os.Error) { + contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path! + w.WriteHeader(http.StatusNotFound) + servePage(w, "File "+relpath, "", "", contents) +} + +func init() { + // set goroot + cwd, err := os.Getwd() + if err != nil { + log.Fatalf("cwd: %s", err) + } + log.Printf("cwd = %s", cwd) + *goroot = filepath.Clean(cwd) + + initHandlers() + readTemplates() + registerPublicHandlers(http.DefaultServeMux) +} diff --git a/misc/goplay/Makefile b/misc/goplay/Makefile new file mode 100644 index 000000000..28d024511 --- /dev/null +++ b/misc/goplay/Makefile @@ -0,0 +1,13 @@ +# 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. + +include ../../src/Make.inc + +TARG=goplay + +GOFILES=\ + goplay.go\ + +include ../../src/Make.cmd + diff --git a/misc/goplay/README b/misc/goplay/README new file mode 100644 index 000000000..e8a1d290f --- /dev/null +++ b/misc/goplay/README @@ -0,0 +1 @@ +See doc.go. diff --git a/misc/goplay/doc.go b/misc/goplay/doc.go new file mode 100644 index 000000000..9685551bd --- /dev/null +++ b/misc/goplay/doc.go @@ -0,0 +1,25 @@ +// 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. + +// 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, first build and install it: +// $ cd $GOROOT/misc/goplay +// $ gomake install +// Then, run it: +// $ goplay +// 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 +// the -http parameter. Do so at your own risk. +package documentation diff --git a/misc/goplay/goplay.go b/misc/goplay/goplay.go new file mode 100644 index 000000000..bbc388ba4 --- /dev/null +++ b/misc/goplay/goplay.go @@ -0,0 +1,280 @@ +// 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. + +package main + +import ( + "exec" + "flag" + "http" + "io" + "io/ioutil" + "log" + "os" + "runtime" + "strconv" + "template" +) + +var ( + httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on") + htmlOutput = flag.Bool("html", false, "render program output as HTML") +) + +var ( + // a source of numbers, for naming temporary files + uniq = make(chan int) + // the architecture-identifying character of the tool chain, 5, 6, or 8 + archChar string +) + +func main() { + flag.Parse() + + // set archChar + switch runtime.GOARCH { + case "arm": + archChar = "5" + case "amd64": + archChar = "6" + case "386": + archChar = "8" + default: + log.Fatalln("unrecognized GOARCH:", runtime.GOARCH) + } + + // source of unique numbers + go func() { + for i := 0; ; i++ { + uniq <- i + } + }() + + http.HandleFunc("/", FrontPage) + http.HandleFunc("/compile", Compile) + log.Fatal(http.ListenAndServe(*httpListen, nil)) +} + +// 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. +func FrontPage(w http.ResponseWriter, req *http.Request) { + data, err := ioutil.ReadFile(req.URL.Path[1:]) + if err != nil { + data = helloWorld + } + frontPage.Execute(w, data) +} + +// Compile is an HTTP handler that reads Go source code from the request, +// compiles and links the code (returning any errors), runs the program, +// and sends the program's output as the HTTP response. +func Compile(w http.ResponseWriter, req *http.Request) { + // x is the base name for .go, .6, executable files + x := os.TempDir() + "/compile" + strconv.Itoa(<-uniq) + src := x + ".go" + obj := x + "." + archChar + bin := x + if runtime.GOOS == "windows" { + bin += ".exe" + } + + // write request Body to x.go + f, err := os.Create(src) + if err != nil { + error(w, nil, err) + return + } + defer os.Remove(src) + defer f.Close() + _, err = io.Copy(f, req.Body) + if err != nil { + error(w, nil, err) + return + } + f.Close() + + // build x.go, creating x.6 + out, err := run(archChar+"g", "-o", obj, src) + defer os.Remove(obj) + if err != nil { + error(w, out, err) + return + } + + // link x.6, creating x (the program binary) + out, err = run(archChar+"l", "-o", bin, obj) + defer os.Remove(bin) + if err != nil { + error(w, out, err) + return + } + + // run x + out, err = run(bin) + if err != nil { + error(w, out, err) + } + + // write the output of x as the http response + if *htmlOutput { + w.Write(out) + } else { + output.Execute(w, out) + } +} + +// error writes compile, link, or runtime errors to the HTTP connection. +// The JavaScript interface uses the 404 status code to identify the error. +func error(w http.ResponseWriter, out []byte, err os.Error) { + w.WriteHeader(404) + if out != nil { + output.Execute(w, out) + } else { + output.Execute(w, err.String()) + } +} + +// run executes the specified command and returns its output and an error. +func run(cmd ...string) ([]byte, os.Error) { + return exec.Command(cmd[0], cmd[1:]...).CombinedOutput() +} + +var frontPage = template.Must(template.New("frontPage").Parse(frontPageText)) // HTML template +var output = template.Must(template.New("output").Parse(outputText)) // HTML template + +var outputText = `<pre>{{html .}}</pre>` + +var frontPageText = `<!doctype html> +<html> +<head> +<style> +pre, textarea { + font-family: Monaco, 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 100%; +} +.hints { + font-size: 0.8em; + text-align: right; +} +#edit, #output, #errors { width: 100%; text-align: left; } +#edit { height: 500px; } +#output { color: #00c; } +#errors { color: #c00; } +</style> +<script> + +function insertTabs(n) { + // find the selection start and end + var cont = document.getElementById("edit"); + var start = cont.selectionStart; + var end = cont.selectionEnd; + // split the textarea content into two, and insert n tabs + var v = cont.value; + var u = v.substr(0, start); + for (var i=0; i<n; i++) { + u += "\t"; + } + u += v.substr(end); + // set revised content + cont.value = u; + // reset caret position after inserted tabs + cont.selectionStart = start+n; + cont.selectionEnd = start+n; +} + +function autoindent(el) { + var curpos = el.selectionStart; + var tabs = 0; + while (curpos > 0) { + curpos--; + if (el.value[curpos] == "\t") { + tabs++; + } else if (tabs > 0 || el.value[curpos] == "\n") { + break; + } + } + setTimeout(function() { + insertTabs(tabs); + }, 1); +} + +function keyHandler(event) { + var e = window.event || event; + if (e.keyCode == 9) { // tab + insertTabs(1); + e.preventDefault(); + return false; + } + if (e.keyCode == 13) { // enter + if (e.shiftKey) { // +shift + compile(e.target); + e.preventDefault(); + return false; + } else { + autoindent(e.target); + } + } + return true; +} + +var xmlreq; + +function autocompile() { + if(!document.getElementById("autocompile").checked) { + return; + } + compile(); +} + +function compile() { + var prog = document.getElementById("edit").value; + var req = new XMLHttpRequest(); + xmlreq = req; + req.onreadystatechange = compileUpdate; + req.open("POST", "/compile", true); + req.setRequestHeader("Content-Type", "text/plain; charset=utf-8"); + req.send(prog); +} + +function compileUpdate() { + var req = xmlreq; + if(!req || req.readyState != 4) { + return; + } + if(req.status == 200) { + document.getElementById("output").innerHTML = req.responseText; + document.getElementById("errors").innerHTML = ""; + } else { + document.getElementById("errors").innerHTML = req.responseText; + document.getElementById("output").innerHTML = ""; + } +} +</script> +</head> +<body> +<table width="100%"><tr><td width="60%" valign="top"> +<textarea autofocus="true" id="edit" spellcheck="false" onkeydown="keyHandler(event);" onkeyup="autocompile();">{{html .}}</textarea> +<div class="hints"> +(Shift-Enter to compile and run.) +<input type="checkbox" id="autocompile" value="checked" /> Compile and run after each keystroke +</div> +<td width="3%"> +<td width="27%" align="right" valign="top"> +<div id="output"></div> +</table> +<div id="errors"></div> +</body> +</html> +` + +var helloWorld = []byte(`package main + +import "fmt" + +func main() { + fmt.Println("hello, world") +} +`) diff --git a/misc/kate/go.xml b/misc/kate/go.xml new file mode 100644 index 000000000..14d88b26a --- /dev/null +++ b/misc/kate/go.xml @@ -0,0 +1,147 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE language SYSTEM "language.dtd"> +<language name="Go" section="Sources" + version="1.00" kateversion="2.4" + indenter="cstyle" + extensions="*.go" + mimetype="" + priority="5" + author="The Go Authors" + license="BSD"> + <highlighting> + <list name="keywords"> + <item> break </item> + <item> case </item> + <item> chan </item> + <item> const </item> + <item> continue </item> + <item> default </item> + <item> defer </item> + <item> else </item> + <item> fallthrough </item> + <item> for </item> + <item> func </item> + <item> go </item> + <item> goto </item> + <item> if </item> + <item> import </item> + <item> interface </item> + <item> map </item> + <item> package </item> + <item> range </item> + <item> return </item> + <item> select </item> + <item> struct </item> + <item> switch </item> + <item> type </item> + <item> var </item> + </list> + <list name="predeclared"> + <item> false </item> + <item> iota </item> + <item> nil </item> + <item> true </item> + </list> + <list name="types"> + <item> bool </item> + <item> byte </item> + <item> complex64 </item> + <item> complex128 </item> + <item> float32 </item> + <item> float64 </item> + <item> int </item> + <item> int8 </item> + <item> int16 </item> + <item> int32 </item> + <item> int64 </item> + <item> string </item> + <item> uint </item> + <item> uintptr </item> + <item> uint8 </item> + <item> uint16 </item> + <item> uint32 </item> + <item> uint64 </item> + </list> + <list name="functions"> + <item> append </item> + <item> cap </item> + <item> close </item> + <item> complex </item> + <item> copy </item> + <item> imag </item> + <item> len </item> + <item> make </item> + <item> new </item> + <item> panic </item> + <item> print </item> + <item> println </item> + <item> real </item> + <item> recover </item> + </list> + <contexts> + <context attribute="Normal Text" lineEndContext="#stay" name="Normal"> + <DetectSpaces /> + <keyword attribute="Keyword" context="#stay" String="keywords"/> + <keyword attribute="Predeclared Identifier" context="#stay" + String="predeclared"/> + <keyword attribute="Data Type" context="#stay" String="types"/> + <keyword attribute="Builtin Function" context="#stay" String="functions"/> + <DetectIdentifier /> + <DetectChar attribute="Symbol" context="#stay" char="{" beginRegion="Brace1" /> + <DetectChar attribute="Symbol" context="#stay" char="}" endRegion="Brace1" /> + <HlCOct attribute="Octal" context="#stay"/> + <HlCHex attribute="Hex" context="#stay"/> + <HlCChar attribute="Char" context="#stay"/> + <DetectChar attribute="String" context="String" char="""/> + <DetectChar attribute="Multiline String" context="Multiline String" char="`"/> + <Detect2Chars attribute="Comment" context="Comment 1" char="/" char1="/"/> + <Detect2Chars attribute="Comment" context="Comment 2" char="/" char1="*" beginRegion="Comment"/> + <AnyChar attribute="Symbol" context="#stay" String=":!%&()+,-/.*<=>?[]|~^;"/> + </context> + + <context attribute="String" lineEndContext="#pop" name="String"> + <LineContinue attribute="String" context="#stay"/> + <HlCStringChar attribute="String Char" context="#stay"/> + <DetectChar attribute="String" context="#pop" char="""/> + </context> + + <context attribute="String" lineEndContext="#stay" name="Multiline String"> + <LineContinue attribute="String" context="#stay"/> + <HlCStringChar attribute="String Char" context="#stay"/> + <DetectChar attribute="String" context="#pop" char="`"/> + </context> + + <context attribute="Comment" lineEndContext="#pop" name="Comment 1"> + <LineContinue attribute="Comment" context="#stay"/> + </context> + + <context attribute="Comment" lineEndContext="#stay" name="Comment 2"> + <Detect2Chars attribute="Comment" context="#pop" char="*" char1="/" endRegion="Comment"/> + </context> + </contexts> + <itemDatas> + <itemData name="Normal Text" defStyleNum="dsNormal" spellChecking="false"/> + <itemData name="Keyword" defStyleNum="dsKeyword" spellChecking="false"/> + <itemData name="Predeclared Identifier" defStyleNum="dsOthers" spellChecking="false"/> + <itemData name="Builtin Function" defStyleNum="dsFunction" spellChecking="false"/> + <itemData name="Data Type" defStyleNum="dsDataType" spellChecking="false"/> + <itemData name="Decimal" defStyleNum="dsDecVal" spellChecking="false"/> + <itemData name="Octal" defStyleNum="dsBaseN" spellChecking="false"/> + <itemData name="Hex" defStyleNum="dsBaseN" spellChecking="false"/> + <itemData name="Float" defStyleNum="dsFloat" spellChecking="false"/> + <itemData name="Char" defStyleNum="dsChar" spellChecking="false"/> + <itemData name="String" defStyleNum="dsString"/> + <itemData name="String Char" defStyleNum="dsChar"/> + <itemData name="Comment" defStyleNum="dsComment"/> + <itemData name="Symbol" defStyleNum="dsNormal" spellChecking="false"/> + <itemData name="Error" defStyleNum="dsError" spellChecking="false"/> + </itemDatas> + </highlighting> + <general> + <comments> + <comment name="singleLine" start="//" /> + <comment name="multiLine" start="/*" end="*/" /> + </comments> + <keywords casesensitive="1" additionalDeliminator="'"" /> + </general> +</language> diff --git a/misc/notepadplus/README b/misc/notepadplus/README new file mode 100755 index 000000000..000d31746 --- /dev/null +++ b/misc/notepadplus/README @@ -0,0 +1,8 @@ +Given a Notepad++ installation at <DIR>: + +1. Add the contents of userDefineLang.xml at <DIR>\userDefineLang.xml + between <NotepadPlus> ... </NotepadPlus> + +2. Copy go.xml to <DIR>\plugins\APIs + +3. Restart Notepad++ diff --git a/misc/notepadplus/go.xml b/misc/notepadplus/go.xml new file mode 100755 index 000000000..7c5d8a173 --- /dev/null +++ b/misc/notepadplus/go.xml @@ -0,0 +1,66 @@ +<NotepadPlus> + <!-- Go Programming Language builtins and keywords --> + <AutoComplete> + <KeyWord name="append"/> + <KeyWord name="bool" /> + <KeyWord name="break" /> + <KeyWord name="byte" /> + <KeyWord name="cap" /> + <KeyWord name="case" /> + <KeyWord name="chan" /> + <KeyWord name="close" /> + <KeyWord name="complex" /> + <KeyWord name="complex128" /> + <KeyWord name="complex64" /> + <KeyWord name="const" /> + <KeyWord name="continue" /> + <KeyWord name="copy" /> + <KeyWord name="default" /> + <KeyWord name="defer" /> + <KeyWord name="else" /> + <KeyWord name="fallthrough" /> + <KeyWord name="false" /> + <KeyWord name="float32" /> + <KeyWord name="float64" /> + <KeyWord name="for" /> + <KeyWord name="func" /> + <KeyWord name="go" /> + <KeyWord name="goto" /> + <KeyWord name="if" /> + <KeyWord name="iota" /> + <KeyWord name="imag" /> + <KeyWord name="import" /> + <KeyWord name="int" /> + <KeyWord name="int16" /> + <KeyWord name="int32" /> + <KeyWord name="int64" /> + <KeyWord name="int8" /> + <KeyWord name="interface" /> + <KeyWord name="len" /> + <KeyWord name="make" /> + <KeyWord name="map" /> + <KeyWord name="new" /> + <KeyWord name="nil" /> + <KeyWord name="package" /> + <KeyWord name="panic" /> + <KeyWord name="print" /> + <KeyWord name="println" /> + <KeyWord name="range" /> + <KeyWord name="real" /> + <KeyWord name="recover" /> + <KeyWord name="return" /> + <KeyWord name="select" /> + <KeyWord name="string" /> + <KeyWord name="struct" /> + <KeyWord name="switch" /> + <KeyWord name="true" /> + <KeyWord name="type" /> + <KeyWord name="uint" /> + <KeyWord name="uint16" /> + <KeyWord name="uint32" /> + <KeyWord name="uint64" /> + <KeyWord name="uint8" /> + <KeyWord name="uintptr" /> + <KeyWord name="var" /> + </AutoComplete> +</NotepadPlus> diff --git a/misc/notepadplus/userDefineLang.xml b/misc/notepadplus/userDefineLang.xml new file mode 100755 index 000000000..d1927a340 --- /dev/null +++ b/misc/notepadplus/userDefineLang.xml @@ -0,0 +1,36 @@ +<!-- <NotepadPlus> --> + <UserLang name="go" ext="go"> + <Settings> + <Global caseIgnored="no" /> + <TreatAsSymbol comment="no" commentLine="no" /> + <Prefix words1="no" words2="no" words3="no" words4="no" /> + </Settings> + <KeywordLists> + <Keywords name="Delimiters">"`0"`</Keywords> + <Keywords name="Folder+"></Keywords> + <Keywords name="Folder-"></Keywords> + <Keywords name="Operators">( ) [ ] { } ... . , _ & ^ % > < ! = + - * | :</Keywords> + <Keywords name="Comment"> 1/* 2*/ 0//</Keywords> + <Keywords name="Words1">append bool break byte cap case chan close complex complex128 complex64 const continue copy default defer else fallthrough false float32 float64 for func go goto if iota imag import int int16 int32 int64 int8 interface len make map new nil package panic print println range real recover return select string struct switch true type uint uint16 uint32 uint64 uint8 uintptr var</Keywords> + <Keywords name="Words2"></Keywords> + <Keywords name="Words3"></Keywords> + <Keywords name="Words4"></Keywords> + </KeywordLists> + <Styles> + <WordsStyle name="DEFAULT" styleID="11" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="FOLDEROPEN" styleID="12" fgColor="FFFF00" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="FOLDERCLOSE" styleID="13" fgColor="0B243B" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="KEYWORD1" styleID="5" fgColor="AA0000" bgColor="FFFFFF" fontName="" fontStyle="1" /> + <WordsStyle name="KEYWORD2" styleID="6" fgColor="AA0000" bgColor="FFFFFF" fontName="" fontStyle="1" /> + <WordsStyle name="KEYWORD3" styleID="7" fgColor="AA0000" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="KEYWORD4" styleID="8" fgColor="A00000" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="COMMENT" styleID="1" fgColor="AAAAAA" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="COMMENT LINE" styleID="2" fgColor="AAAAAA" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="NUMBER" styleID="4" fgColor="A52A2A" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="OPERATOR" styleID="10" fgColor="8000FF" bgColor="FFFFFF" fontName="" fontStyle="1" /> + <WordsStyle name="DELIMINER1" styleID="14" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="DELIMINER2" styleID="15" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="0" /> + <WordsStyle name="DELIMINER3" styleID="16" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="0" /> + </Styles> + </UserLang> +<!-- </NotepadPlus> --> diff --git a/misc/swig/callback/Makefile b/misc/swig/callback/Makefile new file mode 100644 index 000000000..fde0d107b --- /dev/null +++ b/misc/swig/callback/Makefile @@ -0,0 +1,17 @@ +# 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) $*.go + $(LD) $(SWIG_RPATH) -o $@ $*.$O diff --git a/misc/swig/callback/callback.h b/misc/swig/callback/callback.h new file mode 100644 index 000000000..80232a8b3 --- /dev/null +++ b/misc/swig/callback/callback.h @@ -0,0 +1,24 @@ +// 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. + +class Callback { +public: + virtual ~Callback() { } + virtual std::string run() { return "Callback::run"; } +}; + +class Caller { +private: + Callback *callback_; +public: + Caller(): callback_(0) { } + ~Caller() { delCallback(); } + void delCallback() { delete callback_; callback_ = 0; } + void setCallback(Callback *cb) { delCallback(); callback_ = cb; } + std::string call() { + if (callback_ != 0) + return callback_->run(); + return ""; + } +}; diff --git a/misc/swig/callback/callback.swigcxx b/misc/swig/callback/callback.swigcxx new file mode 100644 index 000000000..0c97ef101 --- /dev/null +++ b/misc/swig/callback/callback.swigcxx @@ -0,0 +1,18 @@ +/* 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. */ + +/* An example of writing a C++ virtual function in Go. */ + +%module(directors="1") callback + +%{ +#include <string> +#include "callback.h" +%} + +%include "std_string.i" + +%feature("director"); + +%include "callback.h" diff --git a/misc/swig/callback/run b/misc/swig/callback/run Binary files differnew file mode 100755 index 000000000..de150ed05 --- /dev/null +++ b/misc/swig/callback/run diff --git a/misc/swig/callback/run.go b/misc/swig/callback/run.go new file mode 100644 index 000000000..a76e636cb --- /dev/null +++ b/misc/swig/callback/run.go @@ -0,0 +1,39 @@ +// 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/callback" + "fmt" +) + +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/Makefile b/misc/swig/stdio/Makefile new file mode 100644 index 000000000..e7d330587 --- /dev/null +++ b/misc/swig/stdio/Makefile @@ -0,0 +1,17 @@ +# 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/file +SWIGFILES=\ + file.swig + +CLEANFILES+=hello + +include ../../../src/Make.pkg + +%: install %.go + $(GC) $*.go + $(LD) $(SWIG_RPATH) -o $@ $*.$O diff --git a/misc/swig/stdio/file.swig b/misc/swig/stdio/file.swig new file mode 100644 index 000000000..57c623f8f --- /dev/null +++ b/misc/swig/stdio/file.swig @@ -0,0 +1,11 @@ +/* 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. */ + +/* A trivial example of wrapping a C library using SWIG. */ + +%{ +#include <stdio.h> +%} + +int puts(const char *); diff --git a/misc/swig/stdio/hello b/misc/swig/stdio/hello Binary files differnew file mode 100755 index 000000000..10c55631f --- /dev/null +++ b/misc/swig/stdio/hello diff --git a/misc/swig/stdio/hello.go b/misc/swig/stdio/hello.go new file mode 100644 index 000000000..eec294278 --- /dev/null +++ b/misc/swig/stdio/hello.go @@ -0,0 +1,11 @@ +// 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 new file mode 100644 index 000000000..d4ae3b97f --- /dev/null +++ b/misc/vim/autoload/go/complete.vim @@ -0,0 +1,49 @@ +" 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. +" +" This file provides a utility function that performs auto-completion of +" package names, for use by other commands. + +let s:goos = $GOOS +let s:goarch = $GOARCH + +if len(s:goos) == 0 + if exists('g:golang_goos') + let s:goos = g:golang_goos + elseif has('win32') || has('win64') + let s:goos = 'windows' + elseif has('macunix') + let s:goos = 'darwin' + else + let s:goos = '*' + endif +endif + +if len(s:goarch) == 0 + if exists('g:golang_goarch') + let s:goarch = g:golang_goarch + else + let s:goarch = '*' + endif +endif + +function! go#complete#Package(ArgLead, CmdLine, CursorPos) + let goroot = $GOROOT + if len(goroot) == 0 + " should not occur. + 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 + endfor + return sort(keys(ret)) +endfunction diff --git a/misc/vim/ftdetect/gofiletype.vim b/misc/vim/ftdetect/gofiletype.vim new file mode 100644 index 000000000..f03a1d8dc --- /dev/null +++ b/misc/vim/ftdetect/gofiletype.vim @@ -0,0 +1 @@ +au BufReadPre,BufNewFile *.go set filetype=go fileencoding=utf-8 fileencodings=utf-8 diff --git a/misc/vim/ftplugin/go/fmt.vim b/misc/vim/ftplugin/go/fmt.vim new file mode 100644 index 000000000..a299dfcee --- /dev/null +++ b/misc/vim/ftplugin/go/fmt.vim @@ -0,0 +1,30 @@ +" 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. +" +" fmt.vim: Vim command to format Go files with gofmt. +" +" This filetype plugin add a new commands for go buffers: +" +" :Fmt +" +" Filter the current Go buffer through gofmt. +" It tries to preserve cursor position and avoids +" replacing the buffer with stderr output. +" + +command! -buffer Fmt call s:GoFormat() + +function! s:GoFormat() + let view = winsaveview() + %!gofmt + if v:shell_error + %| " output errors returned by gofmt + " TODO(dchest): perhaps, errors should go to quickfix + undo + echohl Error | echomsg "Gofmt returned error" | echohl None + endif + call winrestview(view) +endfunction + +" vim:ts=4:sw=4:et diff --git a/misc/vim/ftplugin/go/godoc.vim b/misc/vim/ftplugin/go/godoc.vim new file mode 100644 index 000000000..55195a674 --- /dev/null +++ b/misc/vim/ftplugin/go/godoc.vim @@ -0,0 +1,13 @@ +" 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 new file mode 100644 index 000000000..6705a476b --- /dev/null +++ b/misc/vim/ftplugin/go/import.vim @@ -0,0 +1,201 @@ +" 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. +" +" import.vim: Vim commands to import/drop Go packages. +" +" This filetype plugin adds three new commands for go buffers: +" +" :Import {path} +" +" Import ensures that the provided package {path} is imported +" 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. +" +" :Drop {path} +" +" Remove the import line for the provided package {path}, if +" present in the current Go buffer. If {path} is not being +" imported, an error will be displayed and the buffer will be +" untouched. +" +" In addition to these commands, there are also two shortcuts mapped: +" +" \f - Runs :Import fmt +" \F - Runs :Drop fmt +" +" 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") + finish +endif + +command! -buffer -nargs=? -complete=customlist,go#complete#Package Drop call s:SwitchImport(0, '', <f-args>) +command! -buffer -nargs=1 -complete=customlist,go#complete#Package Import call s:SwitchImport(1, '', <f-args>) +command! -buffer -nargs=* -complete=customlist,go#complete#Package ImportAs call s:SwitchImport(1, <f-args>) +map <buffer> <LocalLeader>f :Import fmt<CR> +map <buffer> <LocalLeader>F :Drop fmt<CR> + +function! s:SwitchImport(enabled, localname, path) + let view = winsaveview() + let path = a:path + + " Quotes are not necessary, so remove them if provided. + if path[0] == '"' + let path = strpart(path, 1) + endif + if path[len(path)-1] == '"' + let path = strpart(path, 0, len(path) - 1) + endif + if path == '' + call s:Error('Import path not provided') + return + endif + + let qpath = '"' . path . '"' + if a:localname != '' + let qlocalpath = a:localname . ' ' . qpath + else + let qlocalpath = qpath + endif + let indentstr = 0 + let packageline = -1 " Position of package name statement + let appendline = -1 " Position to introduce new import + let deleteline = -1 " Position of line with existing import + let linesdelta = 0 " Lines added/removed + + " Find proper place to add/remove import. + let line = 0 + while line <= line('$') + let linestr = getline(line) + + if linestr =~# '^package\s' + let packageline = line + let appendline = line + + elseif linestr =~# '^import\s\+(' + let appendstr = qlocalpath + let indentstr = 1 + let appendline = line + while line <= line("$") + let line = line + 1 + let linestr = getline(line) + let m = matchlist(getline(line), '^\()\|\(\s\+\)\(\S*\s*\)"\(.\+\)"\)') + if empty(m) + continue + endif + if m[1] == ')' + break + endif + if a:localname != '' && m[3] != '' + let qlocalpath = printf('%-' . (len(m[3])-1) . 's %s', a:localname, qpath) + endif + let appendstr = m[2] . qlocalpath + let indentstr = 0 + if m[4] == path + let appendline = -1 + let deleteline = line + break + elseif m[4] < path + let appendline = line + endif + endwhile + break + + elseif linestr =~# '^import ' + if appendline == packageline + let appendstr = 'import ' . qlocalpath + let appendline = line - 1 + endif + let m = matchlist(linestr, '^import\(\s\+\)\(\S*\s*\)"\(.\+\)"') + if !empty(m) + if m[3] == path + let appendline = -1 + let deleteline = line + break + endif + if m[3] < path + let appendline = line + endif + if a:localname != '' && m[2] != '' + let qlocalpath = printf("%s %" . len(m[2])-1 . "s", a:localname, qpath) + endif + let appendstr = 'import' . m[1] . qlocalpath + endif + + elseif linestr =~# '^\(var\|const\|type\|func\)\>' + break + + endif + let line = line + 1 + endwhile + + " Append or remove the package import, as requested. + if a:enabled + if deleteline != -1 + call s:Error(qpath . ' already being imported') + elseif appendline == -1 + call s:Error('No package line found') + else + if appendline == packageline + call append(appendline + 0, '') + call append(appendline + 1, 'import (') + call append(appendline + 2, ')') + let appendline += 2 + let linesdelta += 3 + let appendstr = qlocalpath + let indentstr = 1 + endif + call append(appendline, appendstr) + execute appendline + 1 + if indentstr + execute 'normal >>' + endif + let linesdelta += 1 + endif + else + if deleteline == -1 + call s:Error(qpath . ' not being imported') + else + execute deleteline . 'd' + let linesdelta -= 1 + + if getline(deleteline-1) =~# '^import\s\+(' && getline(deleteline) =~# '^)' + " Delete empty import block + let deleteline -= 1 + execute deleteline . "d" + execute deleteline . "d" + let linesdelta -= 2 + endif + + if getline(deleteline) == '' && getline(deleteline - 1) == '' + " Delete spacing for removed line too. + execute deleteline . "d" + let linesdelta -= 1 + endif + endif + endif + + " Adjust view for any changes. + let view.lnum += linesdelta + let view.topline += linesdelta + if view.topline < 0 + let view.topline = 0 + endif + + " Put buffer back where it was. + call winrestview(view) + +endfunction + +function! s:Error(s) + echohl Error | echo a:s | echohl None +endfunction + +" vim:ts=4:sw=4:et diff --git a/misc/vim/indent/go.vim b/misc/vim/indent/go.vim new file mode 100644 index 000000000..faf4d79e2 --- /dev/null +++ b/misc/vim/indent/go.vim @@ -0,0 +1,65 @@ +" 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. +" +" indent/go.vim: Vim indent file for Go. +" +" TODO: +" - function invocations split across lines +" - general line splits (line ends in an operator) + +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +" C indentation is too far off useful, mainly due to Go's := operator. +" Let's just define our own. +setlocal nolisp +setlocal autoindent +setlocal indentexpr=GoIndent(v:lnum) +setlocal indentkeys+=<:>,0=},0=) + +if exists("*GoIndent") + finish +endif + +function! GoIndent(lnum) + let prevlnum = prevnonblank(a:lnum-1) + if prevlnum == 0 + " top of file + return 0 + endif + + " grab the previous and current line, stripping comments. + let prevl = substitute(getline(prevlnum), '//.*$', '', '') + let thisl = substitute(getline(a:lnum), '//.*$', '', '') + let previ = indent(prevlnum) + + let ind = previ + + if prevl =~ '[({]\s*$' + " previous line opened a block + let ind += &sw + endif + if prevl =~# '^\s*\(case .*\|default\):$' + " previous line is part of a switch statement + let ind += &sw + endif + " TODO: handle if the previous line is a label. + + if thisl =~ '^\s*[)}]' + " this line closed a block + let ind -= &sw + endif + + " Colons are tricky. + " We want to outdent if it's part of a switch ("case foo:" or "default:"). + " We ignore trying to deal with jump labels because (a) they're rare, and + " (b) they're hard to disambiguate from a composite literal key. + if thisl =~# '^\s*\(case .*\|default\):$' + let ind -= &sw + endif + + return ind +endfunction diff --git a/misc/vim/plugin/godoc.vim b/misc/vim/plugin/godoc.vim new file mode 100644 index 000000000..fdb496631 --- /dev/null +++ b/misc/vim/plugin/godoc.vim @@ -0,0 +1,85 @@ +" 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("g:loaded_godoc") + finish +endif +let g:loaded_godoc = 1 + +let s:buf_nr = -1 +let s:last_word = '' + +function! s:GodocView() + if !bufexists(s:buf_nr) + leftabove new + file `="[Godoc]"` + let s:buf_nr = bufnr('%') + elseif bufwinnr(s:buf_nr) == -1 + leftabove split + execute s:buf_nr . 'buffer' + delete _ + elseif bufwinnr(s:buf_nr) != bufwinnr('%') + execute bufwinnr(s:buf_nr) . 'wincmd w' + endif + + setlocal filetype=godoc + setlocal bufhidden=delete + setlocal buftype=nofile + setlocal noswapfile + setlocal nobuflisted + setlocal modifiable + setlocal nocursorline + setlocal nocursorcolumn + setlocal iskeyword+=: + setlocal iskeyword-=- + + nnoremap <buffer> <silent> K :Godoc<cr> + + au BufHidden <buffer> call let <SID>buf_nr = -1 +endfunction + +function! s:GodocWord(word) + let word = a:word + silent! let content = system('godoc ' . word) + if v:shell_error || !len(content) + if len(s:last_word) + silent! let content = system('godoc ' . s:last_word.'/'.word) + if v:shell_error || !len(content) + echo 'No documentation found for "' . word . '".' + return + endif + let word = s:last_word.'/'.word + else + echo 'No documentation found for "' . word . '".' + return + endif + endif + let s:last_word = word + silent! call s:GodocView() + setlocal modifiable + silent! %d _ + silent! put! =content + silent! normal gg + setlocal nomodifiable + setfiletype godoc +endfunction + +function! s:Godoc(...) + let word = join(a:000, ' ') + if !len(word) + let word = expand('<cword>') + endif + let word = substitute(word, '[^a-zA-Z0-9\/]', '', 'g') + if !len(word) + return + endif + call s:GodocWord(word) +endfunction + +command! -nargs=* -range -complete=customlist,go#complete#Package Godoc :call s:Godoc(<q-args>) +nnoremap <silent> <Plug>(godoc-keyword) :<C-u>call <SID>Godoc('')<CR> + +" vim:ts=4:sw=4:et diff --git a/misc/vim/readme.txt b/misc/vim/readme.txt new file mode 100644 index 000000000..fe15da993 --- /dev/null +++ b/misc/vim/readme.txt @@ -0,0 +1,76 @@ +Vim plugins for Go (http://golang.org) +====================================== + +To use all the Vim plugins, add these lines to your vimrc. + + set rtp+=$GOROOT/misc/vim + filetype plugin indent on + syntax on + +If you want to select fewer plugins, use the instructions in the rest of +this file. + +Vim syntax highlighting +----------------------- + +To install automatic syntax highlighting for GO programs: + + 1. Copy or link the filetype detection script to the ftdetect directory + underneath your vim runtime directory (normally $HOME/.vim/ftdetect) + 2. Copy or link syntax/go.vim to the syntax directory underneath your vim + runtime directory (normally $HOME/.vim/syntax). Linking this file rather + than just copying it will ensure any changes are automatically reflected + in your syntax highlighting. + 3. Add the following line to your .vimrc file (normally $HOME/.vimrc): + + syntax on + +In a typical unix environment you might accomplish this using the following +commands: + + mkdir -p $HOME/.vim/ftdetect + mkdir -p $HOME/.vim/syntax + mkdir -p $HOME/.vim/autoload/go + ln -s $GOROOT/misc/vim/ftdetect/gofiletype.vim $HOME/.vim/ftdetect/ + ln -s $GOROOT/misc/vim/syntax/go.vim $HOME/.vim/syntax + ln -s $GOROOT/misc/vim/autoload/go/complete.vim $HOME/.vim/autoload/go + echo "syntax on" >> $HOME/.vimrc + + +Vim filetype plugins +-------------------- + +To install one of the available filetype plugins: + + 1. Same as 1 above. + 2. Copy or link one or more plugins from ftplugin/go/*.vim to the + Go-specific ftplugin directory underneath your vim runtime directory + (normally $HOME/.vim/ftplugin/go/*.vim). + 3. Add the following line to your .vimrc file (normally $HOME/.vimrc): + + filetype plugin on + + +Vim indentation plugin +---------------------- + +To install automatic indentation: + + 1. Same as 1 above. + 2. Copy or link indent/go.vim to the indent directory underneath your vim + runtime directory (normally $HOME/.vim/indent). + 3. Add the following line to your .vimrc file (normally $HOME/.vimrc): + + filetype indent on + + +Godoc plugin +------------ + +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/vim/syntax/go.vim b/misc/vim/syntax/go.vim new file mode 100644 index 000000000..26d7defe3 --- /dev/null +++ b/misc/vim/syntax/go.vim @@ -0,0 +1,208 @@ +" Copyright 2009 The Go Authors. All rights reserved. +" Use of this source code is governed by a BSD-style +" license that can be found in the LICENSE file. +" +" go.vim: Vim syntax file for Go. +" +" Options: +" There are some options for customizing the highlighting; the recommended +" settings are the default values, but you can write: +" let OPTION_NAME = 0 +" in your ~/.vimrc file to disable particular options. You can also write: +" let OPTION_NAME = 1 +" to enable particular options. At present, all options default to on. +" +" - go_highlight_array_whitespace_error +" Highlights white space after "[]". +" - go_highlight_chan_whitespace_error +" Highlights white space around the communications operator that don't follow +" the standard style. +" - go_highlight_extra_types +" Highlights commonly used library types (os.Error, etc.). +" - go_highlight_space_tab_error +" Highlights instances of tabs following spaces. +" - go_highlight_trailing_whitespace_error +" Highlights trailing white space. + +" Quit when a (custom) syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +if !exists("go_highlight_array_whitespace_error") + let go_highlight_array_whitespace_error = 1 +endif +if !exists("go_highlight_chan_whitespace_error") + let go_highlight_chan_whitespace_error = 1 +endif +if !exists("go_highlight_extra_types") + let go_highlight_extra_types = 1 +endif +if !exists("go_highlight_space_tab_error") + let go_highlight_space_tab_error = 1 +endif +if !exists("go_highlight_trailing_whitespace_error") + let go_highlight_trailing_whitespace_error = 1 +endif + +syn case match + +syn keyword goDirective package import +syn keyword goDeclaration var const type +syn keyword goDeclType struct interface + +hi def link goDirective Statement +hi def link goDeclaration Keyword +hi def link goDeclType Keyword + +" Keywords within functions +syn keyword goStatement defer go goto return break continue fallthrough +syn keyword goConditional if else switch select +syn keyword goLabel case default +syn keyword goRepeat for range + +hi def link goStatement Statement +hi def link goConditional Conditional +hi def link goLabel Label +hi def link goRepeat Repeat + +" Predefined types +syn keyword goType chan map bool string +syn keyword goSignedInts int int8 int16 int32 int64 +syn keyword goUnsignedInts byte uint uint8 uint16 uint32 uint64 uintptr +syn keyword goFloats float32 float64 +syn keyword goComplexes complex64 complex128 + +hi def link goType Type +hi def link goSignedInts Type +hi def link goUnsignedInts Type +hi def link goFloats Type +hi def link goComplexes Type + +" Treat func specially: it's a declaration at the start of a line, but a type +" elsewhere. Order matters here. +syn match goType /\<func\>/ +syn match goDeclaration /^func\>/ + +" Predefined functions and values +syn keyword goBuiltins append cap close complex copy imag len +syn keyword goBuiltins make new panic print println real recover +syn keyword goConstants iota true false nil + +hi def link goBuiltins Keyword +hi def link goConstants Keyword + +" Comments; their contents +syn keyword goTodo contained TODO FIXME XXX BUG +syn cluster goCommentGroup contains=goTodo +syn region goComment start="/\*" end="\*/" contains=@goCommentGroup,@Spell +syn region goComment start="//" end="$" contains=@goCommentGroup,@Spell + +hi def link goComment Comment +hi def link goTodo Todo + +" Go escapes +syn match goEscapeOctal display contained "\\[0-7]\{3}" +syn match goEscapeC display contained +\\[abfnrtv\\'"]+ +syn match goEscapeX display contained "\\x\x\{2}" +syn match goEscapeU display contained "\\u\x\{4}" +syn match goEscapeBigU display contained "\\U\x\{8}" +syn match goEscapeError display contained +\\[^0-7xuUabfnrtv\\'"]+ + +hi def link goEscapeOctal goSpecialString +hi def link goEscapeC goSpecialString +hi def link goEscapeX goSpecialString +hi def link goEscapeU goSpecialString +hi def link goEscapeBigU goSpecialString +hi def link goSpecialString Special +hi def link goEscapeError Error + +" Strings and their contents +syn cluster goStringGroup contains=goEscapeOctal,goEscapeC,goEscapeX,goEscapeU,goEscapeBigU,goEscapeError +syn region goString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=@goStringGroup +syn region goRawString start=+`+ end=+`+ + +hi def link goString String +hi def link goRawString String + +" Characters; their contents +syn cluster goCharacterGroup contains=goEscapeOctal,goEscapeC,goEscapeX,goEscapeU,goEscapeBigU +syn region goCharacter start=+'+ skip=+\\\\\|\\'+ end=+'+ contains=@goCharacterGroup + +hi def link goCharacter Character + +" Regions +syn region goBlock start="{" end="}" transparent fold +syn region goParen start='(' end=')' transparent + +" Integers +syn match goDecimalInt "\<\d\+\([Ee]\d\+\)\?\>" +syn match goHexadecimalInt "\<0x\x\+\>" +syn match goOctalInt "\<0\o\+\>" +syn match goOctalError "\<0\o*[89]\d*\>" + +hi def link goDecimalInt Integer +hi def link goHexadecimalInt Integer +hi def link goOctalInt Integer +hi def link Integer Number + +" Floating point +syn match goFloat "\<\d\+\.\d*\([Ee][-+]\d\+\)\?\>" +syn match goFloat "\<\.\d\+\([Ee][-+]\d\+\)\?\>" +syn match goFloat "\<\d\+[Ee][-+]\d\+\>" + +hi def link goFloat Float + +" Imaginary literals +syn match goImaginary "\<\d\+i\>" +syn match goImaginary "\<\d\+\.\d*\([Ee][-+]\d\+\)\?i\>" +syn match goImaginary "\<\.\d\+\([Ee][-+]\d\+\)\?i\>" +syn match goImaginary "\<\d\+[Ee][-+]\d\+i\>" + +hi def link goImaginary Number + +" Spaces after "[]" +if go_highlight_array_whitespace_error != 0 + syn match goSpaceError display "\(\[\]\)\@<=\s\+" +endif + +" Spacing errors around the 'chan' keyword +if go_highlight_chan_whitespace_error != 0 + " receive-only annotation on chan type + syn match goSpaceError display "\(<-\)\@<=\s\+\(chan\>\)\@=" + " send-only annotation on chan type + syn match goSpaceError display "\(\<chan\)\@<=\s\+\(<-\)\@=" + " value-ignoring receives in a few contexts + syn match goSpaceError display "\(\(^\|[={(,;]\)\s*<-\)\@<=\s\+" +endif + +" Extra types commonly seen +if go_highlight_extra_types != 0 + syn match goExtraType /\<bytes\.\(Buffer\)\>/ + syn match goExtraType /\<io\.\(Reader\|Writer\|ReadWriter\|ReadWriteCloser\)\>/ + syn match goExtraType /\<\(os\.Error\)\>/ + syn match goExtraType /\<reflect\.\(Kind\|Type\|Value\)\>/ + syn match goExtraType /\<unsafe\.Pointer\>/ +endif + +" Space-tab error +if go_highlight_space_tab_error != 0 + syn match goSpaceError display " \+\t"me=e-1 +endif + +" Trailing white space error +if go_highlight_trailing_whitespace_error != 0 + syn match goSpaceError display excludenl "\s\+$" +endif + +hi def link goExtraType Type +hi def link goSpaceError Error + +" Search backwards for a global declaration to start processing the syntax. +"syn sync match goSync grouphere NONE /^\(const\|var\|type\|func\)\>/ + +" There's a bug in the implementation of grouphere. For now, use the +" following as a more expensive/less precise workaround. +syn sync minlines=500 + +let b:current_syntax = "go" diff --git a/misc/vim/syntax/godoc.vim b/misc/vim/syntax/godoc.vim new file mode 100644 index 000000000..82f78aa3c --- /dev/null +++ b/misc/vim/syntax/godoc.vim @@ -0,0 +1,20 @@ +" 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. + +if exists("b:current_syntax") + finish +endif + +syn case match +syn match godocTitle "^\([A-Z]*\)$" + +command -nargs=+ HiLink hi def link <args> + +HiLink godocTitle Title + +delcommand HiLink + +let b:current_syntax = "godoc" + +" vim:ts=4 sts=2 sw=2: diff --git a/misc/xcode/go.pbfilespec b/misc/xcode/go.pbfilespec new file mode 100644 index 000000000..1034778f5 --- /dev/null +++ b/misc/xcode/go.pbfilespec @@ -0,0 +1,31 @@ +/* + Copyright 2009 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. + + go.pbfilespec + Go source file spec for Xcode 3 + + There is not much documentation available regarding the format + of .pbfilespec files. As a starting point, see for instance the + outdated documentation at: + http://maxao.free.fr/xcode-plugin-interface/specifications.html + and the files in: + /Developer/Library/PrivateFrameworks/XcodeEdit.framework/Versions/A/Resources/ + + Place this file in directory: + ~/Library/Application Support/Developer/Shared/Xcode/Specifications/ +*/ + +( + { + Identifier = sourcecode.go; + BasedOn = sourcecode; + Name = "Go Files"; + Extensions = ("go"); + MIMETypes = ("text/go"); + Language = "xcode.lang.go"; + IsTextFile = YES; + IsSourceFile = YES; + } +) diff --git a/misc/xcode/go.xclangspec b/misc/xcode/go.xclangspec new file mode 100644 index 000000000..e515564da --- /dev/null +++ b/misc/xcode/go.xclangspec @@ -0,0 +1,293 @@ +/* + Copyright 2009 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. + + Go.xclangspec + Go language specification for Xcode 3 + + This is a preliminary version that supports basic syntax high-lighting + (such as keywords, literals, and comments) and an attempt to provide + some structure information (incomplete). + + There is not much documentation available regarding the format + of .xclangspec files. As a starting point, see for instance the + outdated documentation at: + http://maxao.free.fr/xcode-plugin-interface/specifications.html + and the files in: + /Developer/Library/PrivateFrameworks/XcodeEdit.framework/Versions/A/Resources/ + + Place this file in directory: + ~/Library/Application Support/Developer/Shared/Xcode/Specifications/ +*/ + +( + +// ---------------------------------------------------------------------------- +// Keywords + +// TODO How do we get general Unicode identifiers? + + { + Identifier = "xcode.lang.go.identifier"; + Syntax = { + StartChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"; + Chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; + Words = ( + "break", + "case", + "chan", + "const", + "continue", + + "default", + "defer", + "else", + "fallthrough", + "for", + + "func", + "go", + "goto", + "if", + "import", + + "interface", + "map", + "package", + "range", + "return", + + "select", + "struct", + "switch", + "type", + "var", + ); + Type = "xcode.syntax.keyword"; + AltType = "xcode.syntax.identifier"; // non-keywords are identifiers + }; + }, + +// TODO decide what should go here, if anything + { + Identifier = "xcode.lang.go.interestingOperators"; + Syntax = { + Words = ( + "...", + ".", + "*", + ",", + ":", + ); + Type = "xcode.syntax.plain"; + }; + }, + + { + Identifier = "xcode.lang.go.rawstring"; + Syntax = { + Start = "`"; + End = "`"; + Type = "xcode.syntax.string"; + }; + }, + +// ---------------------------------------------------------------------------- +// Syntax Coloring + + { + Identifier = "xcode.lang.go"; + Description = "Go Coloring"; + BasedOn = "xcode.lang.simpleColoring"; + IncludeInMenu = YES; + Name = "Go"; + Syntax = { + Tokenizer = "xcode.lang.go.lexer.toplevel"; + IncludeRules = ( + "xcode.lang.go.block", + "xcode.lang.go.bracketexpr", + "xcode.lang.go.parenexpr", + ); + Type = "xcode.syntax.plain"; + }; + }, + + // The following rule returns tokens to the other rules + { + Identifier = "xcode.lang.go.lexer"; + Syntax = { + IncludeRules = ( + "xcode.lang.go.comment", + "xcode.lang.go.comment.singleline", + "xcode.lang.string", + "xcode.lang.character", + "xcode.lang.go.rawstring", + "xcode.lang.go.identifier", + "xcode.lang.number", + "xcode.lang.go.interestingOperators", + ); + }; + }, + + { + Identifier = "xcode.lang.go.lexer.toplevel"; + Syntax = { + IncludeRules = ( + "xcode.lang.go.comment", + "xcode.lang.go.comment.singleline", + "xcode.lang.string", + "xcode.lang.character", + "xcode.lang.go.rawstring", + "xcode.lang.go.type.declaration", + "xcode.lang.go.method.declaration", + "xcode.lang.go.function.declaration", + "xcode.lang.go.identifier", + "xcode.lang.number", + ); + }; + }, + + { + Identifier = "xcode.lang.go.method.declaration"; + Syntax = { + Tokenizer = "xcode.lang.go.lexer"; + Rules = ( + "func", + "xcode.lang.go.parenexpr", + "xcode.lang.go.identifier", + "xcode.lang.go.parenexpr", + ); + Type = "xcode.syntax.declaration.method"; + }; + }, + + { + Identifier = "xcode.lang.go.type.declaration"; + Syntax = { + Tokenizer = "xcode.lang.go.lexer"; + Rules = ( + "type", + "xcode.lang.go.identifier", + ); + Type = "xcode.syntax.typedef"; + }; + }, + + { + Identifier = "xcode.lang.go.function.declaration"; + Syntax = { + Tokenizer = "xcode.lang.go.lexer"; + Rules = ( + "func", + "xcode.lang.go.identifier", + "xcode.lang.go.parenexpr", + ); + Type = "xcode.syntax.declaration.function"; + }; + }, + +// ---------------------------------------------------------------------------- +// Blocks + + { + Identifier = "xcode.lang.go.block"; + Syntax = { + Tokenizer = "xcode.lang.go.lexer"; + Start = "{"; + End = "}"; + Foldable = YES; + Recursive = YES; + IncludeRules = ( + "xcode.lang.go.bracketexpr", + "xcode.lang.go.parenexpr", + ); + }; + }, + + { + Identifier = "xcode.lang.go.parenexpr"; + Syntax = { + Tokenizer = "xcode.lang.go.lexer"; + Start = "("; + End = ")"; + Recursive = YES; + IncludeRules = ( + "xcode.lang.go.bracketexpr", + "xcode.lang.go.block", + ); + }; + }, + + { + Identifier = "xcode.lang.go.bracketexpr"; + Syntax = { + Tokenizer = "xcode.lang.go.lexer"; + Start = "["; + End = "]"; + Recursive = YES; + IncludeRules = ( + "xcode.lang.go.parenexpr", + ); + }; + }, + + { + Identifier = "xcode.lang.go.comment"; + Syntax = { + Start = "/*"; + End = "*/"; + Foldable = YES; + IncludeRules = ( + "xcode.lang.url", + "xcode.lang.url.mail", + "xcode.lang.comment.mark", + ); + Type = "xcode.syntax.comment"; + }; + }, + + { + Identifier = "xcode.lang.go.comment.singleline"; + Syntax = { + Start = "//"; + End = "\n"; + IncludeRules = ( + "xcode.lang.url", + "xcode.lang.url.mail", + "xcode.lang.comment.mark", + ); + Type = "xcode.syntax.comment"; + }; + }, + + // This rule recognizes special comments markers and adds them + // to the list of file markers at the top of the editor window. + // This overrides the markers specified in + // /Developer/Library/PrivateFrameworks/XcodeEdit.framework/Versions/A/Resources/BaseSupport.xclangspec + // and appears to apply them to all languages. Thus, for now + // "inherit" the existing markers here for backward-compatibility. + { + Identifier = "xcode.lang.comment.mark"; + Syntax = { + StartChars = "BMTF!?"; + Match = ( + // Go-specific markers + "^\(BUG.*$\)$", // inlude "BUG" in the markers list + "^\(TODO.*$\)$", // inlude "TODO" in the markers list + // inherited markers + "^MARK:[ \t]+\(.*\)$", + "^\(TODO:[ \t]+.*\)$", // include "TODO: " in the markers list + "^\(FIXME:[ \t]+.*\)$", // include "FIXME: " in the markers list + "^\(!!!:.*\)$", // include "!!!:" in the markers list + "^\(\\?\\?\\?:.*\)$" // include "???:" in the markers list + ); + // This is the order of captures. All of the match strings above need the same order. + CaptureTypes = ( + "xcode.syntax.mark" + ); + Type = "xcode.syntax.comment"; + }; + }, + +) diff --git a/misc/zsh/go b/misc/zsh/go new file mode 100644 index 000000000..f17763d93 --- /dev/null +++ b/misc/zsh/go @@ -0,0 +1,14 @@ +# install in /etc/zsh/zshrc or your personal .zshrc + +# gc +prefixes=(5 6 8) +for p in $prefixes; do + compctl -g "*.${p}" ${p}l + compctl -g "*.go" ${p}g +done + +# standard go tools +compctl -g "*.go" gofmt + +# gccgo +compctl -g "*.go" gccgo |