Compare commits

..

6 Commits

Author SHA1 Message Date
Andrew Gerrand f8f91591b7 [release-branch.go1.2] go.tools/playground/socket: require origin to set up socket handler
««« CL 95030044 / bda3619e7a2c
go.tools/playground/socket: require origin to set up socket handler

This prevents cross-site request forgery attacks.

LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/95030044
»»»

TBR=rsc
CC=golang-dev
https://golang.org/cl/97040044
2014-05-05 08:51:42 -07:00
Andrew Gerrand 5849dad824 [release-branch.go1.2] godoc/static: update site policies link
««« CL 74900047 / 1c14028dc979
godoc/static: update site policies link

Fixes golang/go#7520.

LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/74900047
»»»

LGTM=iant
R=golang-codereviews, iant
CC=golang-dev
https://golang.org/cl/83350043
2014-04-02 09:15:59 +11:00
Andrew Gerrand 161de53d86 [release-branch.go1.2] go.tools/blog: strip prefix when serving static content
««« CL 22700043 / 8cdf69ad05e9
go.tools/blog: strip prefix when serving static content

This fix permits godoc to serve images correctly under /blog/.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/22700043
»»»

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/23400043
2013-11-08 13:58:44 +11:00
Andrew Gerrand 72b8aac9ec [release-branch.go1.2] blog: Fix atom feed's `updated' time when there's only one article.
««« CL 18420044 / e145aeb0df07
blog: Fix atom feed's `updated' time when there's only one article.

When computing the time for the "updated" tag of the atom feed, the
current code checks if there is more than one article and if that is
not true, it sets the time to the zero time.Time. This means that
the feed also gets the zero time in this tag when there is exactly one
article.

This trivial patch fixes this so that when there is exactly one
article, the time is set to that article's time.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/18420044

»»»

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/23390043
2013-11-08 13:47:24 +11:00
Andrew Gerrand 4b93659ce3 [release-branch.go1.2] blog: Make the atom feed title configurable.
««« CL 16830043 / 963dd9f808db
blog: Make the atom feed title configurable.

The blog code is quite generic and with the replacement of template and
static files, it can be re-used. But the atom feed title is hard-coded
into the code. This patch adds a field to set the atom feed title to
the Config structure and uses it in the code where the title was
previously hard-coded.

A CL sent separately will set this Config field in the main package in
the go.blog sub-repository. (See CL 16850043 for that other patch).

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/16830043

»»»

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/23380044
2013-11-08 13:45:56 +11:00
Andrew Gerrand 432822781f [release-branch.go1.2] create branch 2013-10-23 10:31:26 +04:00
1369 changed files with 43344 additions and 164948 deletions

10
.gitattributes vendored
View File

@ -1,10 +0,0 @@
# Treat all files in this repo as binary, with no git magic updating
# line endings. Windows users contributing to Go will need to use a
# modern version of git and editors capable of LF line endings.
#
# We'll prevent accidental CRLF line endings from entering the repo
# via the git-review gofmt checks.
#
# See golang.org/issue/9281
* -text

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
# Add no patterns to .gitignore except for files generated by the build.
last-change

2
.hgignore Normal file
View File

@ -0,0 +1,2 @@
syntax:glob
last-change

View File

@ -1,26 +0,0 @@
# Contributing to Go
Go is an open source project.
It is the work of hundreds of contributors. We appreciate your help!
## Filing issues
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
## Contributing code
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
before sending patches.
Unless otherwise noted, the Go source files are distributed under
the BSD-style license found in the LICENSE file.

11
README Normal file
View File

@ -0,0 +1,11 @@
This subrepository holds the source for various packages and tools that support
the Go programming language.
Some of the tools, godoc and vet for example, are included in binary Go distributions.
Others, including the Go oracle and the test coverage tool, can be fetched with "go get".
Packages include a type-checker for Go and an implementation of the Single Static
Assignment (SSA) representation for Go programs.
To submit changes to this repository, see http://golang.org/doc/contribute.html.

View File

@ -1,27 +0,0 @@
# Go Tools
This subrepository holds the source for various packages and tools that support
the Go programming language.
Some of the tools, `godoc` and `vet` for example, are included in binary Go
distributions.
Others, including the Go `guru` and the test coverage tool, can be fetched with
`go get`.
Packages include a type-checker for Go and an implementation of the
Static Single Assignment form (SSA) representation for Go programs.
## Download/Install
The easiest way to install is to run `go get -u golang.org/x/tools/...`. You can
also manually git clone the repository to `$GOPATH/src/golang.org/x/tools`.
## Report Issues / Send Patches
This repository uses Gerrit for code changes. To learn how to submit changes to
this repository, see https://golang.org/doc/contribute.html.
The main issue tracker for the tools repository is located at
https://github.com/golang/go/issues. Prefix your issue with "x/tools/(your
subdir):" in the subject line, so it is easy to find.

View File

@ -1,131 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package parse provides support for parsing benchmark results as
// generated by 'go test -bench'.
package parse // import "golang.org/x/tools/benchmark/parse"
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
// Flags used by Benchmark.Measured to indicate
// which measurements a Benchmark contains.
const (
NsPerOp = 1 << iota
MBPerS
AllocedBytesPerOp
AllocsPerOp
)
// Benchmark is one run of a single benchmark.
type Benchmark struct {
Name string // benchmark name
N int // number of iterations
NsPerOp float64 // nanoseconds per iteration
AllocedBytesPerOp uint64 // bytes allocated per iteration
AllocsPerOp uint64 // allocs per iteration
MBPerS float64 // MB processed per second
Measured int // which measurements were recorded
Ord int // ordinal position within a benchmark run
}
// ParseLine extracts a Benchmark from a single line of testing.B
// output.
func ParseLine(line string) (*Benchmark, error) {
fields := strings.Fields(line)
// Two required, positional fields: Name and iterations.
if len(fields) < 2 {
return nil, fmt.Errorf("two fields required, have %d", len(fields))
}
if !strings.HasPrefix(fields[0], "Benchmark") {
return nil, fmt.Errorf(`first field does not start with "Benchmark"`)
}
n, err := strconv.Atoi(fields[1])
if err != nil {
return nil, err
}
b := &Benchmark{Name: fields[0], N: n}
// Parse any remaining pairs of fields; we've parsed one pair already.
for i := 1; i < len(fields)/2; i++ {
b.parseMeasurement(fields[i*2], fields[i*2+1])
}
return b, nil
}
func (b *Benchmark) parseMeasurement(quant string, unit string) {
switch unit {
case "ns/op":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.NsPerOp = f
b.Measured |= NsPerOp
}
case "MB/s":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.MBPerS = f
b.Measured |= MBPerS
}
case "B/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocedBytesPerOp = i
b.Measured |= AllocedBytesPerOp
}
case "allocs/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocsPerOp = i
b.Measured |= AllocsPerOp
}
}
}
func (b *Benchmark) String() string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "%s %d", b.Name, b.N)
if (b.Measured & NsPerOp) != 0 {
fmt.Fprintf(buf, " %.2f ns/op", b.NsPerOp)
}
if (b.Measured & MBPerS) != 0 {
fmt.Fprintf(buf, " %.2f MB/s", b.MBPerS)
}
if (b.Measured & AllocedBytesPerOp) != 0 {
fmt.Fprintf(buf, " %d B/op", b.AllocedBytesPerOp)
}
if (b.Measured & AllocsPerOp) != 0 {
fmt.Fprintf(buf, " %d allocs/op", b.AllocsPerOp)
}
return buf.String()
}
// Set is a collection of benchmarks from one
// testing.B run, keyed by name to facilitate comparison.
type Set map[string][]*Benchmark
// ParseSet extracts a Set from testing.B output.
// ParseSet preserves the order of benchmarks that have identical
// names.
func ParseSet(r io.Reader) (Set, error) {
bb := make(Set)
scan := bufio.NewScanner(r)
ord := 0
for scan.Scan() {
if b, err := ParseLine(scan.Text()); err == nil {
b.Ord = ord
ord++
bb[b.Name] = append(bb[b.Name], b)
}
}
if err := scan.Err(); err != nil {
return nil, err
}
return bb, nil
}

View File

@ -1,154 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package parse
import (
"reflect"
"strings"
"testing"
)
func TestParseLine(t *testing.T) {
cases := []struct {
line string
want *Benchmark
err bool // expect an error
}{
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77,
Measured: NsPerOp | MBPerS,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s 5 allocs/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77, AllocsPerOp: 5,
Measured: NsPerOp | MBPerS | AllocsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s 3 B/op 5 allocs/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77, AllocedBytesPerOp: 3, AllocsPerOp: 5,
Measured: NsPerOp | MBPerS | AllocedBytesPerOp | AllocsPerOp,
},
},
// error handling cases
{
line: "BenchPress 100 19.6 ns/op", // non-benchmark
err: true,
},
{
line: "BenchmarkEncrypt lots 19.6 ns/op", // non-int iterations
err: true,
},
{
line: "BenchmarkBridge 100000000 19.6 smoots", // unknown unit
want: &Benchmark{
Name: "BenchmarkBridge",
N: 100000000,
},
},
{
line: "PASS",
err: true,
},
}
for _, tt := range cases {
have, err := ParseLine(tt.line)
if tt.err && err == nil {
t.Errorf("parsing line %q should have failed", tt.line)
continue
}
if !reflect.DeepEqual(have, tt.want) {
t.Errorf("parsed line %q incorrectly, want %v have %v", tt.line, tt.want, have)
}
}
}
func TestParseSet(t *testing.T) {
// Test two things:
// 1. The noise that can accompany testing.B output gets ignored.
// 2. Benchmarks with the same name have their order preserved.
in := `
? crypto [no test files]
PASS
pem_decrypt_test.go:17: test 4. %!s(x509.PEMCipher=5)
... [output truncated]
BenchmarkEncrypt 100000000 19.6 ns/op
BenchmarkEncrypt 5000000 517 ns/op
=== RUN TestChunk
--- PASS: TestChunk (0.00 seconds)
--- SKIP: TestLinuxSendfile (0.00 seconds)
fs_test.go:716: skipping; linux-only test
BenchmarkReadRequestApachebench 1000000 2960 ns/op 27.70 MB/s 839 B/op 9 allocs/op
BenchmarkClientServerParallel64 50000 59192 ns/op 7028 B/op 60 allocs/op
ok net/http 95.783s
`
want := Set{
"BenchmarkReadRequestApachebench": []*Benchmark{
{
Name: "BenchmarkReadRequestApachebench",
N: 1000000, NsPerOp: 2960, MBPerS: 27.70, AllocedBytesPerOp: 839, AllocsPerOp: 9,
Measured: NsPerOp | MBPerS | AllocedBytesPerOp | AllocsPerOp,
Ord: 2,
},
},
"BenchmarkClientServerParallel64": []*Benchmark{
{
Name: "BenchmarkClientServerParallel64",
N: 50000, NsPerOp: 59192, AllocedBytesPerOp: 7028, AllocsPerOp: 60,
Measured: NsPerOp | AllocedBytesPerOp | AllocsPerOp,
Ord: 3,
},
},
"BenchmarkEncrypt": []*Benchmark{
{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
Ord: 0,
},
{
Name: "BenchmarkEncrypt",
N: 5000000, NsPerOp: 517,
Measured: NsPerOp,
Ord: 1,
},
},
}
have, err := ParseSet(strings.NewReader(in))
if err != nil {
t.Fatalf("unexpected err during ParseSet: %v", err)
}
if !reflect.DeepEqual(want, have) {
t.Errorf("parsed bench set incorrectly, want %v have %v", want, have)
}
}

View File

@ -5,7 +5,7 @@
// Adapted from encoding/xml/read_test.go. // Adapted from encoding/xml/read_test.go.
// Package atom defines XML data structures for an Atom feed. // Package atom defines XML data structures for an Atom feed.
package atom // import "golang.org/x/tools/blog/atom" package atom
import ( import (
"encoding/xml" "encoding/xml"
@ -34,12 +34,8 @@ type Entry struct {
} }
type Link struct { type Link struct {
Rel string `xml:"rel,attr,omitempty"` Rel string `xml:"rel,attr"`
Href string `xml:"href,attr"` Href string `xml:"href,attr"`
Type string `xml:"type,attr,omitempty"`
HrefLang string `xml:"hreflang,attr,omitempty"`
Title string `xml:"title,attr,omitempty"`
Length uint `xml:"length,attr,omitempty"`
} }
type Person struct { type Person struct {

View File

@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package blog implements a web server for articles written in present format. // Package blog implements a web server for articles written in present format.
package blog // import "golang.org/x/tools/blog" package blog
import ( import (
"bytes" "bytes"
@ -20,18 +20,11 @@ import (
"strings" "strings"
"time" "time"
"golang.org/x/tools/blog/atom" "code.google.com/p/go.tools/blog/atom"
"golang.org/x/tools/present" "code.google.com/p/go.tools/present"
) )
var ( var validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)
validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)
// used to serve relative paths when ServeLocalLinks is enabled.
golangOrgAbsLinkReplacer = strings.NewReplacer(
`href="https://golang.org/pkg`, `href="/pkg`,
`href="https://golang.org/cmd`, `href="/cmd`,
)
)
// Config specifies Server configuration values. // Config specifies Server configuration values.
type Config struct { type Config struct {
@ -47,8 +40,7 @@ type Config struct {
FeedArticles int // Articles to include in Atom and JSON feeds. FeedArticles int // Articles to include in Atom and JSON feeds.
FeedTitle string // The title of the Atom XML feed FeedTitle string // The title of the Atom XML feed
PlayEnabled bool PlayEnabled bool
ServeLocalLinks bool // rewrite golang.org/{pkg,cmd} links to host-less, relative paths.
} }
// Doc represents an article adorned with presentation data. // Doc represents an article adorned with presentation data.
@ -81,17 +73,10 @@ type Server struct {
func NewServer(cfg Config) (*Server, error) { func NewServer(cfg Config) (*Server, error) {
present.PlayEnabled = cfg.PlayEnabled present.PlayEnabled = cfg.PlayEnabled
if notExist(cfg.TemplatePath) {
return nil, fmt.Errorf("template directory not found: %s", cfg.TemplatePath)
}
root := filepath.Join(cfg.TemplatePath, "root.tmpl") root := filepath.Join(cfg.TemplatePath, "root.tmpl")
parse := func(name string) (*template.Template, error) { parse := func(name string) (*template.Template, error) {
path := filepath.Join(cfg.TemplatePath, name)
if notExist(path) {
return nil, fmt.Errorf("template %s was not found in %s", name, cfg.TemplatePath)
}
t := template.New("").Funcs(funcMap) t := template.New("").Funcs(funcMap)
return t.ParseFiles(root, path) return t.ParseFiles(root, filepath.Join(cfg.TemplatePath, name))
} }
s := &Server{cfg: cfg} s := &Server{cfg: cfg}
@ -199,8 +184,8 @@ func (s *Server) loadDocs(root string) error {
if err != nil { if err != nil {
return err return err
} }
var html bytes.Buffer html := new(bytes.Buffer)
err = d.Render(&html, s.template.doc) err = d.Render(html, s.template.doc)
if err != nil { if err != nil {
return err return err
} }
@ -425,18 +410,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
d.Doc = doc d.Doc = doc
t = s.template.article t = s.template.article
} }
var err error err := t.ExecuteTemplate(w, "root", d)
if s.cfg.ServeLocalLinks {
var buf bytes.Buffer
err = t.ExecuteTemplate(&buf, "root", d)
if err != nil {
log.Println(err)
return
}
_, err = golangOrgAbsLinkReplacer.WriteString(w, buf.String())
} else {
err = t.ExecuteTemplate(w, "root", d)
}
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
@ -448,9 +422,3 @@ type docsByTime []*Doc
func (s docsByTime) Len() int { return len(s) } func (s docsByTime) Len() int { return len(s) }
func (s docsByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s docsByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) } func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) }
// notExist reports whether the path exists or not.
func notExist(path string) bool {
_, err := os.Stat(path)
return os.IsNotExist(err)
}

View File

@ -1,44 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package blog
import (
"bytes"
"testing"
)
func TestLinkRewrite(t *testing.T) {
tests := []struct {
input string
output string
}{
{
`For instance, the <a href="https://golang.org/pkg/bytes/" target="_blank">bytes package</a> from the standard library exports the <code>Buffer</code> type.`,
`For instance, the <a href="/pkg/bytes/" target="_blank">bytes package</a> from the standard library exports the <code>Buffer</code> type.`},
{
`(The <a href="https://golang.org/cmd/gofmt/" target="_blank">gofmt command</a> has a <code>-r</code> flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`,
`(The <a href="/cmd/gofmt/" target="_blank">gofmt command</a> has a <code>-r</code> flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`,
},
{
`<a href="//golang.org/LICENSE">BSD license</a>.<br> <a href="//golang.org/doc/tos.html">Terms of Service</a> `,
`<a href="//golang.org/LICENSE">BSD license</a>.<br> <a href="//golang.org/doc/tos.html">Terms of Service</a> `,
},
{
`For instance, the <code>websocket</code> package from the <code>go.net</code> sub-repository has an import path of <code>&#34;golang.org/x/net/websocket&#34;</code>.`,
`For instance, the <code>websocket</code> package from the <code>go.net</code> sub-repository has an import path of <code>&#34;golang.org/x/net/websocket&#34;</code>.`,
},
}
for _, test := range tests {
var buf bytes.Buffer
_, err := golangOrgAbsLinkReplacer.WriteString(&buf, test.input)
if err != nil {
t.Errorf("unexpected error during replacing links. Got: %#v, Want: nil.\n", err)
continue
}
if got, want := buf.String(), test.output; got != want {
t.Errorf("WriteString(%q) = %q. Expected: %q", test.input, got, want)
}
}
}

108
call/call.go Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package call defines the call graph abstraction and various algorithms
and utilities to operate on it. It does not provide a concrete
implementation but permits other analyses (such as pointer analyses or
Rapid Type Analysis) to expose their own call graphs in a
representation-independent manner.
A call graph is a labelled directed graph whose nodes represent
functions and whose edge labels represent syntactic function call
sites. The presence of a labelled edge (caller, site, callee)
indicates that caller may call callee at the specified call site.
A call graph is a multigraph: it may contain multiple edges (caller,
*, callee) connecting the same pair of nodes, so long as the edges
differ by label; this occurs when one function calls another function
from multiple call sites. Also, it may contain multiple edges
(caller, site, *) that differ only by callee; this indicates a
polymorphic call.
A call graph is called CONTEXT INSENSITIVE if no two nodes in N
represent the same syntactic function declaration, i.e. the set of
nodes and the set of syntactic functions are in one-to-one
correspondence.
A context-sensitive call graph may have multiple nodes corresponding
to the same function; this may yield a more precise approximation to
the calling behavior of the program. Consider this program:
func Apply(fn func(V), value V) { fn(value) }
Apply(F, v1)
...
Apply(G, v2)
A context-insensitive call graph would represent all calls to Apply by
the same node, so that node would have successors F and G. A
context-sensitive call graph might represent the first and second
calls to Apply by distinct nodes, so that the first would have
successor F and the second would have successor G. This is a more
precise representation of the possible behaviors of the program.
A SOUND call graph is one that overapproximates the dynamic calling
behaviors of the program in all possible executions. One call graph
is more PRECISE than another if it is a smaller overapproximation of
the dynamic behavior.
All call graphs have a synthetic root node which is responsible for
calling main() and init().
Calls to built-in functions (e.g. panic, println) are not represented
in the call graph; they are treated like built-in operators of the
language.
*/
package call
import "code.google.com/p/go.tools/ssa"
// A Graph represents a call graph.
//
// A graph may contain nodes that are not reachable from the root.
// If the call graph is sound, such nodes indicate unreachable
// functions.
//
type Graph interface {
Root() GraphNode // the distinguished root node
Nodes() []GraphNode // new unordered set of all nodes
}
// A GraphNode represents a node in a call graph.
//
// If the call graph is context sensitive, there may be multiple
// GraphNodes with the same Func(); the identity of the graph node
// indicates the context.
//
// Sites returns the set of syntactic call sites within this function.
//
// For nodes representing synthetic or intrinsic functions
// (e.g. reflect.Call, or the root of the call graph), Sites() returns
// a slice containing a single nil value to indicate the synthetic
// call site, and each edge in Edges() has a nil Site.
//
// All nodes "belong" to a single graph and must not be mixed with
// nodes belonging to another graph.
//
// A site may appear in Sites() but not in {e.Site | e ∈ Edges()}.
// This indicates that that caller node was unreachable, or that the
// call was dynamic yet no func or interface values flow to the call
// site.
//
// Clients should not mutate the node via the results of its methods.
//
type GraphNode interface {
Func() *ssa.Function // the function this node represents
Sites() []ssa.CallInstruction // new unordered set of call sites within this function
Edges() []Edge // new unordered set of outgoing edges
}
// A Edge represents an edge in the call graph.
type Edge struct {
Caller GraphNode
Site ssa.CallInstruction
Callee GraphNode
}

89
call/util.go Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package call
// This file provides various representation-independent utilities
// over call graphs, such as visitation and path search.
//
// TODO(adonovan):
//
// Consider adding lookup functions such as:
// FindSitesByPos(g Graph, lparen token.Pos) []Site
// FindSitesByCallExpr(g Graph, expr *ast.CallExpr) []Site
// FindSitesByInstr(g Graph, instr ssa.CallInstruction) []Site
// FindNodesByFunc(g Graph, fn *ssa.Function) []GraphNode
// (Counterargument: they're all inefficient linear scans; if the
// caller does it explicitly there may be opportunities to optimize.
//
// Add a utility function to eliminate all context from a call graph.
// CalleesOf returns a new set containing all direct callees of the
// caller node.
//
func CalleesOf(caller GraphNode) map[GraphNode]bool {
callees := make(map[GraphNode]bool)
for _, e := range caller.Edges() {
callees[e.Callee] = true
}
return callees
}
// GraphVisitEdges visits all the edges in graph g in depth-first order.
// The edge function is called for each edge in postorder. If it
// returns non-nil, visitation stops and GraphVisitEdges returns that
// value.
//
func GraphVisitEdges(g Graph, edge func(Edge) error) error {
seen := make(map[GraphNode]bool)
var visit func(n GraphNode) error
visit = func(n GraphNode) error {
if !seen[n] {
seen[n] = true
for _, e := range n.Edges() {
if err := visit(e.Callee); err != nil {
return err
}
if err := edge(e); err != nil {
return err
}
}
}
return nil
}
for _, n := range g.Nodes() {
if err := visit(n); err != nil {
return err
}
}
return nil
}
// PathSearch finds an arbitrary path starting at node start and
// ending at some node for which isEnd() returns true. On success,
// PathSearch returns the path as an ordered list of edges; on
// failure, it returns nil.
//
func PathSearch(start GraphNode, isEnd func(GraphNode) bool) []Edge {
stack := make([]Edge, 0, 32)
seen := make(map[GraphNode]bool)
var search func(n GraphNode) []Edge
search = func(n GraphNode) []Edge {
if !seen[n] {
seen[n] = true
if isEnd(n) {
return stack
}
for _, e := range n.Edges() {
stack = append(stack, e) // push
if found := search(e.Callee); found != nil {
return found
}
stack = stack[:len(stack)-1] // pop
}
}
return nil
}
return search(start)
}

View File

@ -1,231 +0,0 @@
// Copyright 2019 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.
// authtest is a diagnostic tool for implementations of the GOAUTH protocol
// described in https://golang.org/issue/26232.
//
// It accepts a single URL as an argument, and executes the GOAUTH protocol to
// fetch and display the headers for that URL.
//
// CAUTION: authtest logs the GOAUTH responses, which may include user
// credentials, to stderr. Do not post its output unless you are certain that
// all of the credentials involved are fake!
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/textproto"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
)
var v = flag.Bool("v", false, "if true, log GOAUTH responses to stderr")
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
flag.Parse()
args := flag.Args()
if len(args) != 1 {
log.Fatalf("usage: [GOAUTH=CMD...] %s URL", filepath.Base(os.Args[0]))
}
resp := try(args[0], nil)
if resp.StatusCode == http.StatusOK {
return
}
resp = try(args[0], resp)
if resp.StatusCode != http.StatusOK {
os.Exit(1)
}
}
func try(url string, prev *http.Response) *http.Response {
req := new(http.Request)
if prev != nil {
*req = *prev.Request
} else {
var err error
req, err = http.NewRequest("HEAD", os.Args[1], nil)
if err != nil {
log.Fatal(err)
}
}
goauth:
for _, argList := range strings.Split(os.Getenv("GOAUTH"), ";") {
// TODO(golang.org/issue/26849): If we escape quoted strings in GOFLAGS, use
// the same quoting here.
args := strings.Split(argList, " ")
if len(args) == 0 || args[0] == "" {
log.Fatalf("invalid or empty command in GOAUTH")
}
creds, err := getCreds(args, prev)
if err != nil {
log.Fatal(err)
}
for _, c := range creds {
if c.Apply(req) {
fmt.Fprintf(os.Stderr, "# request to %s\n", req.URL)
fmt.Fprintf(os.Stderr, "%s %s %s\n", req.Method, req.URL, req.Proto)
req.Header.Write(os.Stderr)
fmt.Fprintln(os.Stderr)
break goauth
}
}
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode < 400 || resp.StatusCode > 500 {
log.Fatalf("unexpected status: %v", resp.Status)
}
fmt.Fprintf(os.Stderr, "# response from %s\n", resp.Request.URL)
formatHead(os.Stderr, resp)
return resp
}
func formatHead(out io.Writer, resp *http.Response) {
fmt.Fprintf(out, "%s %s\n", resp.Proto, resp.Status)
if err := resp.Header.Write(out); err != nil {
log.Fatal(err)
}
fmt.Fprintln(out)
}
type Cred struct {
URLPrefixes []*url.URL
Header http.Header
}
func (c Cred) Apply(req *http.Request) bool {
if req.URL == nil {
return false
}
ok := false
for _, prefix := range c.URLPrefixes {
if prefix.Host == req.URL.Host &&
(req.URL.Path == prefix.Path ||
(strings.HasPrefix(req.URL.Path, prefix.Path) &&
(strings.HasSuffix(prefix.Path, "/") ||
req.URL.Path[len(prefix.Path)] == '/'))) {
ok = true
break
}
}
if !ok {
return false
}
for k, vs := range c.Header {
req.Header.Del(k)
for _, v := range vs {
req.Header.Add(k, v)
}
}
return true
}
func (c Cred) String() string {
var buf strings.Builder
for _, u := range c.URLPrefixes {
fmt.Fprintln(&buf, u)
}
buf.WriteString("\n")
c.Header.Write(&buf)
buf.WriteString("\n")
return buf.String()
}
func getCreds(args []string, resp *http.Response) ([]Cred, error) {
cmd := exec.Command(args[0], args[1:]...)
cmd.Stderr = os.Stderr
if resp != nil {
u := *resp.Request.URL
u.RawQuery = ""
cmd.Args = append(cmd.Args, u.String())
}
var head strings.Builder
if resp != nil {
formatHead(&head, resp)
}
cmd.Stdin = strings.NewReader(head.String())
fmt.Fprintf(os.Stderr, "# %s\n", strings.Join(cmd.Args, " "))
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("%s: %v", strings.Join(cmd.Args, " "), err)
}
os.Stderr.Write(out)
os.Stderr.WriteString("\n")
var creds []Cred
r := textproto.NewReader(bufio.NewReader(bytes.NewReader(out)))
line := 0
readLoop:
for {
var prefixes []*url.URL
for {
prefix, err := r.ReadLine()
if err == io.EOF {
if len(prefixes) > 0 {
return nil, fmt.Errorf("line %d: %v", line, io.ErrUnexpectedEOF)
}
break readLoop
}
line++
if prefix == "" {
if len(prefixes) == 0 {
return nil, fmt.Errorf("line %d: unexpected newline", line)
}
break
}
u, err := url.Parse(prefix)
if err != nil {
return nil, fmt.Errorf("line %d: malformed URL: %v", line, err)
}
if u.Scheme != "https" {
return nil, fmt.Errorf("line %d: non-HTTPS URL %q", line, prefix)
}
if len(u.RawQuery) > 0 {
return nil, fmt.Errorf("line %d: unexpected query string in URL %q", line, prefix)
}
if len(u.Fragment) > 0 {
return nil, fmt.Errorf("line %d: unexpected fragment in URL %q", line, prefix)
}
prefixes = append(prefixes, u)
}
header, err := r.ReadMIMEHeader()
if err != nil {
return nil, fmt.Errorf("headers at line %d: %v", line, err)
}
if len(header) > 0 {
creds = append(creds, Cred{
URLPrefixes: prefixes,
Header: http.Header(header),
})
}
}
return creds, nil
}

View File

@ -1,166 +0,0 @@
// Copyright 2019 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.
// cookieauth uses a “Netscape cookie file” to implement the GOAUTH protocol
// described in https://golang.org/issue/26232.
// It expects the location of the file as the first command-line argument.
//
// Example GOAUTH usage:
// export GOAUTH="cookieauth $(git config --get http.cookieFile)"
//
// See http://www.cookiecentral.com/faq/#3.5 for a description of the Netscape
// cookie file format.
package main
import (
"bufio"
"fmt"
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"strconv"
"strings"
"time"
"unicode"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "usage: %s COOKIEFILE [URL]\n", os.Args[0])
os.Exit(2)
}
log.SetPrefix("cookieauth: ")
f, err := os.Open(os.Args[1])
if err != nil {
log.Fatalf("failed to read cookie file: %v\n", os.Args[1])
os.Exit(1)
}
defer f.Close()
var (
targetURL *url.URL
targetURLs = map[string]*url.URL{}
)
if len(os.Args) == 3 {
targetURL, err = url.ParseRequestURI(os.Args[2])
if err != nil {
log.Fatalf("invalid request URI (%v): %q\n", err, os.Args[2])
}
targetURLs[targetURL.String()] = targetURL
} else if len(os.Args) > 3 {
// Extra arguments were passed: maybe the protocol was expanded?
// We don't know how to interpret the request, so ignore it.
return
}
entries, err := parseCookieFile(f.Name(), f)
if err != nil {
log.Fatalf("error reading cookie file: %v\n", f.Name())
}
jar, err := cookiejar.New(nil)
if err != nil {
log.Fatalf("failed to initialize cookie jar: %v\n", err)
}
for _, e := range entries {
u := &url.URL{
Scheme: "https",
Host: e.Host,
Path: e.Cookie.Path,
}
if targetURL == nil {
targetURLs[u.String()] = u
}
jar.SetCookies(u, []*http.Cookie{&e.Cookie})
}
for _, u := range targetURLs {
req := &http.Request{URL: u, Header: make(http.Header)}
for _, c := range jar.Cookies(req.URL) {
req.AddCookie(c)
}
fmt.Printf("%s\n\n", u)
req.Header.Write(os.Stdout)
fmt.Println()
}
}
type Entry struct {
Host string
Cookie http.Cookie
}
// parseCookieFile parses a Netscape cookie file as described in
// http://www.cookiecentral.com/faq/#3.5.
func parseCookieFile(name string, r io.Reader) ([]*Entry, error) {
var entries []*Entry
s := bufio.NewScanner(r)
line := 0
for s.Scan() {
line++
text := strings.TrimSpace(s.Text())
if len(text) < 2 || (text[0] == '#' && unicode.IsSpace(rune(text[1]))) {
continue
}
e, err := parseCookieLine(text)
if err != nil {
log.Printf("%s:%d: %v\n", name, line, err)
continue
}
entries = append(entries, e)
}
return entries, s.Err()
}
func parseCookieLine(line string) (*Entry, error) {
f := strings.Fields(line)
if len(f) < 7 {
return nil, fmt.Errorf("found %d columns; want 7", len(f))
}
e := new(Entry)
c := &e.Cookie
if domain := f[0]; strings.HasPrefix(domain, "#HttpOnly_") {
c.HttpOnly = true
e.Host = strings.TrimPrefix(domain[10:], ".")
} else {
e.Host = strings.TrimPrefix(domain, ".")
}
isDomain, err := strconv.ParseBool(f[1])
if err != nil {
return nil, fmt.Errorf("non-boolean domain flag: %v", err)
}
if isDomain {
c.Domain = e.Host
}
c.Path = f[2]
c.Secure, err = strconv.ParseBool(f[3])
if err != nil {
return nil, fmt.Errorf("non-boolean secure flag: %v", err)
}
expiration, err := strconv.ParseInt(f[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("malformed expiration: %v", err)
}
c.Expires = time.Unix(expiration, 0)
c.Name = f[5]
c.Value = f[6]
return e, nil
}

View File

@ -1,149 +0,0 @@
// Copyright 2019 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.
// gitauth uses 'git credential' to implement the GOAUTH protocol described in
// https://golang.org/issue/26232. It expects an absolute path to the working
// directory for the 'git' command as the first command-line argument.
//
// Example GOAUTH usage:
// export GOAUTH="gitauth $HOME"
//
// See https://git-scm.com/docs/gitcredentials or run 'man gitcredentials' for
// information on how to configure 'git credential'.
package main
import (
"bytes"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
)
func main() {
if len(os.Args) < 2 || !filepath.IsAbs(os.Args[1]) {
fmt.Fprintf(os.Stderr, "usage: %s WORKDIR [URL]", os.Args[0])
os.Exit(2)
}
log.SetPrefix("gitauth: ")
if len(os.Args) != 3 {
// No explicit URL was passed on the command line, but 'git credential'
// provides no way to enumerate existing credentials.
// Wait for a request for a specific URL.
return
}
u, err := url.ParseRequestURI(os.Args[2])
if err != nil {
log.Fatalf("invalid request URI (%v): %q\n", err, os.Args[1])
}
var (
prefix *url.URL
lastHeader http.Header
lastStatus = http.StatusUnauthorized
)
for lastStatus == http.StatusUnauthorized {
cmd := exec.Command("git", "credential", "fill")
// We don't want to execute a 'git' command in an arbitrary directory, since
// that opens up a number of config-injection attacks (for example,
// https://golang.org/issue/29230). Instead, we have the user configure a
// directory explicitly on the command line.
cmd.Dir = os.Args[1]
cmd.Stdin = strings.NewReader(fmt.Sprintf("url=%s\n", u))
cmd.Stderr = os.Stderr
out, err := cmd.Output()
if err != nil {
log.Fatalf("'git credential fill' failed: %v\n", err)
}
prefix = new(url.URL)
var username, password string
lines := strings.Split(string(out), "\n")
for _, line := range lines {
frags := strings.SplitN(line, "=", 2)
if len(frags) != 2 {
continue // Ignore unrecognized response lines.
}
switch strings.TrimSpace(frags[0]) {
case "protocol":
prefix.Scheme = frags[1]
case "host":
prefix.Host = frags[1]
case "path":
prefix.Path = frags[1]
case "username":
username = frags[1]
case "password":
password = frags[1]
case "url":
// Write to a local variable instead of updating prefix directly:
// if the url field is malformed, we don't want to invalidate
// information parsed from the protocol, host, and path fields.
u, err := url.ParseRequestURI(frags[1])
if err == nil {
prefix = u
} else {
log.Printf("malformed URL from 'git credential fill' (%v): %q\n", err, frags[1])
// Proceed anyway: we might be able to parse the prefix from other fields of the response.
}
}
}
// Double-check that the URL Git gave us is a prefix of the one we requested.
if !strings.HasPrefix(u.String(), prefix.String()) {
log.Fatalf("requested a credential for %q, but 'git credential fill' provided one for %q\n", u, prefix)
}
// Send a HEAD request to try to detect whether the credential is valid.
// If the user just typed in a correct password and has caching enabled,
// we don't want to nag them for it again the next time they run a 'go' command.
req, err := http.NewRequest("HEAD", u.String(), nil)
if err != nil {
log.Fatalf("internal error constructing HTTP HEAD request: %v\n", err)
}
req.SetBasicAuth(username, password)
lastHeader = req.Header
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Printf("HTTPS HEAD request failed to connect: %v\n", err)
// Couldn't verify the credential, but we have no evidence that it is invalid either.
// Proceed, but don't update git's credential cache.
break
}
lastStatus = resp.StatusCode
if resp.StatusCode != http.StatusOK {
log.Printf("%s: %v %s\n", u, resp.StatusCode, http.StatusText(resp.StatusCode))
}
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized {
// We learned something about the credential: it either worked or it was invalid.
// Approve or reject the credential (on a best-effort basis)
// so that the git credential helper can update its cache as appropriate.
action := "approve"
if resp.StatusCode != http.StatusOK {
action = "reject"
}
cmd = exec.Command("git", "credential", action)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stderr
cmd.Stdin = bytes.NewReader(out)
_ = cmd.Run()
}
}
// Write out the credential in the format expected by the 'go' command.
fmt.Printf("%s\n\n", prefix)
lastHeader.Write(os.Stdout)
fmt.Println()
}

View File

@ -1,123 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// netrcauth uses a .netrc file (or _netrc file on Windows) to implement the
// GOAUTH protocol described in https://golang.org/issue/26232.
// It expects the location of the file as the first command-line argument.
//
// Example GOAUTH usage:
// export GOAUTH="netrcauth $HOME/.netrc"
//
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html
// or run 'man 5 netrc' for a description of the .netrc file format.
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strings"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "usage: %s NETRCFILE [URL]", os.Args[0])
os.Exit(2)
}
log.SetPrefix("netrcauth: ")
if len(os.Args) != 2 {
// An explicit URL was passed on the command line, but netrcauth does not
// have any URL-specific output: it dumps the entire .netrc file at the
// first call.
return
}
path := os.Args[1]
data, err := ioutil.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return
}
log.Fatalf("failed to read %s: %v\n", path, err)
}
u := &url.URL{Scheme: "https"}
lines := parseNetrc(string(data))
for _, l := range lines {
u.Host = l.machine
fmt.Printf("%s\n\n", u)
req := &http.Request{Header: make(http.Header)}
req.SetBasicAuth(l.login, l.password)
req.Header.Write(os.Stdout)
fmt.Println()
}
}
// The following functions were extracted from src/cmd/go/internal/web2/web.go
// as of https://golang.org/cl/161698.
type netrcLine struct {
machine string
login string
password string
}
func parseNetrc(data string) []netrcLine {
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html
// for documentation on the .netrc format.
var nrc []netrcLine
var l netrcLine
inMacro := false
for _, line := range strings.Split(data, "\n") {
if inMacro {
if line == "" {
inMacro = false
}
continue
}
f := strings.Fields(line)
i := 0
for ; i < len(f)-1; i += 2 {
// Reset at each "machine" token.
// “The auto-login process searches the .netrc file for a machine token
// that matches […]. Once a match is made, the subsequent .netrc tokens
// are processed, stopping when the end of file is reached or another
// machine or a default token is encountered.”
switch f[i] {
case "machine":
l = netrcLine{machine: f[i+1]}
case "default":
break
case "login":
l.login = f[i+1]
case "password":
l.password = f[i+1]
case "macdef":
// “A macro is defined with the specified name; its contents begin with
// the next .netrc line and continue until a null line (consecutive
// new-line characters) is encountered.”
inMacro = true
}
if l.machine != "" && l.login != "" && l.password != "" {
nrc = append(nrc, l)
l = netrcLine{}
}
}
if i < len(f) && f[i] == "default" {
// “There can be only one default token, and it must be after all machine tokens.”
break
}
}
return nrc
}

View File

@ -1,184 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"os"
"sort"
"strconv"
"text/tabwriter"
"golang.org/x/tools/benchmark/parse"
)
var (
changedOnly = flag.Bool("changed", false, "show only benchmarks that have changed")
magSort = flag.Bool("mag", false, "sort benchmarks by magnitude of change")
best = flag.Bool("best", false, "compare best times from old and new")
)
const usageFooter = `
Each input file should be from:
go test -run=NONE -bench=. > [old,new].txt
Benchcmp compares old and new for each benchmark.
If -test.benchmem=true is added to the "go test" command
benchcmp will also compare memory allocations.
`
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: %s old.txt new.txt\n\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprint(os.Stderr, usageFooter)
os.Exit(2)
}
flag.Parse()
if flag.NArg() != 2 {
flag.Usage()
}
before := parseFile(flag.Arg(0))
after := parseFile(flag.Arg(1))
cmps, warnings := Correlate(before, after)
for _, warn := range warnings {
fmt.Fprintln(os.Stderr, warn)
}
if len(cmps) == 0 {
fatal("benchcmp: no repeated benchmarks")
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 0, 5, ' ', 0)
defer w.Flush()
var header bool // Has the header has been displayed yet for a given block?
if *magSort {
sort.Sort(ByDeltaNsPerOp(cmps))
} else {
sort.Sort(ByParseOrder(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.NsPerOp) {
continue
}
if delta := cmp.DeltaNsPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "benchmark\told ns/op\tnew ns/op\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cmp.Name(), formatNs(cmp.Before.NsPerOp), formatNs(cmp.After.NsPerOp), delta.Percent())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaMBPerS(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.MBPerS) {
continue
}
if delta := cmp.DeltaMBPerS(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told MB/s\tnew MB/s\tspeedup\n")
header = true
}
fmt.Fprintf(w, "%s\t%.2f\t%.2f\t%s\n", cmp.Name(), cmp.Before.MBPerS, cmp.After.MBPerS, delta.Multiple())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaAllocsPerOp(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.AllocsPerOp) {
continue
}
if delta := cmp.DeltaAllocsPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told allocs\tnew allocs\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocsPerOp, cmp.After.AllocsPerOp, delta.Percent())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaAllocedBytesPerOp(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.AllocedBytesPerOp) {
continue
}
if delta := cmp.DeltaAllocedBytesPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told bytes\tnew bytes\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocedBytesPerOp, cmp.After.AllocedBytesPerOp, cmp.DeltaAllocedBytesPerOp().Percent())
}
}
}
func fatal(msg interface{}) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(1)
}
func parseFile(path string) parse.Set {
f, err := os.Open(path)
if err != nil {
fatal(err)
}
defer f.Close()
bb, err := parse.ParseSet(f)
if err != nil {
fatal(err)
}
if *best {
selectBest(bb)
}
return bb
}
func selectBest(bs parse.Set) {
for name, bb := range bs {
if len(bb) < 2 {
continue
}
ord := bb[0].Ord
best := bb[0]
for _, b := range bb {
if b.NsPerOp < best.NsPerOp {
b.Ord = ord
best = b
}
}
bs[name] = []*parse.Benchmark{best}
}
}
// formatNs formats ns measurements to expose a useful amount of
// precision. It mirrors the ns precision logic of testing.B.
func formatNs(ns float64) string {
prec := 0
switch {
case ns < 10:
prec = 2
case ns < 100:
prec = 1
}
return strconv.FormatFloat(ns, 'f', prec, 64)
}

View File

@ -1,59 +0,0 @@
package main
import (
"reflect"
"testing"
"golang.org/x/tools/benchmark/parse"
)
func TestSelectBest(t *testing.T) {
have := parse.Set{
"Benchmark1": []*parse.Benchmark{
{
Name: "Benchmark1",
N: 10, NsPerOp: 100, Measured: parse.NsPerOp,
Ord: 0,
},
{
Name: "Benchmark1",
N: 10, NsPerOp: 50, Measured: parse.NsPerOp,
Ord: 3,
},
},
"Benchmark2": []*parse.Benchmark{
{
Name: "Benchmark2",
N: 10, NsPerOp: 60, Measured: parse.NsPerOp,
Ord: 1,
},
{
Name: "Benchmark2",
N: 10, NsPerOp: 500, Measured: parse.NsPerOp,
Ord: 2,
},
},
}
want := parse.Set{
"Benchmark1": []*parse.Benchmark{
{
Name: "Benchmark1",
N: 10, NsPerOp: 50, Measured: parse.NsPerOp,
Ord: 0,
},
},
"Benchmark2": []*parse.Benchmark{
{
Name: "Benchmark2",
N: 10, NsPerOp: 60, Measured: parse.NsPerOp,
Ord: 1,
},
},
}
selectBest(have)
if !reflect.DeepEqual(want, have) {
t.Errorf("filtered bench set incorrectly, want %v have %v", want, have)
}
}

View File

@ -1,156 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"math"
"golang.org/x/tools/benchmark/parse"
)
// BenchCmp is a pair of benchmarks.
type BenchCmp struct {
Before *parse.Benchmark
After *parse.Benchmark
}
// Correlate correlates benchmarks from two BenchSets.
func Correlate(before, after parse.Set) (cmps []BenchCmp, warnings []string) {
cmps = make([]BenchCmp, 0, len(after))
for name, beforebb := range before {
afterbb := after[name]
if len(beforebb) != len(afterbb) {
warnings = append(warnings, fmt.Sprintf("ignoring %s: before has %d instances, after has %d", name, len(beforebb), len(afterbb)))
continue
}
for i, beforeb := range beforebb {
afterb := afterbb[i]
cmps = append(cmps, BenchCmp{beforeb, afterb})
}
}
return
}
func (c BenchCmp) Name() string { return c.Before.Name }
func (c BenchCmp) String() string { return fmt.Sprintf("<%s, %s>", c.Before, c.After) }
func (c BenchCmp) Measured(flag int) bool { return (c.Before.Measured & c.After.Measured & flag) != 0 }
func (c BenchCmp) DeltaNsPerOp() Delta { return Delta{c.Before.NsPerOp, c.After.NsPerOp} }
func (c BenchCmp) DeltaMBPerS() Delta { return Delta{c.Before.MBPerS, c.After.MBPerS} }
func (c BenchCmp) DeltaAllocedBytesPerOp() Delta {
return Delta{float64(c.Before.AllocedBytesPerOp), float64(c.After.AllocedBytesPerOp)}
}
func (c BenchCmp) DeltaAllocsPerOp() Delta {
return Delta{float64(c.Before.AllocsPerOp), float64(c.After.AllocsPerOp)}
}
// Delta is the before and after value for a benchmark measurement.
// Both must be non-negative.
type Delta struct {
Before float64
After float64
}
// mag calculates the magnitude of a change, regardless of the direction of
// the change. mag is intended for sorting and has no independent meaning.
func (d Delta) mag() float64 {
switch {
case d.Before != 0 && d.After != 0 && d.Before >= d.After:
return d.After / d.Before
case d.Before != 0 && d.After != 0 && d.Before < d.After:
return d.Before / d.After
case d.Before == 0 && d.After == 0:
return 1
default:
// 0 -> 1 or 1 -> 0
// These are significant changes and worth surfacing.
return math.Inf(1)
}
}
// Changed reports whether the benchmark quantities are different.
func (d Delta) Changed() bool { return d.Before != d.After }
// Float64 returns After / Before. If Before is 0, Float64 returns
// 1 if After is also 0, and +Inf otherwise.
func (d Delta) Float64() float64 {
switch {
case d.Before != 0:
return d.After / d.Before
case d.After == 0:
return 1
default:
return math.Inf(1)
}
}
// Percent formats a Delta as a percent change, ranging from -100% up.
func (d Delta) Percent() string {
return fmt.Sprintf("%+.2f%%", 100*d.Float64()-100)
}
// Multiple formats a Delta as a multiplier, ranging from 0.00x up.
func (d Delta) Multiple() string {
return fmt.Sprintf("%.2fx", d.Float64())
}
func (d Delta) String() string {
return fmt.Sprintf("Δ(%f, %f)", d.Before, d.After)
}
// ByParseOrder sorts BenchCmps to match the order in
// which the Before benchmarks were presented to Parse.
type ByParseOrder []BenchCmp
func (x ByParseOrder) Len() int { return len(x) }
func (x ByParseOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByParseOrder) Less(i, j int) bool { return x[i].Before.Ord < x[j].Before.Ord }
// lessByDelta provides lexicographic ordering:
// * largest delta by magnitude
// * alphabetic by name
func lessByDelta(i, j BenchCmp, calcDelta func(BenchCmp) Delta) bool {
iDelta, jDelta := calcDelta(i).mag(), calcDelta(j).mag()
if iDelta != jDelta {
return iDelta < jDelta
}
return i.Name() < j.Name()
}
// ByDeltaNsPerOp sorts BenchCmps lexicographically by change
// in ns/op, descending, then by benchmark name.
type ByDeltaNsPerOp []BenchCmp
func (x ByDeltaNsPerOp) Len() int { return len(x) }
func (x ByDeltaNsPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaNsPerOp) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaNsPerOp) }
// ByDeltaMBPerS sorts BenchCmps lexicographically by change
// in MB/s, descending, then by benchmark name.
type ByDeltaMBPerS []BenchCmp
func (x ByDeltaMBPerS) Len() int { return len(x) }
func (x ByDeltaMBPerS) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaMBPerS) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaMBPerS) }
// ByDeltaAllocedBytesPerOp sorts BenchCmps lexicographically by change
// in B/op, descending, then by benchmark name.
type ByDeltaAllocedBytesPerOp []BenchCmp
func (x ByDeltaAllocedBytesPerOp) Len() int { return len(x) }
func (x ByDeltaAllocedBytesPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaAllocedBytesPerOp) Less(i, j int) bool {
return lessByDelta(x[i], x[j], BenchCmp.DeltaAllocedBytesPerOp)
}
// ByDeltaAllocsPerOp sorts BenchCmps lexicographically by change
// in allocs/op, descending, then by benchmark name.
type ByDeltaAllocsPerOp []BenchCmp
func (x ByDeltaAllocsPerOp) Len() int { return len(x) }
func (x ByDeltaAllocsPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaAllocsPerOp) Less(i, j int) bool {
return lessByDelta(x[i], x[j], BenchCmp.DeltaAllocsPerOp)
}

View File

@ -1,133 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"math"
"reflect"
"sort"
"testing"
"golang.org/x/tools/benchmark/parse"
)
func TestDelta(t *testing.T) {
cases := []struct {
before float64
after float64
mag float64
f float64
changed bool
pct string
mult string
}{
{before: 1, after: 1, mag: 1, f: 1, changed: false, pct: "+0.00%", mult: "1.00x"},
{before: 1, after: 2, mag: 0.5, f: 2, changed: true, pct: "+100.00%", mult: "2.00x"},
{before: 2, after: 1, mag: 0.5, f: 0.5, changed: true, pct: "-50.00%", mult: "0.50x"},
{before: 0, after: 0, mag: 1, f: 1, changed: false, pct: "+0.00%", mult: "1.00x"},
{before: 1, after: 0, mag: math.Inf(1), f: 0, changed: true, pct: "-100.00%", mult: "0.00x"},
{before: 0, after: 1, mag: math.Inf(1), f: math.Inf(1), changed: true, pct: "+Inf%", mult: "+Infx"},
}
for _, tt := range cases {
d := Delta{tt.before, tt.after}
if want, have := tt.mag, d.mag(); want != have {
t.Errorf("%s.mag(): want %f have %f", d, want, have)
}
if want, have := tt.f, d.Float64(); want != have {
t.Errorf("%s.Float64(): want %f have %f", d, want, have)
}
if want, have := tt.changed, d.Changed(); want != have {
t.Errorf("%s.Changed(): want %t have %t", d, want, have)
}
if want, have := tt.pct, d.Percent(); want != have {
t.Errorf("%s.Percent(): want %q have %q", d, want, have)
}
if want, have := tt.mult, d.Multiple(); want != have {
t.Errorf("%s.Multiple(): want %q have %q", d, want, have)
}
}
}
func TestCorrelate(t *testing.T) {
// Benches that are going to be successfully correlated get N thus:
// 0x<counter><num benches><b = before | a = after>
// Read this: "<counter> of <num benches>, from <before|after>".
before := parse.Set{
"BenchmarkOneEach": []*parse.Benchmark{{Name: "BenchmarkOneEach", N: 0x11b}},
"BenchmarkOneToNone": []*parse.Benchmark{{Name: "BenchmarkOneToNone"}},
"BenchmarkOneToTwo": []*parse.Benchmark{{Name: "BenchmarkOneToTwo"}},
"BenchmarkTwoToOne": []*parse.Benchmark{
{Name: "BenchmarkTwoToOne"},
{Name: "BenchmarkTwoToOne"},
},
"BenchmarkTwoEach": []*parse.Benchmark{
{Name: "BenchmarkTwoEach", N: 0x12b},
{Name: "BenchmarkTwoEach", N: 0x22b},
},
}
after := parse.Set{
"BenchmarkOneEach": []*parse.Benchmark{{Name: "BenchmarkOneEach", N: 0x11a}},
"BenchmarkNoneToOne": []*parse.Benchmark{{Name: "BenchmarkNoneToOne"}},
"BenchmarkTwoToOne": []*parse.Benchmark{{Name: "BenchmarkTwoToOne"}},
"BenchmarkOneToTwo": []*parse.Benchmark{
{Name: "BenchmarkOneToTwo"},
{Name: "BenchmarkOneToTwo"},
},
"BenchmarkTwoEach": []*parse.Benchmark{
{Name: "BenchmarkTwoEach", N: 0x12a},
{Name: "BenchmarkTwoEach", N: 0x22a},
},
}
pairs, errs := Correlate(before, after)
// Fail to match: BenchmarkOneToNone, BenchmarkOneToTwo, BenchmarkTwoToOne.
// Correlate does not notice BenchmarkNoneToOne.
if len(errs) != 3 {
t.Errorf("Correlated expected 4 errors, got %d: %v", len(errs), errs)
}
// Want three correlated pairs: one BenchmarkOneEach, two BenchmarkTwoEach.
if len(pairs) != 3 {
t.Fatalf("Correlated expected 3 pairs, got %v", pairs)
}
for _, pair := range pairs {
if pair.Before.N&0xF != 0xb {
t.Errorf("unexpected Before in pair %s", pair)
}
if pair.After.N&0xF != 0xa {
t.Errorf("unexpected After in pair %s", pair)
}
if pair.Before.N>>4 != pair.After.N>>4 {
t.Errorf("mismatched pair %s", pair)
}
}
}
func TestBenchCmpSorting(t *testing.T) {
c := []BenchCmp{
{&parse.Benchmark{Name: "BenchmarkMuchFaster", NsPerOp: 10, Ord: 3}, &parse.Benchmark{Name: "BenchmarkMuchFaster", NsPerOp: 1}},
{&parse.Benchmark{Name: "BenchmarkSameB", NsPerOp: 5, Ord: 1}, &parse.Benchmark{Name: "BenchmarkSameB", NsPerOp: 5}},
{&parse.Benchmark{Name: "BenchmarkSameA", NsPerOp: 5, Ord: 2}, &parse.Benchmark{Name: "BenchmarkSameA", NsPerOp: 5}},
{&parse.Benchmark{Name: "BenchmarkSlower", NsPerOp: 10, Ord: 0}, &parse.Benchmark{Name: "BenchmarkSlower", NsPerOp: 11}},
}
// Test just one magnitude-based sort order; they are symmetric.
sort.Sort(ByDeltaNsPerOp(c))
want := []string{"BenchmarkMuchFaster", "BenchmarkSlower", "BenchmarkSameA", "BenchmarkSameB"}
have := []string{c[0].Name(), c[1].Name(), c[2].Name(), c[3].Name()}
if !reflect.DeepEqual(want, have) {
t.Errorf("ByDeltaNsOp incorrect sorting: want %v have %v", want, have)
}
sort.Sort(ByParseOrder(c))
want = []string{"BenchmarkSlower", "BenchmarkSameB", "BenchmarkSameA", "BenchmarkMuchFaster"}
have = []string{c[0].Name(), c[1].Name(), c[2].Name(), c[3].Name()}
if !reflect.DeepEqual(want, have) {
t.Errorf("ByParseOrder incorrect sorting: want %v have %v", want, have)
}
}

View File

@ -1,37 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
The benchcmp command displays performance changes between benchmarks.
Benchcmp parses the output of two 'go test' benchmark runs,
correlates the results per benchmark, and displays the deltas.
To measure the performance impact of a change, use 'go test'
to run benchmarks before and after the change:
go test -run=NONE -bench=. ./... > old.txt
# make changes
go test -run=NONE -bench=. ./... > new.txt
Then feed the benchmark results to benchcmp:
benchcmp old.txt new.txt
Benchcmp will summarize and display the performance changes,
in a format like this:
$ benchcmp old.txt new.txt
benchmark old ns/op new ns/op delta
BenchmarkConcat 523 68.6 -86.88%
benchmark old allocs new allocs delta
BenchmarkConcat 3 1 -66.67%
benchmark old bytes new bytes delta
BenchmarkConcat 80 48 -40.00%
*/
package main // import "golang.org/x/tools/cmd/benchcmp"

View File

@ -1 +0,0 @@
testdata/out.got

View File

@ -1,467 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Bundle creates a single-source-file version of a source package
// suitable for inclusion in a particular target package.
//
// Usage:
//
// bundle [-o file] [-dst path] [-pkg name] [-prefix p] [-import old=new] <src>
//
// The src argument specifies the import path of the package to bundle.
// The bundling of a directory of source files into a single source file
// necessarily imposes a number of constraints.
// The package being bundled must not use cgo; must not use conditional
// file compilation, whether with build tags or system-specific file names
// like code_amd64.go; must not depend on any special comments, which
// may not be preserved; must not use any assembly sources;
// must not use renaming imports; and must not use reflection-based APIs
// that depend on the specific names of types or struct fields.
//
// By default, bundle writes the bundled code to standard output.
// If the -o argument is given, bundle writes to the named file
// and also includes a ``//go:generate'' comment giving the exact
// command line used, for regenerating the file with ``go generate.''
//
// Bundle customizes its output for inclusion in a particular package, the destination package.
// By default bundle assumes the destination is the package in the current directory,
// but the destination package can be specified explicitly using the -dst option,
// which takes an import path as its argument.
// If the source package imports the destination package, bundle will remove
// those imports and rewrite any references to use direct references to the
// corresponding symbols.
// Bundle also must write a package declaration in the output and must
// choose a name to use in that declaration.
// If the -package option is given, bundle uses that name.
// Otherwise, if the -dst option is given, bundle uses the last
// element of the destination import path.
// Otherwise, by default bundle uses the package name found in the
// package sources in the current directory.
//
// To avoid collisions, bundle inserts a prefix at the beginning of
// every package-level const, func, type, and var identifier in src's code,
// updating references accordingly. The default prefix is the package name
// of the source package followed by an underscore. The -prefix option
// specifies an alternate prefix.
//
// Occasionally it is necessary to rewrite imports during the bundling
// process. The -import option, which may be repeated, specifies that
// an import of "old" should be rewritten to import "new" instead.
//
// Example
//
// Bundle archive/zip for inclusion in cmd/dist:
//
// cd $GOROOT/src/cmd/dist
// bundle -o zip.go archive/zip
//
// Bundle golang.org/x/net/http2 for inclusion in net/http,
// prefixing all identifiers by "http2" instead of "http2_",
// and rewriting the import "golang.org/x/net/http2/hpack"
// to "internal/golang.org/x/net/http2/hpack":
//
// cd $GOROOT/src/net/http
// bundle -o h2_bundle.go \
// -prefix http2 \
// -import golang.org/x/net/http2/hpack=internal/golang.org/x/net/http2/hpack \
// golang.org/x/net/http2
//
// Two ways to update the http2 bundle:
//
// go generate net/http
//
// cd $GOROOT/src/net/http
// go generate
//
// Update both bundles, restricting ``go generate'' to running bundle commands:
//
// go generate -run bundle cmd/dist net/http
//
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/build"
"go/format"
"go/parser"
"go/printer"
"go/token"
"go/types"
"io/ioutil"
"log"
"os"
"path"
"strconv"
"strings"
"golang.org/x/tools/go/loader"
)
var (
outputFile = flag.String("o", "", "write output to `file` (default standard output)")
dstPath = flag.String("dst", "", "set destination import `path` (default taken from current directory)")
pkgName = flag.String("pkg", "", "set destination package `name` (default taken from current directory)")
prefix = flag.String("prefix", "&_", "set bundled identifier prefix to `p` (default is \"&_\", where & stands for the original name)")
underscore = flag.Bool("underscore", false, "rewrite golang.org/x/* to internal/x/* imports; temporary workaround for golang.org/issue/16333")
importMap = map[string]string{}
)
func init() {
flag.Var(flagFunc(addImportMap), "import", "rewrite import using `map`, of form old=new (can be repeated)")
}
func addImportMap(s string) {
if strings.Count(s, "=") != 1 {
log.Fatal("-import argument must be of the form old=new")
}
i := strings.Index(s, "=")
old, new := s[:i], s[i+1:]
if old == "" || new == "" {
log.Fatal("-import argument must be of the form old=new; old and new must be non-empty")
}
importMap[old] = new
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: bundle [options] <src>\n")
flag.PrintDefaults()
}
func main() {
log.SetPrefix("bundle: ")
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) != 1 {
usage()
os.Exit(2)
}
if *dstPath != "" {
if *pkgName == "" {
*pkgName = path.Base(*dstPath)
}
} else {
wd, _ := os.Getwd()
pkg, err := build.ImportDir(wd, 0)
if err != nil {
log.Fatalf("cannot find package in current directory: %v", err)
}
*dstPath = pkg.ImportPath
if *pkgName == "" {
*pkgName = pkg.Name
}
}
code, err := bundle(args[0], *dstPath, *pkgName, *prefix)
if err != nil {
log.Fatal(err)
}
if *outputFile != "" {
err := ioutil.WriteFile(*outputFile, code, 0666)
if err != nil {
log.Fatal(err)
}
} else {
_, err := os.Stdout.Write(code)
if err != nil {
log.Fatal(err)
}
}
}
// isStandardImportPath is copied from cmd/go in the standard library.
func isStandardImportPath(path string) bool {
i := strings.Index(path, "/")
if i < 0 {
i = len(path)
}
elem := path[:i]
return !strings.Contains(elem, ".")
}
var ctxt = &build.Default
func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
// Load the initial package.
conf := loader.Config{ParserMode: parser.ParseComments, Build: ctxt}
conf.TypeCheckFuncBodies = func(p string) bool { return p == src }
conf.Import(src)
lprog, err := conf.Load()
if err != nil {
return nil, err
}
// Because there was a single Import call and Load succeeded,
// InitialPackages is guaranteed to hold the sole requested package.
info := lprog.InitialPackages()[0]
if strings.Contains(prefix, "&") {
prefix = strings.Replace(prefix, "&", info.Files[0].Name.Name, -1)
}
objsToUpdate := make(map[types.Object]bool)
var rename func(from types.Object)
rename = func(from types.Object) {
if !objsToUpdate[from] {
objsToUpdate[from] = true
// Renaming a type that is used as an embedded field
// requires renaming the field too. e.g.
// type T int // if we rename this to U..
// var s struct {T}
// print(s.T) // ...this must change too
if _, ok := from.(*types.TypeName); ok {
for id, obj := range info.Uses {
if obj == from {
if field := info.Defs[id]; field != nil {
rename(field)
}
}
}
}
}
}
// Rename each package-level object.
scope := info.Pkg.Scope()
for _, name := range scope.Names() {
rename(scope.Lookup(name))
}
var out bytes.Buffer
fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.\n")
if *outputFile != "" {
fmt.Fprintf(&out, "//go:generate bundle %s\n", strings.Join(os.Args[1:], " "))
} else {
fmt.Fprintf(&out, "// $ bundle %s\n", strings.Join(os.Args[1:], " "))
}
fmt.Fprintf(&out, "\n")
// Concatenate package comments from all files...
for _, f := range info.Files {
if doc := f.Doc.Text(); strings.TrimSpace(doc) != "" {
for _, line := range strings.Split(doc, "\n") {
fmt.Fprintf(&out, "// %s\n", line)
}
}
}
// ...but don't let them become the actual package comment.
fmt.Fprintln(&out)
fmt.Fprintf(&out, "package %s\n\n", dstpkg)
// BUG(adonovan,shurcooL): bundle may generate incorrect code
// due to shadowing between identifiers and imported package names.
//
// The generated code will either fail to compile or
// (unlikely) compile successfully but have different behavior
// than the original package. The risk of this happening is higher
// when the original package has renamed imports (they're typically
// renamed in order to resolve a shadow inside that particular .go file).
// TODO(adonovan,shurcooL):
// - detect shadowing issues, and either return error or resolve them
// - preserve comments from the original import declarations.
// pkgStd and pkgExt are sets of printed import specs. This is done
// to deduplicate instances of the same import name and path.
var pkgStd = make(map[string]bool)
var pkgExt = make(map[string]bool)
for _, f := range info.Files {
for _, imp := range f.Imports {
path, err := strconv.Unquote(imp.Path.Value)
if err != nil {
log.Fatalf("invalid import path string: %v", err) // Shouldn't happen here since conf.Load succeeded.
}
if path == dst {
continue
}
if newPath, ok := importMap[path]; ok {
path = newPath
}
var name string
if imp.Name != nil {
name = imp.Name.Name
}
spec := fmt.Sprintf("%s %q", name, path)
if isStandardImportPath(path) {
pkgStd[spec] = true
} else {
if *underscore {
spec = strings.Replace(spec, "golang.org/x/", "internal/x/", 1)
}
pkgExt[spec] = true
}
}
}
// Print a single declaration that imports all necessary packages.
fmt.Fprintln(&out, "import (")
for p := range pkgStd {
fmt.Fprintf(&out, "\t%s\n", p)
}
if len(pkgExt) > 0 {
fmt.Fprintln(&out)
}
for p := range pkgExt {
fmt.Fprintf(&out, "\t%s\n", p)
}
fmt.Fprint(&out, ")\n\n")
// Modify and print each file.
for _, f := range info.Files {
// Update renamed identifiers.
for id, obj := range info.Defs {
if objsToUpdate[obj] {
id.Name = prefix + obj.Name()
}
}
for id, obj := range info.Uses {
if objsToUpdate[obj] {
id.Name = prefix + obj.Name()
}
}
// For each qualified identifier that refers to the
// destination package, remove the qualifier.
// The "@@@." strings are removed in postprocessing.
ast.Inspect(f, func(n ast.Node) bool {
if sel, ok := n.(*ast.SelectorExpr); ok {
if id, ok := sel.X.(*ast.Ident); ok {
if obj, ok := info.Uses[id].(*types.PkgName); ok {
if obj.Imported().Path() == dst {
id.Name = "@@@"
}
}
}
}
return true
})
last := f.Package
if len(f.Imports) > 0 {
imp := f.Imports[len(f.Imports)-1]
last = imp.End()
if imp.Comment != nil {
if e := imp.Comment.End(); e > last {
last = e
}
}
}
// Pretty-print package-level declarations.
// but no package or import declarations.
var buf bytes.Buffer
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
continue
}
beg, end := sourceRange(decl)
printComments(&out, f.Comments, last, beg)
buf.Reset()
format.Node(&buf, lprog.Fset, &printer.CommentedNode{Node: decl, Comments: f.Comments})
// Remove each "@@@." in the output.
// TODO(adonovan): not hygienic.
out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
last = printSameLineComment(&out, f.Comments, lprog.Fset, end)
out.WriteString("\n\n")
}
printLastComments(&out, f.Comments, last)
}
// Now format the entire thing.
result, err := format.Source(out.Bytes())
if err != nil {
log.Fatalf("formatting failed: %v", err)
}
return result, nil
}
// sourceRange returns the [beg, end) interval of source code
// belonging to decl (incl. associated comments).
func sourceRange(decl ast.Decl) (beg, end token.Pos) {
beg = decl.Pos()
end = decl.End()
var doc, com *ast.CommentGroup
switch d := decl.(type) {
case *ast.GenDecl:
doc = d.Doc
if len(d.Specs) > 0 {
switch spec := d.Specs[len(d.Specs)-1].(type) {
case *ast.ValueSpec:
com = spec.Comment
case *ast.TypeSpec:
com = spec.Comment
}
}
case *ast.FuncDecl:
doc = d.Doc
}
if doc != nil {
beg = doc.Pos()
}
if com != nil && com.End() > end {
end = com.End()
}
return beg, end
}
func printComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos, end token.Pos) {
for _, cg := range comments {
if pos <= cg.Pos() && cg.Pos() < end {
for _, c := range cg.List {
fmt.Fprintln(out, c.Text)
}
fmt.Fprintln(out)
}
}
}
const infinity = 1 << 30
func printLastComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos token.Pos) {
printComments(out, comments, pos, infinity)
}
func printSameLineComment(out *bytes.Buffer, comments []*ast.CommentGroup, fset *token.FileSet, pos token.Pos) token.Pos {
tf := fset.File(pos)
for _, cg := range comments {
if pos <= cg.Pos() && tf.Line(cg.Pos()) == tf.Line(pos) {
for _, c := range cg.List {
fmt.Fprintln(out, c.Text)
}
return cg.End()
}
}
return pos
}
type flagFunc func(string)
func (f flagFunc) Set(s string) error {
f(s)
return nil
}
func (f flagFunc) String() string { return "" }

View File

@ -1,74 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.9
package main
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"runtime"
"testing"
"golang.org/x/tools/go/buildutil"
)
func TestBundle(t *testing.T) {
load := func(name string) string {
data, err := ioutil.ReadFile(name)
if err != nil {
t.Fatal(err)
}
return string(data)
}
ctxt = buildutil.FakeContext(map[string]map[string]string{
"initial": {
"a.go": load("testdata/src/initial/a.go"),
"b.go": load("testdata/src/initial/b.go"),
"c.go": load("testdata/src/initial/c.go"),
},
"domain.name/importdecl": {
"p.go": load("testdata/src/domain.name/importdecl/p.go"),
},
"fmt": {
"print.go": `package fmt; func Println(...interface{})`,
},
})
os.Args = os.Args[:1] // avoid e.g. -test=short in the output
out, err := bundle("initial", "github.com/dest", "dest", "prefix")
if err != nil {
t.Fatal(err)
}
if got, want := string(out), load("testdata/out.golden"); got != want {
t.Errorf("-- got --\n%s\n-- want --\n%s\n-- diff --", got, want)
if err := ioutil.WriteFile("testdata/out.got", out, 0644); err != nil {
t.Fatal(err)
}
t.Log(diff("testdata/out.golden", "testdata/out.got"))
}
}
func diff(a, b string) string {
var cmd *exec.Cmd
switch runtime.GOOS {
case "plan9":
cmd = exec.Command("/bin/diff", "-c", a, b)
default:
cmd = exec.Command("/usr/bin/diff", "-u", a, b)
}
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Run() // nonzero exit is expected
if out.Len() == 0 {
return "(failed to compute diff)"
}
return out.String()
}

View File

@ -1,62 +0,0 @@
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
// $ bundle
// The package doc comment
//
package dest
import (
"fmt"
. "fmt"
_ "fmt"
renamedfmt "fmt"
renamedfmt2 "fmt"
"domain.name/importdecl"
)
// init functions are not renamed
func init() { prefixfoo() }
// Type S.
type prefixS struct {
prefixt
u int
} /* multi-line
comment
*/
// non-associated comment
/*
non-associated comment2
*/
// Function bar.
func prefixbar(s *prefixS) {
fmt.Println(s.prefixt, s.u) // comment inside function
}
// file-end comment
type prefixt int // type1
// const1
const prefixc = 1 // const2
func prefixfoo() {
fmt.Println(importdecl.F())
}
// zinit
const (
prefixz1 = iota // z1
prefixz2 // z2
) // zend
func prefixbaz() {
renamedfmt.Println()
renamedfmt2.Println()
Println()
}

View File

@ -1,3 +0,0 @@
package importdecl
func F() int { return 1 }

View File

@ -1,27 +0,0 @@
package initial
import "fmt" // this comment should not be visible
// init functions are not renamed
func init() { foo() }
// Type S.
type S struct {
t
u int
} /* multi-line
comment
*/
// non-associated comment
/*
non-associated comment2
*/
// Function bar.
func bar(s *S) {
fmt.Println(s.t, s.u) // comment inside function
}
// file-end comment

View File

@ -1,23 +0,0 @@
// The package doc comment
package initial
import (
"fmt"
"domain.name/importdecl"
)
type t int // type1
// const1
const c = 1 // const2
func foo() {
fmt.Println(importdecl.F())
}
// zinit
const (
z1 = iota // z1
z2 // z2
) // zend

View File

@ -1,12 +0,0 @@
package initial
import _ "fmt"
import renamedfmt "fmt"
import renamedfmt2 "fmt"
import . "fmt"
func baz() {
renamedfmt.Println()
renamedfmt2.Println()
Println()
}

View File

@ -1,349 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// callgraph: a tool for reporting the call graph of a Go program.
// See Usage for details, or run with -help.
package main // import "golang.org/x/tools/cmd/callgraph"
// TODO(adonovan):
//
// Features:
// - restrict graph to a single package
// - output
// - functions reachable from root (use digraph tool?)
// - unreachable functions (use digraph tool?)
// - dynamic (runtime) types
// - indexed output (numbered nodes)
// - JSON output
// - additional template fields:
// callee file/line/col
import (
"bufio"
"bytes"
"flag"
"fmt"
"go/build"
"go/token"
"io"
"log"
"os"
"runtime"
"text/template"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/callgraph/rta"
"golang.org/x/tools/go/callgraph/static"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
// flags
var (
algoFlag = flag.String("algo", "rta",
`Call graph construction algorithm (static, cha, rta, pta)`)
testFlag = flag.Bool("test", false,
"Loads test code (*_test.go) for imported packages")
formatFlag = flag.String("format",
"{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}",
"A template expression specifying how to format an edge")
ptalogFlag = flag.String("ptalog", "",
"Location of the points-to analysis log file, or empty to disable logging.")
)
func init() {
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
}
const Usage = `callgraph: display the the call graph of a Go program.
Usage:
callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] package...
Flags:
-algo Specifies the call-graph construction algorithm, one of:
static static calls only (unsound)
cha Class Hierarchy Analysis
rta Rapid Type Analysis
pta inclusion-based Points-To Analysis
The algorithms are ordered by increasing precision in their
treatment of dynamic calls (and thus also computational cost).
RTA and PTA require a whole program (main or test), and
include only functions reachable from main.
-test Include the package's tests in the analysis.
-format Specifies the format in which each call graph edge is displayed.
One of:
digraph output suitable for input to
golang.org/x/tools/cmd/digraph.
graphviz output in AT&T GraphViz (.dot) format.
All other values are interpreted using text/template syntax.
The default value is:
{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}
The structure passed to the template is (effectively):
type Edge struct {
Caller *ssa.Function // calling function
Callee *ssa.Function // called function
// Call site:
Filename string // containing file
Offset int // offset within file of '('
Line int // line number
Column int // column number of call
Dynamic string // "static" or "dynamic"
Description string // e.g. "static method call"
}
Caller and Callee are *ssa.Function values, which print as
"(*sync/atomic.Mutex).Lock", but other attributes may be
derived from them, e.g. Caller.Pkg.Pkg.Path yields the
import path of the enclosing package. Consult the go/ssa
API documentation for details.
Examples:
Show the call graph of the trivial web server application:
callgraph -format digraph $GOROOT/src/net/http/triv.go
Same, but show only the packages of each function:
callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \
$GOROOT/src/net/http/triv.go | sort | uniq
Show functions that make dynamic calls into the 'fmt' test package,
using the pointer analysis algorithm:
callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=pta fmt |
sed -ne 's/-dynamic-/--/p' |
sed -ne 's/-->.*fmt_test.*$//p' | sort | uniq
Show all functions directly called by the callgraph tool's main function:
callgraph -format=digraph golang.org/x/tools/cmd/callgraph |
digraph succs golang.org/x/tools/cmd/callgraph.main
`
func init() {
// If $GOMAXPROCS isn't set, use the full capacity of the machine.
// For small machines, use at least 4 threads.
if os.Getenv("GOMAXPROCS") == "" {
n := runtime.NumCPU()
if n < 4 {
n = 4
}
runtime.GOMAXPROCS(n)
}
}
func main() {
flag.Parse()
if err := doCallgraph("", "", *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
os.Exit(1)
}
}
var stdout io.Writer = os.Stdout
func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) error {
if len(args) == 0 {
fmt.Fprintln(os.Stderr, Usage)
return nil
}
cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Tests: tests,
Dir: dir,
}
if gopath != "" {
cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing
}
initial, err := packages.Load(cfg, args...)
if err != nil {
return err
}
if packages.PrintErrors(initial) > 0 {
return fmt.Errorf("packages contain errors")
}
// Create and build SSA-form program representation.
prog, pkgs := ssautil.AllPackages(initial, 0)
prog.Build()
// -- call graph construction ------------------------------------------
var cg *callgraph.Graph
switch algo {
case "static":
cg = static.CallGraph(prog)
case "cha":
cg = cha.CallGraph(prog)
case "pta":
// Set up points-to analysis log file.
var ptalog io.Writer
if *ptalogFlag != "" {
if f, err := os.Create(*ptalogFlag); err != nil {
log.Fatalf("Failed to create PTA log file: %s", err)
} else {
buf := bufio.NewWriter(f)
ptalog = buf
defer func() {
if err := buf.Flush(); err != nil {
log.Printf("flush: %s", err)
}
if err := f.Close(); err != nil {
log.Printf("close: %s", err)
}
}()
}
}
mains, err := mainPackages(pkgs)
if err != nil {
return err
}
config := &pointer.Config{
Mains: mains,
BuildCallGraph: true,
Log: ptalog,
}
ptares, err := pointer.Analyze(config)
if err != nil {
return err // internal error in pointer analysis
}
cg = ptares.CallGraph
case "rta":
mains, err := mainPackages(pkgs)
if err != nil {
return err
}
var roots []*ssa.Function
for _, main := range mains {
roots = append(roots, main.Func("init"), main.Func("main"))
}
rtares := rta.Analyze(roots, true)
cg = rtares.CallGraph
// NB: RTA gives us Reachable and RuntimeTypes too.
default:
return fmt.Errorf("unknown algorithm: %s", algo)
}
cg.DeleteSyntheticNodes()
// -- output------------------------------------------------------------
var before, after string
// Pre-canned formats.
switch format {
case "digraph":
format = `{{printf "%q %q" .Caller .Callee}}`
case "graphviz":
before = "digraph callgraph {\n"
after = "}\n"
format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}`
}
tmpl, err := template.New("-format").Parse(format)
if err != nil {
return fmt.Errorf("invalid -format template: %v", err)
}
// Allocate these once, outside the traversal.
var buf bytes.Buffer
data := Edge{fset: prog.Fset}
fmt.Fprint(stdout, before)
if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
data.position.Offset = -1
data.edge = edge
data.Caller = edge.Caller.Func
data.Callee = edge.Callee.Func
buf.Reset()
if err := tmpl.Execute(&buf, &data); err != nil {
return err
}
stdout.Write(buf.Bytes())
if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' {
fmt.Fprintln(stdout)
}
return nil
}); err != nil {
return err
}
fmt.Fprint(stdout, after)
return nil
}
// mainPackages returns the main packages to analyze.
// Each resulting package is named "main" and has a main function.
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
var mains []*ssa.Package
for _, p := range pkgs {
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
mains = append(mains, p)
}
}
if len(mains) == 0 {
return nil, fmt.Errorf("no main packages")
}
return mains, nil
}
type Edge struct {
Caller *ssa.Function
Callee *ssa.Function
edge *callgraph.Edge
fset *token.FileSet
position token.Position // initialized lazily
}
func (e *Edge) pos() *token.Position {
if e.position.Offset == -1 {
e.position = e.fset.Position(e.edge.Pos()) // called lazily
}
return &e.position
}
func (e *Edge) Filename() string { return e.pos().Filename }
func (e *Edge) Column() int { return e.pos().Column }
func (e *Edge) Line() int { return e.pos().Line }
func (e *Edge) Offset() int { return e.pos().Offset }
func (e *Edge) Dynamic() string {
if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil {
return "dynamic"
}
return "static"
}
func (e *Edge) Description() string { return e.edge.Description() }

View File

@ -1,99 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// No testdata on Android.
// +build !android
// +build go1.11
package main
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"testing"
)
func init() {
// This test currently requires GOPATH mode.
// Explicitly disabling module mode should suffix, but
// we'll also turn off GOPROXY just for good measure.
if err := os.Setenv("GO111MODULE", "off"); err != nil {
log.Fatal(err)
}
if err := os.Setenv("GOPROXY", "off"); err != nil {
log.Fatal(err)
}
}
func TestCallgraph(t *testing.T) {
gopath, err := filepath.Abs("testdata")
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
algo string
tests bool
want []string
}{
{"rta", false, []string{
// rta imprecisely shows cross product of {main,main2} x {C,D}
`pkg.main --> (pkg.C).f`,
`pkg.main --> (pkg.D).f`,
`pkg.main --> pkg.main2`,
`pkg.main2 --> (pkg.C).f`,
`pkg.main2 --> (pkg.D).f`,
}},
{"pta", false, []string{
// pta distinguishes main->C, main2->D. Also has a root node.
`<root> --> pkg.init`,
`<root> --> pkg.main`,
`pkg.main --> (pkg.C).f`,
`pkg.main --> pkg.main2`,
`pkg.main2 --> (pkg.D).f`,
}},
// tests: both the package's main and the test's main are called.
// The callgraph includes all the guts of the "testing" package.
{"rta", true, []string{
`pkg.test.main --> testing.MainStart`,
`testing.runExample --> pkg.Example`,
`pkg.Example --> (pkg.C).f`,
`pkg.main --> (pkg.C).f`,
}},
{"pta", true, []string{
`<root> --> pkg.test.main`,
`<root> --> pkg.main`,
`pkg.test.main --> testing.MainStart`,
`testing.runExample --> pkg.Example`,
`pkg.Example --> (pkg.C).f`,
`pkg.main --> (pkg.C).f`,
}},
} {
const format = "{{.Caller}} --> {{.Callee}}"
stdout = new(bytes.Buffer)
if err := doCallgraph("testdata/src", gopath, test.algo, format, test.tests, []string{"pkg"}); err != nil {
t.Error(err)
continue
}
edges := make(map[string]bool)
for _, line := range strings.Split(fmt.Sprint(stdout), "\n") {
edges[line] = true
}
for _, edge := range test.want {
if !edges[edge] {
t.Errorf("callgraph(%q, %t): missing edge: %s",
test.algo, test.tests, edge)
}
}
if t.Failed() {
t.Log("got:\n", stdout)
}
}
}

View File

@ -1,25 +0,0 @@
package main
type I interface {
f()
}
type C int
func (C) f() {}
type D int
func (D) f() {}
func main() {
var i I = C(0)
i.f() // dynamic call
main2()
}
func main2() {
var i I = D(0)
i.f() // dynamic call
}

View File

@ -1,10 +0,0 @@
package main
// An Example function must have an "Output:" comment for the go build
// system to generate a call to it from the test main package.
func Example() {
C(0).f()
// Output:
}

View File

@ -1,504 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Compilebench benchmarks the speed of the Go compiler.
//
// Usage:
//
// compilebench [options]
//
// It times the compilation of various packages and prints results in
// the format used by package testing (and expected by golang.org/x/perf/cmd/benchstat).
//
// The options are:
//
// -alloc
// Report allocations.
//
// -compile exe
// Use exe as the path to the cmd/compile binary.
//
// -compileflags 'list'
// Pass the space-separated list of flags to the compilation.
//
// -link exe
// Use exe as the path to the cmd/link binary.
//
// -linkflags 'list'
// Pass the space-separated list of flags to the linker.
//
// -count n
// Run each benchmark n times (default 1).
//
// -cpuprofile file
// Write a CPU profile of the compiler to file.
//
// -go path
// Path to "go" command (default "go").
//
// -memprofile file
// Write a memory profile of the compiler to file.
//
// -memprofilerate rate
// Set runtime.MemProfileRate during compilation.
//
// -obj
// Report object file statistics.
//
// -pkg pkg
// Benchmark compiling a single package.
//
// -run regexp
// Only run benchmarks with names matching regexp.
//
// -short
// Skip long-running benchmarks.
//
// Although -cpuprofile and -memprofile are intended to write a
// combined profile for all the executed benchmarks to file,
// today they write only the profile for the last benchmark executed.
//
// The default memory profiling rate is one profile sample per 512 kB
// allocated (see ``go doc runtime.MemProfileRate'').
// Lowering the rate (for example, -memprofilerate 64000) produces
// a more fine-grained and therefore accurate profile, but it also incurs
// execution cost. For benchmark comparisons, never use timings
// obtained with a low -memprofilerate option.
//
// Example
//
// Assuming the base version of the compiler has been saved with
// ``toolstash save,'' this sequence compares the old and new compiler:
//
// compilebench -count 10 -compile $(toolstash -n compile) >old.txt
// compilebench -count 10 >new.txt
// benchstat old.txt new.txt
//
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
var (
goroot string
compiler string
linker string
runRE *regexp.Regexp
is6g bool
)
var (
flagGoCmd = flag.String("go", "go", "path to \"go\" command")
flagAlloc = flag.Bool("alloc", false, "report allocations")
flagObj = flag.Bool("obj", false, "report object file stats")
flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary")
flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile")
flagLinker = flag.String("link", "", "use `exe` as the cmd/link binary")
flagLinkerFlags = flag.String("linkflags", "", "additional `flags` to pass to link")
flagRun = flag.String("run", "", "run benchmarks matching `regexp`")
flagCount = flag.Int("count", 1, "run benchmarks `n` times")
flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`")
flagMemprofile = flag.String("memprofile", "", "write memory profile to `file`")
flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`")
flagPackage = flag.String("pkg", "", "if set, benchmark the package at path `pkg`")
flagShort = flag.Bool("short", false, "skip long-running benchmarks")
)
type test struct {
name string
r runner
}
type runner interface {
long() bool
run(name string, count int) error
}
var tests = []test{
{"BenchmarkTemplate", compile{"html/template"}},
{"BenchmarkUnicode", compile{"unicode"}},
{"BenchmarkGoTypes", compile{"go/types"}},
{"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}},
{"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}},
{"BenchmarkFlate", compile{"compress/flate"}},
{"BenchmarkGoParser", compile{"go/parser"}},
{"BenchmarkReflect", compile{"reflect"}},
{"BenchmarkTar", compile{"archive/tar"}},
{"BenchmarkXML", compile{"encoding/xml"}},
{"BenchmarkLinkCompiler", link{"cmd/compile"}},
{"BenchmarkStdCmd", goBuild{[]string{"std", "cmd"}}},
{"BenchmarkHelloSize", size{"$GOROOT/test/helloworld.go", false}},
{"BenchmarkCmdGoSize", size{"cmd/go", true}},
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n")
fmt.Fprintf(os.Stderr, "options:\n")
flag.PrintDefaults()
os.Exit(2)
}
func main() {
log.SetFlags(0)
log.SetPrefix("compilebench: ")
flag.Usage = usage
flag.Parse()
if flag.NArg() != 0 {
usage()
}
s, err := exec.Command(*flagGoCmd, "env", "GOROOT").CombinedOutput()
if err != nil {
log.Fatalf("%s env GOROOT: %v", *flagGoCmd, err)
}
goroot = strings.TrimSpace(string(s))
os.Setenv("GOROOT", goroot) // for any subcommands
compiler = *flagCompiler
if compiler == "" {
var foundTool string
foundTool, compiler = toolPath("compile", "6g")
if foundTool == "6g" {
is6g = true
}
}
linker = *flagLinker
if linker == "" && !is6g { // TODO: Support 6l
_, linker = toolPath("link")
}
if is6g {
*flagMemprofilerate = -1
*flagAlloc = false
*flagCpuprofile = ""
*flagMemprofile = ""
}
if *flagRun != "" {
r, err := regexp.Compile(*flagRun)
if err != nil {
log.Fatalf("invalid -run argument: %v", err)
}
runRE = r
}
if *flagPackage != "" {
tests = []test{
{"BenchmarkPkg", compile{*flagPackage}},
{"BenchmarkPkgLink", link{*flagPackage}},
}
runRE = nil
}
for i := 0; i < *flagCount; i++ {
for _, tt := range tests {
if tt.r.long() && *flagShort {
continue
}
if runRE == nil || runRE.MatchString(tt.name) {
if err := tt.r.run(tt.name, i); err != nil {
log.Printf("%s: %v", tt.name, err)
}
}
}
}
}
func toolPath(names ...string) (found, path string) {
var out1 []byte
var err1 error
for i, name := range names {
out, err := exec.Command(*flagGoCmd, "tool", "-n", name).CombinedOutput()
if err == nil {
return name, strings.TrimSpace(string(out))
}
if i == 0 {
out1, err1 = out, err
}
}
log.Fatalf("go tool -n %s: %v\n%s", names[0], err1, out1)
return "", ""
}
type Pkg struct {
Dir string
GoFiles []string
}
func goList(dir string) (*Pkg, error) {
var pkg Pkg
out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output()
if err != nil {
return nil, fmt.Errorf("go list -json %s: %v\n", dir, err)
}
if err := json.Unmarshal(out, &pkg); err != nil {
return nil, fmt.Errorf("go list -json %s: unmarshal: %v", dir, err)
}
return &pkg, nil
}
func runCmd(name string, cmd *exec.Cmd) error {
start := time.Now()
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%v\n%s", err, out)
}
fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
return nil
}
type goBuild struct{ pkgs []string }
func (goBuild) long() bool { return true }
func (r goBuild) run(name string, count int) error {
args := []string{"build", "-a"}
if *flagCompilerFlags != "" {
args = append(args, "-gcflags", *flagCompilerFlags)
}
args = append(args, r.pkgs...)
cmd := exec.Command(*flagGoCmd, args...)
cmd.Dir = filepath.Join(goroot, "src")
return runCmd(name, cmd)
}
type size struct {
// path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
path string
isLong bool
}
func (r size) long() bool { return r.isLong }
func (r size) run(name string, count int) error {
if strings.HasPrefix(r.path, "$GOROOT/") {
r.path = goroot + "/" + r.path[len("$GOROOT/"):]
}
cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", r.path)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
defer os.Remove("_compilebenchout_")
info, err := os.Stat("_compilebenchout_")
if err != nil {
return err
}
out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
if err != nil {
return fmt.Errorf("size: %v\n%s", err, out)
}
lines := strings.Split(string(out), "\n")
if len(lines) < 2 {
return fmt.Errorf("not enough output from size: %s", out)
}
f := strings.Fields(lines[1])
if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size())
} else if strings.Contains(lines[0], "bss") && len(f) >= 3 {
fmt.Printf("%s 1 %s text-bytes %s data-bytes %s bss-bytes %v exe-bytes\n", name, f[0], f[1], f[2], info.Size())
}
return nil
}
type compile struct{ dir string }
func (compile) long() bool { return false }
func (c compile) run(name string, count int) error {
// Make sure dependencies needed by go tool compile are installed to GOROOT/pkg.
out, err := exec.Command(*flagGoCmd, "build", "-i", c.dir).CombinedOutput()
if err != nil {
return fmt.Errorf("go build -i %s: %v\n%s", c.dir, err, out)
}
// Find dir and source file list.
pkg, err := goList(c.dir)
if err != nil {
return err
}
args := []string{"-o", "_compilebench_.o"}
args = append(args, strings.Fields(*flagCompilerFlags)...)
args = append(args, pkg.GoFiles...)
if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil {
return err
}
opath := pkg.Dir + "/_compilebench_.o"
if *flagObj {
// TODO(josharian): object files are big; just read enough to find what we seek.
data, err := ioutil.ReadFile(opath)
if err != nil {
log.Print(err)
}
// Find start of export data.
i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n")
// Count bytes to end of export data.
nexport := bytes.Index(data[i:], []byte("\n$$\n"))
fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport)
}
fmt.Println()
os.Remove(opath)
return nil
}
type link struct{ dir string }
func (link) long() bool { return false }
func (r link) run(name string, count int) error {
if linker == "" {
// No linker. Skip the test.
return nil
}
// Build dependencies.
out, err := exec.Command(*flagGoCmd, "build", "-i", "-o", "/dev/null", r.dir).CombinedOutput()
if err != nil {
return fmt.Errorf("go build -i %s: %v\n%s", r.dir, err, out)
}
// Build the main package.
pkg, err := goList(r.dir)
if err != nil {
return err
}
args := []string{"-o", "_compilebench_.o"}
args = append(args, pkg.GoFiles...)
cmd := exec.Command(compiler, args...)
cmd.Dir = pkg.Dir
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return fmt.Errorf("compiling: %v", err)
}
defer os.Remove(pkg.Dir + "/_compilebench_.o")
// Link the main package.
args = []string{"-o", "_compilebench_.exe"}
args = append(args, strings.Fields(*flagLinkerFlags)...)
args = append(args, "_compilebench_.o")
if err := runBuildCmd(name, count, pkg.Dir, linker, args); err != nil {
return err
}
fmt.Println()
defer os.Remove(pkg.Dir + "/_compilebench_.exe")
return err
}
// runBuildCmd runs "tool args..." in dir, measures standard build
// tool metrics, and prints a benchmark line. The caller may print
// additional metrics and then must print a newline.
//
// This assumes tool accepts standard build tool flags like
// -memprofilerate, -memprofile, and -cpuprofile.
func runBuildCmd(name string, count int, dir, tool string, args []string) error {
var preArgs []string
if *flagMemprofilerate >= 0 {
preArgs = append(preArgs, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
}
if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
if *flagAlloc || *flagMemprofile != "" {
preArgs = append(preArgs, "-memprofile", "_compilebench_.memprof")
}
if *flagCpuprofile != "" {
preArgs = append(preArgs, "-cpuprofile", "_compilebench_.cpuprof")
}
}
cmd := exec.Command(tool, append(preArgs, args...)...)
cmd.Dir = dir
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
start := time.Now()
err := cmd.Run()
if err != nil {
return err
}
end := time.Now()
haveAllocs := false
var allocs, allocbytes int64
if *flagAlloc || *flagMemprofile != "" {
out, err := ioutil.ReadFile(dir + "/_compilebench_.memprof")
if err != nil {
log.Print("cannot find memory profile after compilation")
}
for _, line := range strings.Split(string(out), "\n") {
f := strings.Fields(line)
if len(f) < 4 || f[0] != "#" || f[2] != "=" {
continue
}
val, err := strconv.ParseInt(f[3], 0, 64)
if err != nil {
continue
}
haveAllocs = true
switch f[1] {
case "TotalAlloc":
allocbytes = val
case "Mallocs":
allocs = val
}
}
if !haveAllocs {
log.Println("missing stats in memprof (golang.org/issue/18641)")
}
if *flagMemprofile != "" {
outpath := *flagMemprofile
if *flagCount != 1 {
outpath = fmt.Sprintf("%s_%d", outpath, count)
}
if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
log.Print(err)
}
}
os.Remove(dir + "/_compilebench_.memprof")
}
if *flagCpuprofile != "" {
out, err := ioutil.ReadFile(dir + "/_compilebench_.cpuprof")
if err != nil {
log.Print(err)
}
outpath := *flagCpuprofile
if *flagCount != 1 {
outpath = fmt.Sprintf("%s_%d", outpath, count)
}
if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
log.Print(err)
}
os.Remove(dir + "/_compilebench_.cpuprof")
}
wallns := end.Sub(start).Nanoseconds()
userns := cmd.ProcessState.UserTime().Nanoseconds()
fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns)
if haveAllocs {
fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs)
}
return nil
}

View File

@ -1,2 +0,0 @@
NOTE: For Go releases 1.5 and later, this tool lives in the
standard repository. The code here is not maintained.

View File

@ -19,7 +19,6 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings"
) )
const usageMessage = "" + const usageMessage = "" +
@ -84,7 +83,7 @@ func main() {
// Generate coverage-annotated source. // Generate coverage-annotated source.
if *mode != "" { if *mode != "" {
annotate(flag.Arg(0)) cover(flag.Arg(0))
return return
} }
@ -196,16 +195,17 @@ func (f *File) Visit(node ast.Node) ast.Visitor {
// if y { // if y {
// } // }
// } // }
const backupToElse = token.Pos(len("else ")) // The AST doesn't remember the else location. We can make an accurate guess.
switch stmt := n.Else.(type) { switch stmt := n.Else.(type) {
case *ast.IfStmt: case *ast.IfStmt:
block := &ast.BlockStmt{ block := &ast.BlockStmt{
Lbrace: n.Body.End(), // Start at end of the "if" block so the covered part looks like it starts at the "else". Lbrace: stmt.If - backupToElse, // So the covered part looks like it starts at the "else".
List: []ast.Stmt{stmt}, List: []ast.Stmt{stmt},
Rbrace: stmt.End(), Rbrace: stmt.End(),
} }
n.Else = block n.Else = block
case *ast.BlockStmt: case *ast.BlockStmt:
stmt.Lbrace = n.Body.End() // Start at end of the "if" block so the covered part looks like it starts at the "else". stmt.Lbrace -= backupToElse // So the block looks like it starts at the "else".
default: default:
panic("unexpected node type in if") panic("unexpected node type in if")
} }
@ -221,11 +221,6 @@ func (f *File) Visit(node ast.Node) ast.Visitor {
if n.Body == nil || len(n.Body.List) == 0 { if n.Body == nil || len(n.Body.List) == 0 {
return nil return nil
} }
case *ast.TypeSwitchStmt:
// Don't annotate an empty type switch - creates a syntax error.
if n.Body == nil || len(n.Body.List) == 0 {
return nil
}
} }
return f return f
} }
@ -297,7 +292,7 @@ func (f *File) addImport(path string) string {
var slashslash = []byte("//") var slashslash = []byte("//")
// initialComments returns the prefix of content containing only // initialComments returns the prefix of content containing only
// whitespace and line comments. Any +build directives must appear // whitepace and line comments. Any +build directives must appear
// within this region. This approach is more reliable than using // within this region. This approach is more reliable than using
// go/printer to print a modified AST containing comments. // go/printer to print a modified AST containing comments.
// //
@ -324,17 +319,16 @@ func initialComments(content []byte) []byte {
return content[:end] return content[:end]
} }
func annotate(name string) { func cover(name string) {
fset := token.NewFileSet() fset := token.NewFileSet()
content, err := ioutil.ReadFile(name) content, err := ioutil.ReadFile(name)
if err != nil { if err != nil {
log.Fatalf("cover: %s: %s", name, err) log.Fatalf("cover: %s: %s", name, err)
} }
parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments) parsedFile, err := parser.ParseFile(fset, name, content, 0)
if err != nil { if err != nil {
log.Fatalf("cover: %s: %s", name, err) log.Fatalf("cover: %s: %s", name, err)
} }
parsedFile.Comments = trimComments(parsedFile, fset)
file := &File{ file := &File{
fset: fset, fset: fset,
@ -360,26 +354,6 @@ func annotate(name string) {
file.addVariables(fd) file.addVariables(fd)
} }
// trimComments drops all but the //go: comments, some of which are semantically important.
// We drop all others because they can appear in places that cause our counters
// to appear in syntactically incorrect places. //go: appears at the beginning of
// the line and is syntactically safe.
func trimComments(file *ast.File, fset *token.FileSet) []*ast.CommentGroup {
var comments []*ast.CommentGroup
for _, group := range file.Comments {
var list []*ast.Comment
for _, comment := range group.List {
if strings.HasPrefix(comment.Text, "//go:") && fset.Position(comment.Slash).Column == 1 {
list = append(list, comment)
}
}
if list != nil {
comments = append(comments, &ast.CommentGroup{List: list})
}
}
return comments
}
func (f *File) print(w io.Writer) { func (f *File) print(w io.Writer) {
printer.Fprint(w, f.fset, f.astFile) printer.Fprint(w, f.fset, f.astFile)
} }
@ -458,7 +432,7 @@ func (f *File) newCounter(start, end token.Pos, numStmt int) ast.Stmt {
// //
// counters will be added before S1 and before S3. The block containing S2 // counters will be added before S1 and before S3. The block containing S2
// will be visited in a separate call. // will be visited in a separate call.
// TODO: Nested simple blocks get unnecessary (but correct) counters // TODO: Nested simple blocks get unecessary (but correct) counters
func (f *File) addCounters(pos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) []ast.Stmt { func (f *File) addCounters(pos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) []ast.Stmt {
// Special case: make sure we add a counter to an empty block. Can't do this below // Special case: make sure we add a counter to an empty block. Can't do this below
// or we will add a counter to an empty statement list after, say, a return statement. // or we will add a counter to an empty statement list after, say, a return statement.
@ -503,9 +477,6 @@ func (f *File) addCounters(pos, blockEnd token.Pos, list []ast.Stmt, extendToClo
// Therefore we draw a line at the start of the body of the first function literal we find. // Therefore we draw a line at the start of the body of the first function literal we find.
// TODO: what if there's more than one? Probably doesn't matter much. // TODO: what if there's more than one? Probably doesn't matter much.
func hasFuncLiteral(n ast.Node) (bool, token.Pos) { func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
if n == nil {
return false, 0
}
var literal funcLitFinder var literal funcLitFinder
ast.Walk(&literal, n) ast.Walk(&literal, n)
return literal.found(), token.Pos(literal) return literal.found(), token.Pos(literal)
@ -520,54 +491,24 @@ func (f *File) statementBoundary(s ast.Stmt) token.Pos {
// Treat blocks like basic blocks to avoid overlapping counters. // Treat blocks like basic blocks to avoid overlapping counters.
return s.Lbrace return s.Lbrace
case *ast.IfStmt: case *ast.IfStmt:
found, pos := hasFuncLiteral(s.Init)
if found {
return pos
}
found, pos = hasFuncLiteral(s.Cond)
if found {
return pos
}
return s.Body.Lbrace return s.Body.Lbrace
case *ast.ForStmt: case *ast.ForStmt:
found, pos := hasFuncLiteral(s.Init)
if found {
return pos
}
found, pos = hasFuncLiteral(s.Cond)
if found {
return pos
}
found, pos = hasFuncLiteral(s.Post)
if found {
return pos
}
return s.Body.Lbrace return s.Body.Lbrace
case *ast.LabeledStmt: case *ast.LabeledStmt:
return f.statementBoundary(s.Stmt) return f.statementBoundary(s.Stmt)
case *ast.RangeStmt: case *ast.RangeStmt:
// Ranges might loop over things with function literals.: for _ = range []func(){ ... } {.
// TODO: There are a few other such possibilities, but they're extremely unlikely.
found, pos := hasFuncLiteral(s.X) found, pos := hasFuncLiteral(s.X)
if found { if found {
return pos return pos
} }
return s.Body.Lbrace return s.Body.Lbrace
case *ast.SwitchStmt: case *ast.SwitchStmt:
found, pos := hasFuncLiteral(s.Init)
if found {
return pos
}
found, pos = hasFuncLiteral(s.Tag)
if found {
return pos
}
return s.Body.Lbrace return s.Body.Lbrace
case *ast.SelectStmt: case *ast.SelectStmt:
return s.Body.Lbrace return s.Body.Lbrace
case *ast.TypeSwitchStmt: case *ast.TypeSwitchStmt:
found, pos := hasFuncLiteral(s.Init)
if found {
return pos
}
return s.Body.Lbrace return s.Body.Lbrace
} }
// If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal. // If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal.
@ -605,16 +546,6 @@ func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
return true return true
case *ast.TypeSwitchStmt: case *ast.TypeSwitchStmt:
return true return true
case *ast.ExprStmt:
// Calls to panic change the flow.
// We really should verify that "panic" is the predefined function,
// but without type checking we can't and the likelihood of it being
// an actual problem is vanishingly small.
if call, ok := s.X.(*ast.CallExpr); ok {
if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
return true
}
}
} }
found, _ := hasFuncLiteral(s) found, _ := hasFuncLiteral(s)
return found return found
@ -653,11 +584,6 @@ func (b blockSlice) Len() int { return len(b) }
func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte } func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte }
func (b blockSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b blockSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
// offset translates a token position into a 0-indexed byte offset.
func (f *File) offset(pos token.Pos) int {
return f.fset.Position(pos).Offset
}
// addVariables adds to the end of the file the declarations to set up the counter and position variables. // addVariables adds to the end of the file the declarations to set up the counter and position variables.
func (f *File) addVariables(w io.Writer) { func (f *File) addVariables(w io.Writer) {
// Self-check: Verify that the instrumented basic blocks are disjoint. // Self-check: Verify that the instrumented basic blocks are disjoint.
@ -670,10 +596,7 @@ func (f *File) addVariables(w io.Writer) {
for i := 1; i < len(t); i++ { for i := 1; i < len(t); i++ {
if t[i-1].endByte > t[i].startByte { if t[i-1].endByte > t[i].startByte {
fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index) fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
// Note: error message is in byte positions, not token positions. fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n", f.name, t[i-1].startByte, t[i-1].endByte, f.name, t[i].startByte, t[i].endByte)
fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
} }
} }

View File

@ -2,10 +2,6 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// No testdata on Android.
// +build !android
package main_test package main_test
import ( import (
@ -54,9 +50,6 @@ func TestCover(t *testing.T) {
lines[i] = bytes.Replace(line, []byte("LINE"), []byte(fmt.Sprint(i+1)), -1) lines[i] = bytes.Replace(line, []byte("LINE"), []byte(fmt.Sprint(i+1)), -1)
} }
err = ioutil.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666) err = ioutil.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666)
if err != nil {
t.Fatal(err)
}
// defer removal of test_line.go // defer removal of test_line.go
if !debug { if !debug {

View File

@ -17,11 +17,5 @@ be mildly confused by single statements with multiple function literals.
For usage information, please see: For usage information, please see:
go help testflag go help testflag
go tool cover -help go tool cover -help
No longer maintained:
For Go releases 1.5 and later, this tool lives in the
standard repository. The code here is not maintained.
*/ */
package main // import "golang.org/x/tools/cmd/cover" package main

View File

@ -10,30 +10,26 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build"
"go/parser" "go/parser"
"go/token" "go/token"
"os" "os"
"path/filepath"
"text/tabwriter" "text/tabwriter"
"golang.org/x/tools/cover"
) )
// funcOutput takes two file names as arguments, a coverage profile to read as input and an output // funcOutput takes two file names as arguments, a coverage profile to read as input and an output
// file to write ("" means to write to standard output). The function reads the profile and produces // file to write ("" means to write to standard output). The function reads the profile and produces
// as output the coverage data broken down by function, like this: // as output the coverage data broken down by function, like this:
// //
// fmt/format.go:30: init 100.0% // fmt/format.go: init 100.0%
// fmt/format.go:57: clearflags 100.0% // fmt/format.go: computePadding 84.6%
// ... // ...
// fmt/scan.go:1046: doScan 100.0% // fmt/scan.go: doScan 100.0%
// fmt/scan.go:1075: advance 96.2% // fmt/scan.go: advance 96.2%
// fmt/scan.go:1119: doScanf 96.8% // fmt/scan.go: doScanf 96.8%
// total: (statements) 91.9% // total: (statements) 91.4%
func funcOutput(profile, outputFile string) error { func funcOutput(profile, outputFile string) error {
profiles, err := cover.ParseProfiles(profile) profiles, err := ParseProfiles(profile)
if err != nil { if err != nil {
return err return err
} }
@ -68,7 +64,7 @@ func funcOutput(profile, outputFile string) error {
// Now match up functions and profile blocks. // Now match up functions and profile blocks.
for _, f := range funcs { for _, f := range funcs {
c, t := f.coverage(profile) c, t := f.coverage(profile)
fmt.Fprintf(tabber, "%s:%d:\t%s\t%.1f%%\n", fn, f.startLine, f.name, 100.0*float64(c)/float64(t)) fmt.Fprintf(tabber, "%s:\t%s\t%.1f%%\n", fn, f.name, 100.0*float64(c)/float64(t))
total += t total += t
covered += c covered += c
} }
@ -130,7 +126,7 @@ func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor {
} }
// coverage returns the fraction of the statements in the function that were covered, as a numerator and denominator. // coverage returns the fraction of the statements in the function that were covered, as a numerator and denominator.
func (f *FuncExtent) coverage(profile *cover.Profile) (num, den int64) { func (f *FuncExtent) coverage(profile *Profile) (num, den int64) {
// We could avoid making this n^2 overall by doing a single scan and annotating the functions, // We could avoid making this n^2 overall by doing a single scan and annotating the functions,
// but the sizes of the data structures is never very large and the scan is almost instantaneous. // but the sizes of the data structures is never very large and the scan is almost instantaneous.
var covered, total int64 var covered, total int64
@ -154,13 +150,3 @@ func (f *FuncExtent) coverage(profile *cover.Profile) (num, den int64) {
} }
return covered, total return covered, total
} }
// findFile finds the location of the named file in GOROOT, GOPATH etc.
func findFile(file string) (string, error) {
dir, file := filepath.Split(file)
pkg, err := build.Import(dir, ".", build.FindOnly)
if err != nil {
return "", fmt.Errorf("can't find %q: %v", file, err)
}
return filepath.Join(pkg.Dir, file), nil
}

View File

@ -16,15 +16,13 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"golang.org/x/tools/cover"
) )
// htmlOutput reads the profile data from profile and generates an HTML // htmlOutput reads the profile data from profile and generates an HTML
// coverage report, writing it to outfile. If outfile is empty, // coverage report, writing it to outfile. If outfile is empty,
// it writes the report to a temporary file and opens it in a web browser. // it writes the report to a temporary file and opens it in a web browser.
func htmlOutput(profile, outfile string) error { func htmlOutput(profile, outfile string) error {
profiles, err := cover.ParseProfiles(profile) profiles, err := ParseProfiles(profile)
if err != nil { if err != nil {
return err return err
} }
@ -50,9 +48,8 @@ func htmlOutput(profile, outfile string) error {
return err return err
} }
d.Files = append(d.Files, &templateFile{ d.Files = append(d.Files, &templateFile{
Name: fn, Name: fn,
Body: template.HTML(buf.String()), Body: template.HTML(buf.String()),
Coverage: percentCovered(profile),
}) })
} }
@ -67,9 +64,6 @@ func htmlOutput(profile, outfile string) error {
} else { } else {
out, err = os.Create(outfile) out, err = os.Create(outfile)
} }
if err != nil {
return err
}
err = htmlTemplate.Execute(out, d) err = htmlTemplate.Execute(out, d)
if err == nil { if err == nil {
err = out.Close() err = out.Close()
@ -87,26 +81,9 @@ func htmlOutput(profile, outfile string) error {
return nil return nil
} }
// percentCovered returns, as a percentage, the fraction of the statements in
// the profile covered by the test run.
// In effect, it reports the coverage of a given source file.
func percentCovered(p *cover.Profile) float64 {
var total, covered int64
for _, b := range p.Blocks {
total += int64(b.NumStmt)
if b.Count > 0 {
covered += int64(b.NumStmt)
}
}
if total == 0 {
return 0
}
return float64(covered) / float64(total) * 100
}
// htmlGen generates an HTML coverage report with the provided filename, // htmlGen generates an HTML coverage report with the provided filename,
// source code, and tokens, and writes it to the given Writer. // source code, and tokens, and writes it to the given Writer.
func htmlGen(w io.Writer, src []byte, boundaries []cover.Boundary) error { func htmlGen(w io.Writer, src []byte, boundaries []Boundary) error {
dst := bufio.NewWriter(w) dst := bufio.NewWriter(w)
for i := range src { for i := range src {
for len(boundaries) > 0 && boundaries[0].Offset == i { for len(boundaries) > 0 && boundaries[0].Offset == i {
@ -187,9 +164,8 @@ type templateData struct {
} }
type templateFile struct { type templateFile struct {
Name string Name string
Body template.HTML Body template.HTML
Coverage float64
} }
const tmplHTML = ` const tmplHTML = `
@ -237,7 +213,7 @@ const tmplHTML = `
<div id="nav"> <div id="nav">
<select id="files"> <select id="files">
{{range $i, $f := .Files}} {{range $i, $f := .Files}}
<option value="file{{$i}}">{{$f.Name}} ({{printf "%.1f" $f.Coverage}}%)</option> <option value="file{{$i}}">{{$f.Name}}</option>
{{end}} {{end}}
</select> </select>
</div> </div>

View File

@ -2,15 +2,15 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package cover provides support for parsing coverage profiles package main
// generated by "go test -coverprofile=cover.out".
package cover // import "golang.org/x/tools/cover"
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"go/build"
"math" "math"
"os" "os"
"path/filepath"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
@ -37,10 +37,10 @@ func (p byFileName) Len() int { return len(p) }
func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName } func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName }
func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// ParseProfiles parses profile data in the specified file and returns a // ParseProfiles parses profile data from the given Reader and returns a
// Profile for each source file described therein. // Profile for each file.
func ParseProfiles(fileName string) ([]*Profile, error) { func ParseProfiles(fileName string) ([]*Profile, error) {
pf, err := os.Open(fileName) pf, err := os.Open(profile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -66,7 +66,7 @@ func ParseProfiles(fileName string) ([]*Profile, error) {
} }
m := lineRe.FindStringSubmatch(line) m := lineRe.FindStringSubmatch(line)
if m == nil { if m == nil {
return nil, fmt.Errorf("line %q doesn't match expected format: %v", line, lineRe) return nil, fmt.Errorf("line %q doesn't match expected format: %v", m, lineRe)
} }
fn := m[1] fn := m[1]
p := files[fn] p := files[fn]
@ -91,29 +91,6 @@ func ParseProfiles(fileName string) ([]*Profile, error) {
} }
for _, p := range files { for _, p := range files {
sort.Sort(blocksByStart(p.Blocks)) sort.Sort(blocksByStart(p.Blocks))
// Merge samples from the same location.
j := 1
for i := 1; i < len(p.Blocks); i++ {
b := p.Blocks[i]
last := p.Blocks[j-1]
if b.StartLine == last.StartLine &&
b.StartCol == last.StartCol &&
b.EndLine == last.EndLine &&
b.EndCol == last.EndCol {
if b.NumStmt != last.NumStmt {
return nil, fmt.Errorf("inconsistent NumStmt: changed from %d to %d", last.NumStmt, b.NumStmt)
}
if mode == "set" {
p.Blocks[j-1].Count |= b.Count
} else {
p.Blocks[j-1].Count += b.Count
}
continue
}
p.Blocks[j] = b
j++
}
p.Blocks = p.Blocks[:j]
} }
// Generate a sorted slice. // Generate a sorted slice.
profiles := make([]*Profile, 0, len(files)) profiles := make([]*Profile, 0, len(files))
@ -185,7 +162,7 @@ func (p *Profile) Boundaries(src []byte) (boundaries []Boundary) {
if b.StartLine == line && b.StartCol == col { if b.StartLine == line && b.StartCol == col {
boundaries = append(boundaries, boundary(si, true, b.Count)) boundaries = append(boundaries, boundary(si, true, b.Count))
} }
if b.EndLine == line && b.EndCol == col || line > b.EndLine { if b.EndLine == line && b.EndCol == col {
boundaries = append(boundaries, boundary(si, false, 0)) boundaries = append(boundaries, boundary(si, false, 0))
bi++ bi++
continue // Don't advance through src; maybe the next block starts here. continue // Don't advance through src; maybe the next block starts here.
@ -211,3 +188,13 @@ func (b boundariesByPos) Less(i, j int) bool {
} }
return b[i].Offset < b[j].Offset return b[i].Offset < b[j].Offset
} }
// findFile finds the location of the named file in GOROOT, GOPATH etc.
func findFile(file string) (string, error) {
dir, file := filepath.Split(file)
pkg, err := build.Import(dir, ".", build.FindOnly)
if err != nil {
return "", fmt.Errorf("can't find %q: %v", file, err)
}
return filepath.Join(pkg.Dir, file), nil
}

View File

@ -58,31 +58,12 @@ func verify() {
PASS = false PASS = false
} }
} }
verifyPanic()
if !PASS { if !PASS {
fmt.Fprintf(os.Stderr, "FAIL\n") fmt.Fprintf(os.Stderr, "FAIL\n")
os.Exit(2) os.Exit(2)
} }
} }
// verifyPanic is a special check for the known counter that should be
// after the panic call in testPanic.
func verifyPanic() {
if coverTest.Count[panicIndex-1] != 1 {
// Sanity check for test before panic.
fmt.Fprintf(os.Stderr, "bad before panic")
PASS = false
}
if coverTest.Count[panicIndex] != 0 {
fmt.Fprintf(os.Stderr, "bad at panic: %d should be 0\n", coverTest.Count[panicIndex])
PASS = false
}
if coverTest.Count[panicIndex+1] != 1 {
fmt.Fprintf(os.Stderr, "bad after panic")
PASS = false
}
}
// count returns the count and index for the counter at the specified line. // count returns the count and index for the counter at the specified line.
func count(line uint32) (uint32, int) { func count(line uint32) (uint32, int) {
// Linear search is fine. Choose perfect fit over approximate. // Linear search is fine. Choose perfect fit over approximate.

View File

@ -22,24 +22,6 @@ func testAll() {
testTypeSwitch() testTypeSwitch()
testSelect1() testSelect1()
testSelect2() testSelect2()
testPanic()
testEmptySwitches()
}
// The indexes of the counters in testPanic are known to main.go
const panicIndex = 3
// This test appears first because the index of its counters is known to main.go
func testPanic() {
defer func() {
recover()
}()
check(LINE, 1)
panic("should not get next line")
check(LINE, 0) // this is GoCover.Count[panicIndex]
// The next counter is in testSimple and it will be non-zero.
// If the panic above does not trigger a counter, the test will fail
// because GoCover.Count[panicIndex] will be the one in testSimple.
} }
func testSimple() { func testSimple() {
@ -86,13 +68,10 @@ func testIf() {
check(LINE, 0) check(LINE, 0)
} }
} }
if func(a, b int) bool { return a < b }(3, 4) {
check(LINE, 1)
}
} }
func testFor() { func testFor() {
for i := 0; i < 10; func() { i++; check(LINE, 10) }() { for i := 0; i < 10; i++ {
check(LINE, 10) check(LINE, 10)
} }
} }
@ -125,7 +104,7 @@ func testBlockRun() {
} }
func testSwitch() { func testSwitch() {
for i := 0; i < 5; func() { i++; check(LINE, 5) }() { for i := 0; i < 5; i++ {
switch i { switch i {
case 0: case 0:
check(LINE, 1) check(LINE, 1)
@ -142,7 +121,7 @@ func testSwitch() {
func testTypeSwitch() { func testTypeSwitch() {
var x = []interface{}{1, 2.0, "hi"} var x = []interface{}{1, 2.0, "hi"}
for _, v := range x { for _, v := range x {
switch func() { check(LINE, 3) }(); v.(type) { switch v.(type) {
case int: case int:
check(LINE, 1) check(LINE, 1)
case float64: case float64:
@ -196,23 +175,3 @@ func testSelect2() {
} }
} }
} }
// Empty control statements created syntax errors. This function
// is here just to be sure that those are handled correctly now.
func testEmptySwitches() {
check(LINE, 1)
switch 3 {
}
check(LINE, 1)
switch i := (interface{})(3).(int); i {
}
check(LINE, 1)
c := make(chan int)
go func() {
check(LINE, 1)
c <- 1
select {}
}()
<-c
check(LINE, 1)
}

View File

@ -1,589 +0,0 @@
// Copyright 2019 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 digraph command performs queries over unlabelled directed graphs
represented in text form. It is intended to integrate nicely with
typical UNIX command pipelines.
Usage:
your-application | digraph [command]
The support commands are:
nodes
the set of all nodes
degree
the in-degree and out-degree of each node
preds <node> ...
the set of immediate predecessors of the specified nodes
succs <node> ...
the set of immediate successors of the specified nodes
forward <node> ...
the set of nodes transitively reachable from the specified nodes
reverse <node> ...
the set of nodes that transitively reach the specified nodes
somepath <node> <node>
the list of nodes on some arbitrary path from the first node to the second
allpaths <node> <node>
the set of nodes on all paths from the first node to the second
sccs
all strongly connected components (one per line)
scc <node>
the set of nodes nodes strongly connected to the specified one
Input format:
Each line contains zero or more words. Words are separated by unquoted
whitespace; words may contain Go-style double-quoted portions, allowing spaces
and other characters to be expressed.
Each word declares a node, and if there are more than one, an edge from the
first to each subsequent one. The graph is provided on the standard input.
For instance, the following (acyclic) graph specifies a partial order among the
subtasks of getting dressed:
$ cat clothes.txt
socks shoes
"boxer shorts" pants
pants belt shoes
shirt tie sweater
sweater jacket
hat
The line "shirt tie sweater" indicates the two edges shirt -> tie and
shirt -> sweater, not shirt -> tie -> sweater.
Example usage:
Using digraph with existing Go tools:
$ go mod graph | digraph nodes # Operate on the Go module graph.
$ go list -m all | digraph nodes # Operate on the Go package graph.
Show the transitive closure of imports of the digraph tool itself:
$ go list -f '{{.ImportPath}} {{join .Imports " "}}' ... | digraph forward golang.org/x/tools/cmd/digraph
Show which clothes (see above) must be donned before a jacket:
$ digraph reverse jacket
*/
package main // import "golang.org/x/tools/cmd/digraph"
// TODO(adonovan):
// - support input files other than stdin
// - support alternative formats (AT&T GraphViz, CSV, etc),
// a comment syntax, etc.
// - allow queries to nest, like Blaze query language.
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"os"
"sort"
"strconv"
"unicode"
"unicode/utf8"
)
func usage() {
fmt.Fprintf(os.Stderr, `Usage: your-application | digraph [command]
The support commands are:
nodes
the set of all nodes
degree
the in-degree and out-degree of each node
preds <node> ...
the set of immediate predecessors of the specified nodes
succs <node> ...
the set of immediate successors of the specified nodes
forward <node> ...
the set of nodes transitively reachable from the specified nodes
reverse <node> ...
the set of nodes that transitively reach the specified nodes
somepath <node> <node>
the list of nodes on some arbitrary path from the first node to the second
allpaths <node> <node>
the set of nodes on all paths from the first node to the second
sccs
all strongly connected components (one per line)
scc <node>
the set of nodes nodes strongly connected to the specified one
`)
os.Exit(2)
}
func main() {
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
usage()
}
if err := digraph(args[0], args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "digraph: %s\n", err)
os.Exit(1)
}
}
type nodelist []string
func (l nodelist) println(sep string) {
for i, label := range l {
if i > 0 {
fmt.Fprint(stdout, sep)
}
fmt.Fprint(stdout, label)
}
fmt.Fprintln(stdout)
}
type nodeset map[string]bool
func (s nodeset) sort() nodelist {
labels := make(nodelist, len(s))
var i int
for label := range s {
labels[i] = label
i++
}
sort.Strings(labels)
return labels
}
func (s nodeset) addAll(x nodeset) {
for label := range x {
s[label] = true
}
}
// A graph maps nodes to the non-nil set of their immediate successors.
type graph map[string]nodeset
func (g graph) addNode(label string) nodeset {
edges := g[label]
if edges == nil {
edges = make(nodeset)
g[label] = edges
}
return edges
}
func (g graph) addEdges(from string, to ...string) {
edges := g.addNode(from)
for _, to := range to {
g.addNode(to)
edges[to] = true
}
}
func (g graph) reachableFrom(roots nodeset) nodeset {
seen := make(nodeset)
var visit func(label string)
visit = func(label string) {
if !seen[label] {
seen[label] = true
for e := range g[label] {
visit(e)
}
}
}
for root := range roots {
visit(root)
}
return seen
}
func (g graph) transpose() graph {
rev := make(graph)
for label, edges := range g {
rev.addNode(label)
for succ := range edges {
rev.addEdges(succ, label)
}
}
return rev
}
func (g graph) sccs() []nodeset {
// Kosaraju's algorithm---Tarjan is overkill here.
// Forward pass.
S := make(nodelist, 0, len(g)) // postorder stack
seen := make(nodeset)
var visit func(label string)
visit = func(label string) {
if !seen[label] {
seen[label] = true
for e := range g[label] {
visit(e)
}
S = append(S, label)
}
}
for label := range g {
visit(label)
}
// Reverse pass.
rev := g.transpose()
var scc nodeset
seen = make(nodeset)
var rvisit func(label string)
rvisit = func(label string) {
if !seen[label] {
seen[label] = true
scc[label] = true
for e := range rev[label] {
rvisit(e)
}
}
}
var sccs []nodeset
for len(S) > 0 {
top := S[len(S)-1]
S = S[:len(S)-1] // pop
if !seen[top] {
scc = make(nodeset)
rvisit(top)
sccs = append(sccs, scc)
}
}
return sccs
}
func (g graph) allpaths(from, to string) error {
// Mark all nodes to "to".
seen := make(nodeset) // value of seen[x] indicates whether x is on some path to "to"
var visit func(node string) bool
visit = func(node string) bool {
reachesTo, ok := seen[node]
if !ok {
reachesTo = node == to
seen[node] = reachesTo
for e := range g[node] {
if visit(e) {
reachesTo = true
}
}
if reachesTo && node != to {
seen[node] = true
}
}
return reachesTo
}
visit(from)
// For each marked node, collect its marked successors.
var edges []string
for n := range seen {
for succ := range g[n] {
if seen[succ] {
edges = append(edges, n+" "+succ)
}
}
}
// Sort (so that this method is deterministic) and print edges.
sort.Strings(edges)
for _, e := range edges {
fmt.Fprintln(stdout, e)
}
return nil
}
func parse(rd io.Reader) (graph, error) {
g := make(graph)
var linenum int
in := bufio.NewScanner(rd)
for in.Scan() {
linenum++
// Split into words, honoring double-quotes per Go spec.
words, err := split(in.Text())
if err != nil {
return nil, fmt.Errorf("at line %d: %v", linenum, err)
}
if len(words) > 0 {
g.addEdges(words[0], words[1:]...)
}
}
if err := in.Err(); err != nil {
return nil, err
}
return g, nil
}
// Overridable for testing purposes.
var stdin io.Reader = os.Stdin
var stdout io.Writer = os.Stdout
func digraph(cmd string, args []string) error {
// Parse the input graph.
g, err := parse(stdin)
if err != nil {
return err
}
// Parse the command line.
switch cmd {
case "nodes":
if len(args) != 0 {
return fmt.Errorf("usage: digraph nodes")
}
nodes := make(nodeset)
for label := range g {
nodes[label] = true
}
nodes.sort().println("\n")
case "degree":
if len(args) != 0 {
return fmt.Errorf("usage: digraph degree")
}
nodes := make(nodeset)
for label := range g {
nodes[label] = true
}
rev := g.transpose()
for _, label := range nodes.sort() {
fmt.Fprintf(stdout, "%d\t%d\t%s\n", len(rev[label]), len(g[label]), label)
}
case "succs", "preds":
if len(args) == 0 {
return fmt.Errorf("usage: digraph %s <label> ...", cmd)
}
g := g
if cmd == "preds" {
g = g.transpose()
}
result := make(nodeset)
for _, root := range args {
edges := g[root]
if edges == nil {
return fmt.Errorf("no such node %q", root)
}
result.addAll(edges)
}
result.sort().println("\n")
case "forward", "reverse":
if len(args) == 0 {
return fmt.Errorf("usage: digraph %s <label> ...", cmd)
}
roots := make(nodeset)
for _, root := range args {
if g[root] == nil {
return fmt.Errorf("no such node %q", root)
}
roots[root] = true
}
g := g
if cmd == "reverse" {
g = g.transpose()
}
g.reachableFrom(roots).sort().println("\n")
case "somepath":
if len(args) != 2 {
return fmt.Errorf("usage: digraph somepath <from> <to>")
}
from, to := args[0], args[1]
if g[from] == nil {
return fmt.Errorf("no such 'from' node %q", from)
}
if g[to] == nil {
return fmt.Errorf("no such 'to' node %q", to)
}
seen := make(nodeset)
var visit func(path nodelist, label string) bool
visit = func(path nodelist, label string) bool {
if !seen[label] {
seen[label] = true
if label == to {
append(path, label).println("\n")
return true // unwind
}
for e := range g[label] {
if visit(append(path, label), e) {
return true
}
}
}
return false
}
if !visit(make(nodelist, 0, 100), from) {
return fmt.Errorf("no path from %q to %q", args[0], args[1])
}
case "allpaths":
if len(args) != 2 {
return fmt.Errorf("usage: digraph allpaths <from> <to>")
}
from, to := args[0], args[1]
if g[from] == nil {
return fmt.Errorf("no such 'from' node %q", from)
}
if g[to] == nil {
return fmt.Errorf("no such 'to' node %q", to)
}
g.allpaths(from, to)
case "sccs":
if len(args) != 0 {
return fmt.Errorf("usage: digraph sccs")
}
for _, scc := range g.sccs() {
scc.sort().println(" ")
}
case "scc":
if len(args) != 1 {
return fmt.Errorf("usage: digraph scc <label>")
}
label := args[0]
if g[label] == nil {
return fmt.Errorf("no such node %q", label)
}
for _, scc := range g.sccs() {
if scc[label] {
scc.sort().println("\n")
break
}
}
default:
return fmt.Errorf("no such command %q", cmd)
}
return nil
}
// -- Utilities --------------------------------------------------------
// split splits a line into words, which are generally separated by
// spaces, but Go-style double-quoted string literals are also supported.
// (This approximates the behaviour of the Bourne shell.)
//
// `one "two three"` -> ["one" "two three"]
// `a"\n"b` -> ["a\nb"]
//
func split(line string) ([]string, error) {
var (
words []string
inWord bool
current bytes.Buffer
)
for len(line) > 0 {
r, size := utf8.DecodeRuneInString(line)
if unicode.IsSpace(r) {
if inWord {
words = append(words, current.String())
current.Reset()
inWord = false
}
} else if r == '"' {
var ok bool
size, ok = quotedLength(line)
if !ok {
return nil, errors.New("invalid quotation")
}
s, err := strconv.Unquote(line[:size])
if err != nil {
return nil, err
}
current.WriteString(s)
inWord = true
} else {
current.WriteRune(r)
inWord = true
}
line = line[size:]
}
if inWord {
words = append(words, current.String())
}
return words, nil
}
// quotedLength returns the length in bytes of the prefix of input that
// contain a possibly-valid double-quoted Go string literal.
//
// On success, n is at least two (""); input[:n] may be passed to
// strconv.Unquote to interpret its value, and input[n:] contains the
// rest of the input.
//
// On failure, quotedLength returns false, and the entire input can be
// passed to strconv.Unquote if an informative error message is desired.
//
// quotedLength does not and need not detect all errors, such as
// invalid hex or octal escape sequences, since it assumes
// strconv.Unquote will be applied to the prefix. It guarantees only
// that if there is a prefix of input containing a valid string literal,
// its length is returned.
//
// TODO(adonovan): move this into a strconv-like utility package.
//
func quotedLength(input string) (n int, ok bool) {
var offset int
// next returns the rune at offset, or -1 on EOF.
// offset advances to just after that rune.
next := func() rune {
if offset < len(input) {
r, size := utf8.DecodeRuneInString(input[offset:])
offset += size
return r
}
return -1
}
if next() != '"' {
return // error: not a quotation
}
for {
r := next()
if r == '\n' || r < 0 {
return // error: string literal not terminated
}
if r == '"' {
return offset, true // success
}
if r == '\\' {
var skip int
switch next() {
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"':
skip = 0
case '0', '1', '2', '3', '4', '5', '6', '7':
skip = 2
case 'x':
skip = 2
case 'u':
skip = 4
case 'U':
skip = 8
default:
return // error: invalid escape
}
for i := 0; i < skip; i++ {
next()
}
}
}
}

View File

@ -1,227 +0,0 @@
// Copyright 2019 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"
"reflect"
"strings"
"testing"
)
func TestDigraph(t *testing.T) {
const g1 = `
socks shoes
shorts pants
pants belt shoes
shirt tie sweater
sweater jacket
hat
`
const g2 = `
a b c
b d
c d
d c
`
for _, test := range []struct {
name string
input string
cmd string
args []string
want string
}{
{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
{"scss", g2, "sccs", nil, "a\nb\nc d\n"},
{"scc", g2, "scc", []string{"d"}, "c\nd\n"},
{"succs", g2, "succs", []string{"a"}, "b\nc\n"},
{"preds", g2, "preds", []string{"c"}, "a\nd\n"},
{"preds multiple args", g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\n"},
} {
t.Run(test.name, func(t *testing.T) {
stdin = strings.NewReader(test.input)
stdout = new(bytes.Buffer)
if err := digraph(test.cmd, test.args); err != nil {
t.Fatal(err)
}
got := stdout.(fmt.Stringer).String()
if got != test.want {
t.Errorf("digraph(%s, %s) = got %q, want %q", test.cmd, test.args, got, test.want)
}
})
}
// TODO(adonovan):
// - test somepath (it's nondeterministic).
// - test errors
}
func TestAllpaths(t *testing.T) {
for _, test := range []struct {
name string
in string
to string // from is always "A"
want string
}{
{
name: "Basic",
in: "A B\nB C",
to: "B",
want: "A B\n",
},
{
name: "Long",
in: "A B\nB C\n",
to: "C",
want: "A B\nB C\n",
},
{
name: "Cycle Basic",
in: "A B\nB A",
to: "B",
want: "A B\nB A\n",
},
{
name: "Cycle Path Out",
// A <-> B -> C -> D
in: "A B\nB A\nB C\nC D",
to: "C",
want: "A B\nB A\nB C\n",
},
{
name: "Cycle Path Out Further Out",
// A -> B <-> C -> D -> E
in: "A B\nB C\nC D\nC B\nD E",
to: "D",
want: "A B\nB C\nC B\nC D\n",
},
{
name: "Two Paths Basic",
// /-> C --\
// A -> B -- -> E -> F
// \-> D --/
in: "A B\nB C\nC E\nB D\nD E\nE F",
to: "E",
want: "A B\nB C\nB D\nC E\nD E\n",
},
{
name: "Two Paths With One Immediately From Start",
// /-> B -+ -> D
// A -- |
// \-> C <+
in: "A B\nA C\nB C\nB D",
to: "C",
want: "A B\nA C\nB C\n",
},
{
name: "Two Paths Further Up",
// /-> B --\
// A -- -> D -> E -> F
// \-> C --/
in: "A B\nA C\nB D\nC D\nD E\nE F",
to: "E",
want: "A B\nA C\nB D\nC D\nD E\n",
},
{
// We should include A - C - D even though it's further up the
// second path than D (which would already be in the graph by
// the time we get around to integrating the second path).
name: "Two Splits",
// /-> B --\ /-> E --\
// A -- -> D -- -> G -> H
// \-> C --/ \-> F --/
in: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\nG H",
to: "G",
want: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\n",
},
{
// D - E should not be duplicated.
name: "Two Paths - Two Splits With Gap",
// /-> B --\ /-> F --\
// A -- -> D -> E -- -> H -> I
// \-> C --/ \-> G --/
in: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\nH I",
to: "H",
want: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\n",
},
} {
t.Run(test.name, func(t *testing.T) {
stdin = strings.NewReader(test.in)
stdout = new(bytes.Buffer)
if err := digraph("allpaths", []string{"A", test.to}); err != nil {
t.Fatal(err)
}
got := stdout.(fmt.Stringer).String()
if got != test.want {
t.Errorf("digraph(allpaths, A, %s) = got %q, want %q", test.to, got, test.want)
}
})
}
}
func TestSplit(t *testing.T) {
for _, test := range []struct {
line string
want []string
}{
{`one "2a 2b" three`, []string{"one", "2a 2b", "three"}},
{`one tw"\n\x0a\u000a\012"o three`, []string{"one", "tw\n\n\n\no", "three"}},
} {
got, err := split(test.line)
if err != nil {
t.Errorf("split(%s) failed: %v", test.line, err)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("split(%s) = %v, want %v", test.line, got, test.want)
}
}
}
func TestQuotedLength(t *testing.T) {
for _, test := range []struct {
input string
want int
}{
{`"abc"`, 5},
{`"abc"def`, 5},
{`"abc\"d"ef`, 8}, // "abc\"d" is consumed, ef is residue
{`"\012\n\x0a\u000a\U0000000a"`, 28},
{"\"\xff\"", 3}, // bad UTF-8 is ok
{`"\xff"`, 6}, // hex escape for bad UTF-8 is ok
} {
got, ok := quotedLength(test.input)
if !ok {
got = 0
}
if got != test.want {
t.Errorf("quotedLength(%s) = %d, want %d", test.input, got, test.want)
}
}
// errors
for _, input := range []string{
``, // not a quotation
`a`, // not a quotation
`'a'`, // not a quotation
`"a`, // not terminated
`"\0"`, // short octal escape
`"\x1"`, // short hex escape
`"\u000"`, // short \u escape
`"\U0000000"`, // short \U escape
`"\k"`, // invalid escape
"\"ab\nc\"", // newline
} {
if n, ok := quotedLength(input); ok {
t.Errorf("quotedLength(%s) = %d, want !ok", input, n)
}
}
}

View File

@ -1,150 +0,0 @@
// The eg command performs example-based refactoring.
// For documentation, run the command, or see Help in
// golang.org/x/tools/refactor/eg.
package main // import "golang.org/x/tools/cmd/eg"
import (
"flag"
"fmt"
"go/build"
"go/format"
"go/parser"
"go/token"
"os"
"os/exec"
"strings"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/refactor/eg"
)
var (
beforeeditFlag = flag.String("beforeedit", "", "A command to exec before each file is edited (e.g. chmod, checkout). Whitespace delimits argument words. The string '{}' is replaced by the file name.")
helpFlag = flag.Bool("help", false, "show detailed help message")
templateFlag = flag.String("t", "", "template.go file specifying the refactoring")
transitiveFlag = flag.Bool("transitive", false, "apply refactoring to all dependencies too")
writeFlag = flag.Bool("w", false, "rewrite input files in place (by default, the results are printed to standard output)")
verboseFlag = flag.Bool("v", false, "show verbose matcher diagnostics")
)
func init() {
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
}
const usage = `eg: an example-based refactoring tool.
Usage: eg -t template.go [-w] [-transitive] <args>...
-help show detailed help message
-t template.go specifies the template file (use -help to see explanation)
-w causes files to be re-written in place.
-transitive causes all dependencies to be refactored too.
-v show verbose matcher diagnostics
-beforeedit cmd a command to exec before each file is modified.
"{}" represents the name of the file.
` + loader.FromArgsUsage
func main() {
if err := doMain(); err != nil {
fmt.Fprintf(os.Stderr, "eg: %s\n", err)
os.Exit(1)
}
}
func doMain() error {
flag.Parse()
args := flag.Args()
if *helpFlag {
fmt.Fprint(os.Stderr, eg.Help)
os.Exit(2)
}
if len(args) == 0 {
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}
if *templateFlag == "" {
return fmt.Errorf("no -t template.go file specified")
}
conf := loader.Config{
Fset: token.NewFileSet(),
ParserMode: parser.ParseComments,
}
// The first Created package is the template.
conf.CreateFromFilenames("template", *templateFlag)
if _, err := conf.FromArgs(args, true); err != nil {
return err
}
// Load, parse and type-check the whole program.
iprog, err := conf.Load()
if err != nil {
return err
}
// Analyze the template.
template := iprog.Created[0]
xform, err := eg.NewTransformer(iprog.Fset, template.Pkg, template.Files[0], &template.Info, *verboseFlag)
if err != nil {
return err
}
// Apply it to the input packages.
var pkgs []*loader.PackageInfo
if *transitiveFlag {
for _, info := range iprog.AllPackages {
pkgs = append(pkgs, info)
}
} else {
pkgs = iprog.InitialPackages()
}
var hadErrors bool
for _, pkg := range pkgs {
if pkg == template {
continue
}
for _, file := range pkg.Files {
n := xform.Transform(&pkg.Info, pkg.Pkg, file)
if n == 0 {
continue
}
filename := iprog.Fset.File(file.Pos()).Name()
fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n)
if *writeFlag {
// Run the before-edit command (e.g. "chmod +w", "checkout") if any.
if *beforeeditFlag != "" {
args := strings.Fields(*beforeeditFlag)
// Replace "{}" with the filename, like find(1).
for i := range args {
if i > 0 {
args[i] = strings.Replace(args[i], "{}", filename, -1)
}
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: edit hook %q failed (%s)\n",
args, err)
}
}
if err := eg.WriteAST(iprog.Fset, filename, file); err != nil {
fmt.Fprintf(os.Stderr, "eg: %s\n", err)
hadErrors = true
}
} else {
format.Node(os.Stdout, iprog.Fset, file)
}
}
}
if hadErrors {
os.Exit(1)
}
return nil
}

View File

@ -1,514 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The fiximports command fixes import declarations to use the canonical
// import path for packages that have an "import comment" as defined by
// https://golang.org/s/go14customimport.
//
//
// Background
//
// The Go 1 custom import path mechanism lets the maintainer of a
// package give it a stable name by which clients may import and "go
// get" it, independent of the underlying version control system (such
// as Git) or server (such as github.com) that hosts it. Requests for
// the custom name are redirected to the underlying name. This allows
// packages to be migrated from one underlying server or system to
// another without breaking existing clients.
//
// Because this redirect mechanism creates aliases for existing
// packages, it's possible for a single program to import the same
// package by its canonical name and by an alias. The resulting
// executable will contain two copies of the package, which is wasteful
// at best and incorrect at worst.
//
// To avoid this, "go build" reports an error if it encounters a special
// comment like the one below, and if the import path in the comment
// does not match the path of the enclosing package relative to
// GOPATH/src:
//
// $ grep ^package $GOPATH/src/github.com/bob/vanity/foo/foo.go
// package foo // import "vanity.com/foo"
//
// The error from "go build" indicates that the package canonically
// known as "vanity.com/foo" is locally installed under the
// non-canonical name "github.com/bob/vanity/foo".
//
//
// Usage
//
// When a package that you depend on introduces a custom import comment,
// and your workspace imports it by the non-canonical name, your build
// will stop working as soon as you update your copy of that package
// using "go get -u".
//
// The purpose of the fiximports tool is to fix up all imports of the
// non-canonical path within a Go workspace, replacing them with imports
// of the canonical path. Following a run of fiximports, the workspace
// will no longer depend on the non-canonical copy of the package, so it
// should be safe to delete. It may be necessary to run "go get -u"
// again to ensure that the package is locally installed under its
// canonical path, if it was not already.
//
// The fiximports tool operates locally; it does not make HTTP requests
// and does not discover new custom import comments. It only operates
// on non-canonical packages present in your workspace.
//
// The -baddomains flag is a list of domain names that should always be
// considered non-canonical. You can use this if you wish to make sure
// that you no longer have any dependencies on packages from that
// domain, even those that do not yet provide a canical import path
// comment. For example, the default value of -baddomains includes the
// moribund code hosting site code.google.com, so fiximports will report
// an error for each import of a package from this domain remaining
// after canonicalization.
//
// To see the changes fiximports would make without applying them, use
// the -n flag.
//
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/build"
"go/format"
"go/parser"
"go/token"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
)
// flags
var (
dryrun = flag.Bool("n", false, "dry run: show changes, but don't apply them")
badDomains = flag.String("baddomains", "code.google.com",
"a comma-separated list of domains from which packages should not be imported")
replaceFlag = flag.String("replace", "",
"a comma-separated list of noncanonical=canonical pairs of package paths. If both items in a pair end with '...', they are treated as path prefixes.")
)
// seams for testing
var (
stderr io.Writer = os.Stderr
writeFile = ioutil.WriteFile
)
const usage = `fiximports: rewrite import paths to use canonical package names.
Usage: fiximports [-n] package...
The package... arguments specify a list of packages
in the style of the go tool; see "go help packages".
Hint: use "all" or "..." to match the entire workspace.
For details, see http://godoc.org/golang.org/x/tools/cmd/fiximports.
Flags:
-n: dry run: show changes, but don't apply them
-baddomains a comma-separated list of domains from which packages
should not be imported
`
func main() {
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Fprintf(stderr, usage)
os.Exit(1)
}
if !fiximports(flag.Args()...) {
os.Exit(1)
}
}
type canonicalName struct{ path, name string }
// fiximports fixes imports in the specified packages.
// Invariant: a false result implies an error was already printed.
func fiximports(packages ...string) bool {
// importedBy is the transpose of the package import graph.
importedBy := make(map[string]map[*build.Package]bool)
// addEdge adds an edge to the import graph.
addEdge := func(from *build.Package, to string) {
if to == "C" || to == "unsafe" {
return // fake
}
pkgs := importedBy[to]
if pkgs == nil {
pkgs = make(map[*build.Package]bool)
importedBy[to] = pkgs
}
pkgs[from] = true
}
// List metadata for all packages in the workspace.
pkgs, err := list("...")
if err != nil {
fmt.Fprintf(stderr, "importfix: %v\n", err)
return false
}
// packageName maps each package's path to its name.
packageName := make(map[string]string)
for _, p := range pkgs {
packageName[p.ImportPath] = p.Package.Name
}
// canonical maps each non-canonical package path to
// its canonical path and name.
// A present nil value indicates that the canonical package
// is unknown: hosted on a bad domain with no redirect.
canonical := make(map[string]canonicalName)
domains := strings.Split(*badDomains, ",")
type replaceItem struct {
old, new string
matchPrefix bool
}
var replace []replaceItem
for _, pair := range strings.Split(*replaceFlag, ",") {
if pair == "" {
continue
}
words := strings.Split(pair, "=")
if len(words) != 2 {
fmt.Fprintf(stderr, "importfix: -replace: %q is not of the form \"canonical=noncanonical\".\n", pair)
return false
}
replace = append(replace, replaceItem{
old: strings.TrimSuffix(words[0], "..."),
new: strings.TrimSuffix(words[1], "..."),
matchPrefix: strings.HasSuffix(words[0], "...") &&
strings.HasSuffix(words[1], "..."),
})
}
// Find non-canonical packages and populate importedBy graph.
for _, p := range pkgs {
if p.Error != nil {
msg := p.Error.Err
if strings.Contains(msg, "code in directory") &&
strings.Contains(msg, "expects import") {
// don't show the very errors we're trying to fix
} else {
fmt.Fprintln(stderr, msg)
}
}
for _, imp := range p.Imports {
addEdge(&p.Package, imp)
}
for _, imp := range p.TestImports {
addEdge(&p.Package, imp)
}
for _, imp := range p.XTestImports {
addEdge(&p.Package, imp)
}
// Does package have an explicit import comment?
if p.ImportComment != "" {
if p.ImportComment != p.ImportPath {
canonical[p.ImportPath] = canonicalName{
path: p.Package.ImportComment,
name: p.Package.Name,
}
}
} else {
// Is package matched by a -replace item?
var newPath string
for _, item := range replace {
if item.matchPrefix {
if strings.HasPrefix(p.ImportPath, item.old) {
newPath = item.new + p.ImportPath[len(item.old):]
break
}
} else if p.ImportPath == item.old {
newPath = item.new
break
}
}
if newPath != "" {
newName := packageName[newPath]
if newName == "" {
newName = filepath.Base(newPath) // a guess
}
canonical[p.ImportPath] = canonicalName{
path: newPath,
name: newName,
}
continue
}
// Is package matched by a -baddomains item?
for _, domain := range domains {
slash := strings.Index(p.ImportPath, "/")
if slash < 0 {
continue // no slash: standard package
}
if p.ImportPath[:slash] == domain {
// Package comes from bad domain and has no import comment.
// Report an error each time this package is imported.
canonical[p.ImportPath] = canonicalName{}
// TODO(adonovan): should we make an HTTP request to
// see if there's an HTTP redirect, a "go-import" meta tag,
// or an import comment in the the latest revision?
// It would duplicate a lot of logic from "go get".
}
break
}
}
}
// Find all clients (direct importers) of canonical packages.
// These are the packages that need fixing up.
clients := make(map[*build.Package]bool)
for path := range canonical {
for client := range importedBy[path] {
clients[client] = true
}
}
// Restrict rewrites to the set of packages specified by the user.
if len(packages) == 1 && (packages[0] == "all" || packages[0] == "...") {
// no restriction
} else {
pkgs, err := list(packages...)
if err != nil {
fmt.Fprintf(stderr, "importfix: %v\n", err)
return false
}
seen := make(map[string]bool)
for _, p := range pkgs {
seen[p.ImportPath] = true
}
for client := range clients {
if !seen[client.ImportPath] {
delete(clients, client)
}
}
}
// Rewrite selected client packages.
ok := true
for client := range clients {
if !rewritePackage(client, canonical) {
ok = false
// There were errors.
// Show direct and indirect imports of client.
seen := make(map[string]bool)
var direct, indirect []string
for p := range importedBy[client.ImportPath] {
direct = append(direct, p.ImportPath)
seen[p.ImportPath] = true
}
var visit func(path string)
visit = func(path string) {
for q := range importedBy[path] {
qpath := q.ImportPath
if !seen[qpath] {
seen[qpath] = true
indirect = append(indirect, qpath)
visit(qpath)
}
}
}
if direct != nil {
fmt.Fprintf(stderr, "\timported directly by:\n")
sort.Strings(direct)
for _, path := range direct {
fmt.Fprintf(stderr, "\t\t%s\n", path)
visit(path)
}
if indirect != nil {
fmt.Fprintf(stderr, "\timported indirectly by:\n")
sort.Strings(indirect)
for _, path := range indirect {
fmt.Fprintf(stderr, "\t\t%s\n", path)
}
}
}
}
}
return ok
}
// Invariant: false result => error already printed.
func rewritePackage(client *build.Package, canonical map[string]canonicalName) bool {
ok := true
used := make(map[string]bool)
var filenames []string
filenames = append(filenames, client.GoFiles...)
filenames = append(filenames, client.TestGoFiles...)
filenames = append(filenames, client.XTestGoFiles...)
var first bool
for _, filename := range filenames {
if !first {
first = true
fmt.Fprintf(stderr, "%s\n", client.ImportPath)
}
err := rewriteFile(filepath.Join(client.Dir, filename), canonical, used)
if err != nil {
fmt.Fprintf(stderr, "\tERROR: %v\n", err)
ok = false
}
}
// Show which imports were renamed in this package.
var keys []string
for key := range used {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
if p := canonical[key]; p.path != "" {
fmt.Fprintf(stderr, "\tfixed: %s -> %s\n", key, p.path)
} else {
fmt.Fprintf(stderr, "\tERROR: %s has no import comment\n", key)
ok = false
}
}
return ok
}
// rewrite reads, modifies, and writes filename, replacing all imports
// of packages P in canonical by canonical[P].
// It records in used which canonical packages were imported.
// used[P]=="" indicates that P was imported but its canonical path is unknown.
func rewriteFile(filename string, canonical map[string]canonicalName, used map[string]bool) error {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
return err
}
var changed bool
for _, imp := range f.Imports {
impPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
log.Printf("%s: bad import spec %q: %v",
fset.Position(imp.Pos()), imp.Path.Value, err)
continue
}
canon, ok := canonical[impPath]
if !ok {
continue // import path is canonical
}
used[impPath] = true
if canon.path == "" {
// The canonical path is unknown (a -baddomain).
// Show the offending import.
// TODO(adonovan): should we show the actual source text?
fmt.Fprintf(stderr, "\t%s:%d: import %q\n",
shortPath(filename),
fset.Position(imp.Pos()).Line, impPath)
continue
}
changed = true
imp.Path.Value = strconv.Quote(canon.path)
// Add a renaming import if necessary.
//
// This is a guess at best. We can't see whether a 'go
// get' of the canonical import path would have the same
// name or not. Assume it's the last segment.
newBase := path.Base(canon.path)
if imp.Name == nil && newBase != canon.name {
imp.Name = &ast.Ident{Name: canon.name}
}
}
if changed && !*dryrun {
var buf bytes.Buffer
if err := format.Node(&buf, fset, f); err != nil {
return fmt.Errorf("%s: couldn't format file: %v", filename, err)
}
return writeFile(filename, buf.Bytes(), 0644)
}
return nil
}
// listPackage is a copy of cmd/go/list.Package.
// It has more fields than build.Package and we need some of them.
type listPackage struct {
build.Package
Error *packageError // error loading package
}
// A packageError describes an error loading information about a package.
type packageError struct {
ImportStack []string // shortest path from package named on command line to this one
Pos string // position of error
Err string // the error itself
}
// list runs 'go list' with the specified arguments and returns the
// metadata for matching packages.
func list(args ...string) ([]*listPackage, error) {
cmd := exec.Command("go", append([]string{"list", "-e", "-json"}, args...)...)
cmd.Stdout = new(bytes.Buffer)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return nil, err
}
dec := json.NewDecoder(cmd.Stdout.(io.Reader))
var pkgs []*listPackage
for {
var p listPackage
if err := dec.Decode(&p); err == io.EOF {
break
} else if err != nil {
return nil, err
}
pkgs = append(pkgs, &p)
}
return pkgs, nil
}
// cwd contains the current working directory of the tool.
//
// It is initialized directly so that its value will be set for any other
// package variables or init functions that depend on it, such as the gopath
// variable in main_test.go.
var cwd string = func() string {
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("os.Getwd: %v", err)
}
return cwd
}()
// shortPath returns an absolute or relative name for path, whatever is shorter.
// Plundered from $GOROOT/src/cmd/go/build.go.
func shortPath(path string) string {
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
return rel
}
return path
}

View File

@ -1,253 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// No testdata on Android.
// +build !android
package main
import (
"bytes"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
// TODO(adonovan):
// - test introduction of renaming imports.
// - test induced failures of rewriteFile.
// Guide to the test packages:
//
// new.com/one -- canonical name for old.com/one
// old.com/one -- non-canonical; has import comment "new.com/one"
// old.com/bad -- has a parse error
// fruit.io/orange \
// fruit.io/banana } orange -> pear -> banana -> titanic.biz/bar
// fruit.io/pear /
// titanic.biz/bar -- domain is sinking; package has jumped ship to new.com/bar
// titanic.biz/foo -- domain is sinking but package has no import comment yet
var gopath = filepath.Join(cwd, "testdata")
func init() {
if err := os.Setenv("GOPATH", gopath); err != nil {
log.Fatal(err)
}
// This test currently requires GOPATH mode.
// Explicitly disabling module mode should suffix, but
// we'll also turn off GOPROXY just for good measure.
if err := os.Setenv("GO111MODULE", "off"); err != nil {
log.Fatal(err)
}
if err := os.Setenv("GOPROXY", "off"); err != nil {
log.Fatal(err)
}
}
func TestFixImports(t *testing.T) {
defer func() {
stderr = os.Stderr
*badDomains = "code.google.com"
*replaceFlag = ""
}()
for i, test := range []struct {
packages []string // packages to rewrite, "go list" syntax
badDomains string // -baddomains flag
replaceFlag string // -replace flag
wantOK bool
wantStderr string
wantRewrite map[string]string
}{
// #0. No errors.
{
packages: []string{"all"},
badDomains: "code.google.com",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/one"
_ "titanic.biz/foo"
)`,
},
},
// #1. No packages needed rewriting.
{
packages: []string{"titanic.biz/...", "old.com/...", "new.com/..."},
badDomains: "code.google.com",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
`,
},
// #2. Some packages without import comments matched bad domains.
{
packages: []string{"all"},
badDomains: "titanic.biz",
wantOK: false,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
testdata/src/fruit.io/banana/banana.go:6: import "titanic.biz/foo"
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
ERROR: titanic.biz/foo has no import comment
imported directly by:
fruit.io/pear
imported indirectly by:
fruit.io/orange
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/one"
_ "titanic.biz/foo"
)`,
},
},
// #3. The -replace flag lets user supply missing import comments.
{
packages: []string{"all"},
replaceFlag: "titanic.biz/foo=new.com/foo",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
fixed: titanic.biz/foo -> new.com/foo
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/foo"
_ "new.com/one"
)`,
},
},
// #4. The -replace flag supports wildcards.
// An explicit import comment takes precedence.
{
packages: []string{"all"},
replaceFlag: "titanic.biz/...=new.com/...",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
fixed: titanic.biz/foo -> new.com/foo
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/foo"
_ "new.com/one"
)`,
},
},
// #5. The -replace flag trumps -baddomains.
{
packages: []string{"all"},
badDomains: "titanic.biz",
replaceFlag: "titanic.biz/foo=new.com/foo",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
fixed: titanic.biz/foo -> new.com/foo
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/foo"
_ "new.com/one"
)`,
},
},
} {
*badDomains = test.badDomains
*replaceFlag = test.replaceFlag
stderr = new(bytes.Buffer)
gotRewrite := make(map[string]string)
writeFile = func(filename string, content []byte, mode os.FileMode) error {
filename = strings.Replace(filename, gopath, "$GOPATH", 1)
filename = filepath.ToSlash(filename)
gotRewrite[filename] = string(bytes.TrimSpace(content))
return nil
}
if runtime.GOOS == "windows" {
test.wantStderr = strings.Replace(test.wantStderr, `testdata/src/old.com/bad/bad.go`, `testdata\src\old.com\bad\bad.go`, -1)
test.wantStderr = strings.Replace(test.wantStderr, `testdata/src/fruit.io/banana/banana.go`, `testdata\src\fruit.io\banana\banana.go`, -1)
}
// Check status code.
if fiximports(test.packages...) != test.wantOK {
t.Errorf("#%d. fiximports() = %t", i, !test.wantOK)
}
// Compare stderr output.
if got := stderr.(*bytes.Buffer).String(); got != test.wantStderr {
if strings.Contains(got, "vendor/golang_org/x/text/unicode/norm") {
t.Skip("skipping known-broken test; see golang.org/issue/17417")
}
t.Errorf("#%d. stderr: got <<%s>>, want <<%s>>",
i, stderr, test.wantStderr)
}
// Compare rewrites.
for k, v := range gotRewrite {
if test.wantRewrite[k] != v {
t.Errorf("#%d. rewrite[%s] = <<%s>>, want <<%s>>",
i, k, v, test.wantRewrite[k])
}
delete(test.wantRewrite, k)
}
for k, v := range test.wantRewrite {
t.Errorf("#%d. rewrite[%s] missing, want <<%s>>", i, k, v)
}
}
}
// TestDryRun tests that the -n flag suppresses calls to writeFile.
func TestDryRun(t *testing.T) {
*dryrun = true
defer func() { *dryrun = false }() // restore
stderr = new(bytes.Buffer)
writeFile = func(filename string, content []byte, mode os.FileMode) error {
t.Fatalf("writeFile(%s) called in dryrun mode", filename)
return nil
}
if !fiximports("all") {
t.Fatalf("fiximports failed: %s", stderr)
}
}

View File

@ -1,7 +0,0 @@
package banana
import (
_ "old.com/one"
_ "titanic.biz/bar"
_ "titanic.biz/foo"
)

View File

@ -1,3 +0,0 @@
package orange
import _ "fruit.io/pear"

View File

@ -1,3 +0,0 @@
package pear
import _ "fruit.io/banana"

View File

@ -1 +0,0 @@
package one // import "new.com/one"

View File

@ -1,2 +0,0 @@
// This ill-formed Go source file is here to ensure the tool is robust
// against bad packages in the workspace.

View File

@ -1 +0,0 @@
package one // import "new.com/one"

View File

@ -1,2 +0,0 @@
// This package is moving to new.com too.
package bar // import "new.com/bar"

View File

@ -1,2 +0,0 @@
// This package hasn't jumped ship yet.
package foo

View File

@ -1,5 +0,0 @@
.git
.dockerignore
LICENSE
README.md
.gitignore

View File

@ -1,3 +0,0 @@
build
testgetgo
getgo

View File

@ -1,20 +0,0 @@
FROM golang:latest
ENV SHELL /bin/bash
ENV HOME /root
WORKDIR $HOME
COPY . /go/src/golang.org/x/tools/cmd/getgo
RUN ( \
cd /go/src/golang.org/x/tools/cmd/getgo \
&& go build \
&& mv getgo /usr/local/bin/getgo \
)
# undo the adding of GOPATH to env for testing
ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV GOPATH ""
# delete /go and /usr/local/go for testing
RUN rm -rf /go /usr/local/go

View File

@ -1,27 +0,0 @@
Copyright (c) 2017 The Go Authors. All rights reserved.
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 Google Inc. 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.

View File

@ -1,71 +0,0 @@
# getgo
A proof-of-concept command-line installer for Go.
This installer is designed to both install Go as well as do the initial configuration
of setting up the right environment variables and paths.
It will install the Go distribution (tools & stdlib) to "/.go" inside your home directory by default.
It will setup "$HOME/go" as your GOPATH.
This is where third party libraries and apps will be installed as well as where you will write your Go code.
If Go is already installed via this installer it will upgrade it to the latest version of Go.
Currently the installer supports Windows, \*nix and macOS on x86 & x64.
It supports Bash and Zsh on all of these platforms as well as powershell & cmd.exe on Windows.
## Usage
Windows Powershell/cmd.exe:
`(New-Object System.Net.WebClient).DownloadFile('https://get.golang.org/installer.exe', 'installer.exe'); Start-Process -Wait -NonewWindow installer.exe; Remove-Item installer.exe`
Shell (Linux/macOS/Windows):
`curl -LO https://get.golang.org/$(uname)/go_installer && chmod +x go_installer && ./go_installer && rm go_installer`
## To Do
* Check if Go is already installed (via a different method) and update it in place or at least notify the user
* Lots of testing. It's only had limited testing so far.
* Add support for additional shells.
## Development instructions
### Testing
There are integration tests in [`main_test.go`](main_test.go). Please add more
tests there.
#### On unix/linux with the Dockerfile
The Dockerfile automatically builds the binary, moves it to
`/usr/local/bin/getgo` and then unsets `$GOPATH` and removes all `$GOPATH` from
`$PATH`.
```bash
$ docker build --rm --force-rm -t getgo .
...
$ docker run --rm -it getgo bash
root@78425260fad0:~# getgo -v
Welcome to the Go installer!
Downloading Go version go1.8.3 to /usr/local/go
This may take a bit of time...
Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin" to /root/.bashrc
Downloaded!
Setting up GOPATH
Adding "export GOPATH=/root/go" to /root/.bashrc
Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin" to /root/.bashrc
GOPATH has been setup!
root@78425260fad0:~# which go
/usr/local/go/bin/go
root@78425260fad0:~# echo $GOPATH
/root/go
root@78425260fad0:~# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin
```
## Release instructions
To upload a new release of getgo, run `./make.bash && ./upload.bash`.

View File

@ -1,184 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
package main
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
)
const (
currentVersionURL = "https://golang.org/VERSION?m=text"
downloadURLPrefix = "https://dl.google.com/go"
)
// downloadGoVersion downloads and upacks the specific go version to dest/go.
func downloadGoVersion(version, ops, arch, dest string) error {
suffix := "tar.gz"
if ops == "windows" {
suffix = "zip"
}
uri := fmt.Sprintf("%s/%s.%s-%s.%s", downloadURLPrefix, version, ops, arch, suffix)
verbosef("Downloading %s", uri)
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return err
}
req.Header.Add("User-Agent", fmt.Sprintf("golang.org-getgo/%s", version))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("Downloading Go from %s failed: %v", uri, err)
}
if resp.StatusCode > 299 {
return fmt.Errorf("Downloading Go from %s failed with HTTP status %s", uri, resp.Status)
}
defer resp.Body.Close()
tmpf, err := ioutil.TempFile("", "go")
if err != nil {
return err
}
defer os.Remove(tmpf.Name())
h := sha256.New()
w := io.MultiWriter(tmpf, h)
if _, err := io.Copy(w, resp.Body); err != nil {
return err
}
verbosef("Downloading SHA %s.sha256", uri)
sresp, err := http.Get(uri + ".sha256")
if err != nil {
return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed: %v", uri, err)
}
defer sresp.Body.Close()
if sresp.StatusCode > 299 {
return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed with HTTP status %s", uri, sresp.Status)
}
shasum, err := ioutil.ReadAll(sresp.Body)
if err != nil {
return err
}
// Check the shasum.
sum := fmt.Sprintf("%x", h.Sum(nil))
if sum != string(shasum) {
return fmt.Errorf("Shasum mismatch %s vs. %s", sum, string(shasum))
}
unpackFunc := unpackTar
if ops == "windows" {
unpackFunc = unpackZip
}
if err := unpackFunc(tmpf.Name(), dest); err != nil {
return fmt.Errorf("Unpacking Go to %s failed: %v", dest, err)
}
return nil
}
func unpack(dest, name string, fi os.FileInfo, r io.Reader) error {
if strings.HasPrefix(name, "go/") {
name = name[len("go/"):]
}
path := filepath.Join(dest, name)
if fi.IsDir() {
return os.MkdirAll(path, fi.Mode())
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
func unpackTar(src, dest string) error {
r, err := os.Open(src)
if err != nil {
return err
}
defer r.Close()
archive, err := gzip.NewReader(r)
if err != nil {
return err
}
defer archive.Close()
tarReader := tar.NewReader(archive)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
if err := unpack(dest, header.Name, header.FileInfo(), tarReader); err != nil {
return err
}
}
return nil
}
func unpackZip(src, dest string) error {
zr, err := zip.OpenReader(src)
if err != nil {
return err
}
for _, f := range zr.File {
fr, err := f.Open()
if err != nil {
return err
}
if err := unpack(dest, f.Name, f.FileInfo(), fr); err != nil {
return err
}
fr.Close()
}
return nil
}
func getLatestGoVersion() (string, error) {
resp, err := http.Get(currentVersionURL)
if err != nil {
return "", fmt.Errorf("Getting current Go version failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode > 299 {
b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 1024))
return "", fmt.Errorf("Could not get current Go version: HTTP %d: %q", resp.StatusCode, b)
}
version, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return strings.TrimSpace(string(version)), nil
}

View File

@ -1,36 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestDownloadGoVersion(t *testing.T) {
if testing.Short() {
t.Skipf("Skipping download in short mode")
}
tmpd, err := ioutil.TempDir("", "go")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpd)
if err := downloadGoVersion("go1.8.1", "linux", "amd64", filepath.Join(tmpd, "go")); err != nil {
t.Fatal(err)
}
// Ensure the VERSION file exists.
vf := filepath.Join(tmpd, "go", "VERSION")
if _, err := os.Stat(vf); os.IsNotExist(err) {
t.Fatalf("file %s does not exist and should", vf)
}
}

View File

@ -1,117 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
// The getgo command installs Go to the user's system.
package main
import (
"bufio"
"context"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"strings"
)
var (
interactive = flag.Bool("i", false, "Interactive mode, prompt for inputs.")
verbose = flag.Bool("v", false, "Verbose.")
setupOnly = flag.Bool("skip-dl", false, "Don't download - only set up environment variables")
goVersion = flag.String("version", "", `Version of Go to install (e.g. "1.8.3"). If empty, uses the latest version.`)
version = "devel"
)
var exitCleanly error = errors.New("exit cleanly sentinel value")
func main() {
flag.Parse()
if *goVersion != "" && !strings.HasPrefix(*goVersion, "go") {
*goVersion = "go" + *goVersion
}
ctx := context.Background()
verbosef("version " + version)
runStep := func(s step) {
err := s(ctx)
if err == exitCleanly {
os.Exit(0)
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
}
if !*setupOnly {
runStep(welcome)
runStep(checkOthers)
runStep(chooseVersion)
runStep(downloadGo)
}
runStep(setupGOPATH)
}
func verbosef(format string, v ...interface{}) {
if !*verbose {
return
}
fmt.Printf(format+"\n", v...)
}
func prompt(ctx context.Context, query, defaultAnswer string) (string, error) {
if !*interactive {
return defaultAnswer, nil
}
fmt.Printf("%s [%s]: ", query, defaultAnswer)
type result struct {
answer string
err error
}
ch := make(chan result, 1)
go func() {
s := bufio.NewScanner(os.Stdin)
if !s.Scan() {
ch <- result{"", s.Err()}
return
}
answer := s.Text()
if answer == "" {
answer = defaultAnswer
}
ch <- result{answer, nil}
}()
select {
case r := <-ch:
return r.answer, r.err
case <-ctx.Done():
return "", ctx.Err()
}
}
func runCommand(ctx context.Context, prog string, args ...string) ([]byte, error) {
verbosef("Running command: %s %v", prog, args)
cmd := exec.CommandContext(ctx, prog, args...)
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("running cmd '%s %s' failed: %s err: %v", prog, strings.Join(args, " "), string(out), err)
}
if out != nil && err == nil && len(out) != 0 {
verbosef("%s", out)
}
return out, nil
}

View File

@ -1,173 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"runtime"
"testing"
)
const (
testbin = "testgetgo"
)
var (
exeSuffix string // ".exe" on Windows
)
func init() {
if runtime.GOOS == "windows" {
exeSuffix = ".exe"
}
}
// TestMain creates a getgo command for testing purposes and
// deletes it after the tests have been run.
func TestMain(m *testing.M) {
if os.Getenv("GOGET_INTEGRATION") == "" {
fmt.Fprintln(os.Stderr, "main_test: Skipping integration tests with GOGET_INTEGRATION unset")
return
}
args := []string{"build", "-tags", testbin, "-o", testbin + exeSuffix}
out, err := exec.Command("go", args...).CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "building %s failed: %v\n%s", testbin, err, out)
os.Exit(2)
}
// Don't let these environment variables confuse the test.
os.Unsetenv("GOBIN")
os.Unsetenv("GOPATH")
os.Unsetenv("GIT_ALLOW_PROTOCOL")
os.Unsetenv("PATH")
r := m.Run()
os.Remove(testbin + exeSuffix)
os.Exit(r)
}
func createTmpHome(t *testing.T) string {
tmpd, err := ioutil.TempDir("", "testgetgo")
if err != nil {
t.Fatalf("creating test tempdir failed: %v", err)
}
os.Setenv("HOME", tmpd)
return tmpd
}
// doRun runs the test getgo command, recording stdout and stderr and
// returning exit status.
func doRun(t *testing.T, args ...string) error {
var stdout, stderr bytes.Buffer
t.Logf("running %s %v", testbin, args)
cmd := exec.Command("./"+testbin+exeSuffix, args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = os.Environ()
status := cmd.Run()
if stdout.Len() > 0 {
t.Log("standard output:")
t.Log(stdout.String())
}
if stderr.Len() > 0 {
t.Log("standard error:")
t.Log(stderr.String())
}
return status
}
func TestCommandVerbose(t *testing.T) {
tmpd := createTmpHome(t)
defer os.RemoveAll(tmpd)
err := doRun(t, "-v")
if err != nil {
t.Fatal(err)
}
// make sure things are in path
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
home, err := getHomeDir()
if err != nil {
t.Fatal(err)
}
expected := fmt.Sprintf(`
export PATH=$PATH:%s/.go/bin
export GOPATH=%s/go
export PATH=$PATH:%s/go/bin
`, home, home, home)
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
}
func TestCommandPathExists(t *testing.T) {
tmpd := createTmpHome(t)
defer os.RemoveAll(tmpd)
// run once
err := doRun(t, "-skip-dl")
if err != nil {
t.Fatal(err)
}
// make sure things are in path
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
home, err := getHomeDir()
if err != nil {
t.Fatal(err)
}
expected := fmt.Sprintf(`
export GOPATH=%s/go
export PATH=$PATH:%s/go/bin
`, home, home)
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
// run twice
if err := doRun(t, "-skip-dl"); err != nil {
t.Fatal(err)
}
b, err = ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
}

View File

@ -1,13 +0,0 @@
#!/bin/bash
# Copyright 2017 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 -o -x
LDFLAGS="-X main.version=$(git describe --always --dirty='*')"
GOOS=windows GOARCH=386 go build -o build/installer.exe -ldflags="$LDFLAGS"
GOOS=linux GOARCH=386 go build -o build/installer_linux -ldflags="$LDFLAGS"
GOOS=darwin GOARCH=386 go build -o build/installer_darwin -ldflags="$LDFLAGS"

View File

@ -1,155 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
package main
import (
"bufio"
"context"
"fmt"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
)
const (
bashConfig = ".bash_profile"
zshConfig = ".zshrc"
)
// appendToPATH adds the given path to the PATH environment variable and
// persists it for future sessions.
func appendToPATH(value string) error {
if isInPATH(value) {
return nil
}
return persistEnvVar("PATH", pathVar+envSeparator+value)
}
func isInPATH(dir string) bool {
p := os.Getenv("PATH")
paths := strings.Split(p, envSeparator)
for _, d := range paths {
if d == dir {
return true
}
}
return false
}
func getHomeDir() (string, error) {
home := os.Getenv(homeKey)
if home != "" {
return home, nil
}
u, err := user.Current()
if err != nil {
return "", err
}
return u.HomeDir, nil
}
func checkStringExistsFile(filename, value string) (bool, error) {
file, err := os.OpenFile(filename, os.O_RDONLY, 0600)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if line == value {
return true, nil
}
}
return false, scanner.Err()
}
func appendToFile(filename, value string) error {
verbosef("Adding %q to %s", value, filename)
ok, err := checkStringExistsFile(filename, value)
if err != nil {
return err
}
if ok {
// Nothing to do.
return nil
}
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(lineEnding + value + lineEnding)
return err
}
func isShell(name string) bool {
return strings.Contains(currentShell(), name)
}
// persistEnvVarWindows sets an environment variable in the Windows
// registry.
func persistEnvVarWindows(name, value string) error {
_, err := runCommand(context.Background(), "powershell", "-command",
fmt.Sprintf(`[Environment]::SetEnvironmentVariable("%s", "%s", "User")`, name, value))
return err
}
func persistEnvVar(name, value string) error {
if runtime.GOOS == "windows" {
if err := persistEnvVarWindows(name, value); err != nil {
return err
}
if isShell("cmd.exe") || isShell("powershell.exe") {
return os.Setenv(strings.ToUpper(name), value)
}
// User is in bash, zsh, etc.
// Also set the environment variable in their shell config.
}
rc, err := shellConfigFile()
if err != nil {
return err
}
line := fmt.Sprintf("export %s=%s", strings.ToUpper(name), value)
if err := appendToFile(rc, line); err != nil {
return err
}
return os.Setenv(strings.ToUpper(name), value)
}
func shellConfigFile() (string, error) {
home, err := getHomeDir()
if err != nil {
return "", err
}
switch {
case isShell("bash"):
return filepath.Join(home, bashConfig), nil
case isShell("zsh"):
return filepath.Join(home, zshConfig), nil
default:
return "", fmt.Errorf("%q is not a supported shell", currentShell())
}
}

View File

@ -1,58 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
package main
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestAppendPath(t *testing.T) {
tmpd, err := ioutil.TempDir("", "go")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpd)
if err := os.Setenv("HOME", tmpd); err != nil {
t.Fatal(err)
}
GOPATH := os.Getenv("GOPATH")
if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
t.Fatal(err)
}
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
expected := "export PATH=" + pathVar + envSeparator + filepath.Join(GOPATH, "bin")
if strings.TrimSpace(string(b)) != expected {
t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
}
// Check that appendToPATH is idempotent.
if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
t.Fatal(err)
}
b, err = ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
if strings.TrimSpace(string(b)) != expected {
t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
}
}

View File

@ -1,7 +0,0 @@
# getgo server
## Deployment
```
gcloud app deploy --promote --project golang-org
```

View File

@ -1,7 +0,0 @@
runtime: go
service: get
api_version: go1
handlers:
- url: /.*
script: _go_app

View File

@ -1,61 +0,0 @@
// Copyright 2017 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.
// Command server serves get.golang.org, redirecting users to the appropriate
// getgo installer based on the request path.
package server
import (
"fmt"
"net/http"
"strings"
"time"
)
const (
base = "https://dl.google.com/go/getgo/"
windowsInstaller = base + "installer.exe"
linuxInstaller = base + "installer_linux"
macInstaller = base + "installer_darwin"
)
// substring-based redirects.
var stringMatch = map[string]string{
// via uname, from bash
"MINGW": windowsInstaller, // Reported as MINGW64_NT-10.0 in git bash
"Linux": linuxInstaller,
"Darwin": macInstaller,
}
func init() {
http.HandleFunc("/", handler)
}
func handler(w http.ResponseWriter, r *http.Request) {
if containsIgnoreCase(r.URL.Path, "installer.exe") {
// cache bust
http.Redirect(w, r, windowsInstaller+cacheBust(), http.StatusFound)
return
}
for match, redirect := range stringMatch {
if containsIgnoreCase(r.URL.Path, match) {
http.Redirect(w, r, redirect, http.StatusFound)
return
}
}
http.NotFound(w, r)
}
func containsIgnoreCase(s, substr string) bool {
return strings.Contains(
strings.ToLower(s),
strings.ToLower(substr),
)
}
func cacheBust() string {
return fmt.Sprintf("?%d", time.Now().Nanosecond())
}

View File

@ -1,133 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)
type step func(context.Context) error
func welcome(ctx context.Context) error {
fmt.Println("Welcome to the Go installer!")
answer, err := prompt(ctx, "Would you like to install Go? Y/n", "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Exiting install.")
return exitCleanly
}
return nil
}
func checkOthers(ctx context.Context) error {
// TODO: if go is currently installed install new version over that
path, err := whichGo(ctx)
if err != nil {
fmt.Printf("Cannot check if Go is already installed:\n%v\n", err)
}
if path == "" {
return nil
}
if path != installPath {
fmt.Printf("Go is already installed at %v; remove it from your PATH.\n", path)
}
return nil
}
func chooseVersion(ctx context.Context) error {
if *goVersion != "" {
return nil
}
var err error
*goVersion, err = getLatestGoVersion()
if err != nil {
return err
}
answer, err := prompt(ctx, fmt.Sprintf("The latest Go version is %s, install that? Y/n", *goVersion), "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
// TODO: handle passing a version
fmt.Println("Aborting install.")
return exitCleanly
}
return nil
}
func downloadGo(ctx context.Context) error {
answer, err := prompt(ctx, fmt.Sprintf("Download Go version %s to %s? Y/n", *goVersion, installPath), "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Aborting install.")
return exitCleanly
}
fmt.Printf("Downloading Go version %s to %s\n", *goVersion, installPath)
fmt.Println("This may take a bit of time...")
if err := downloadGoVersion(*goVersion, runtime.GOOS, arch, installPath); err != nil {
return err
}
if err := appendToPATH(filepath.Join(installPath, "bin")); err != nil {
return err
}
fmt.Println("Downloaded!")
return nil
}
func setupGOPATH(ctx context.Context) error {
answer, err := prompt(ctx, "Would you like us to setup your GOPATH? Y/n", "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Exiting and not setting up GOPATH.")
return exitCleanly
}
fmt.Println("Setting up GOPATH")
home, err := getHomeDir()
if err != nil {
return err
}
gopath := os.Getenv("GOPATH")
if gopath == "" {
// set $GOPATH
gopath = filepath.Join(home, "go")
if err := persistEnvVar("GOPATH", gopath); err != nil {
return err
}
fmt.Println("GOPATH has been set up!")
} else {
verbosef("GOPATH is already set to %s", gopath)
}
if err := appendToPATH(filepath.Join(gopath, "bin")); err != nil {
return err
}
return persistEnvChangesForSession()
}

View File

@ -1,38 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
package main
import (
"bytes"
"context"
"os/exec"
"runtime"
"strings"
)
// arch contains either amd64 or 386.
var arch = func() string {
cmd := exec.Command("uname", "-m") // "x86_64"
if runtime.GOOS == "windows" {
cmd = exec.Command("powershell", "-command", "(Get-WmiObject -Class Win32_ComputerSystem).SystemType") // "x64-based PC"
}
out, err := cmd.Output()
if err != nil {
// a sensible default?
return "amd64"
}
if bytes.Contains(out, []byte("64")) {
return "amd64"
}
return "386"
}()
func findGo(ctx context.Context, cmd string) (string, error) {
out, err := exec.CommandContext(ctx, cmd, "go").CombinedOutput()
return strings.TrimSpace(string(out)), err
}

View File

@ -1,55 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package main
import (
"context"
"fmt"
"os"
"path/filepath"
)
const (
envSeparator = ":"
homeKey = "HOME"
lineEnding = "\n"
pathVar = "$PATH"
)
var installPath = func() string {
home, err := getHomeDir()
if err != nil {
return "/usr/local/go"
}
return filepath.Join(home, ".go")
}()
func whichGo(ctx context.Context) (string, error) {
return findGo(ctx, "which")
}
func isWindowsXP() bool {
return false
}
func currentShell() string {
return os.Getenv("SHELL")
}
func persistEnvChangesForSession() error {
shellConfig, err := shellConfigFile()
if err != nil {
return err
}
fmt.Println()
fmt.Printf("One more thing! Run `source %s` to persist the\n", shellConfig)
fmt.Println("new environment variables to your current session, or open a")
fmt.Println("new shell prompt.")
return nil
}

View File

@ -1,86 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package main
import (
"context"
"log"
"os"
"syscall"
"unsafe"
)
const (
envSeparator = ";"
homeKey = "USERPROFILE"
lineEnding = "/r/n"
pathVar = "$env:Path"
)
var installPath = `c:\go`
func isWindowsXP() bool {
v, err := syscall.GetVersion()
if err != nil {
log.Fatalf("GetVersion failed: %v", err)
}
major := byte(v)
return major < 6
}
func whichGo(ctx context.Context) (string, error) {
return findGo(ctx, "where")
}
// currentShell reports the current shell.
// It might be "powershell.exe", "cmd.exe" or any of the *nix shells.
//
// Returns empty string if the shell is unknown.
func currentShell() string {
shell := os.Getenv("SHELL")
if shell != "" {
return shell
}
pid := os.Getppid()
pe, err := getProcessEntry(pid)
if err != nil {
verbosef("getting shell from process entry failed: %v", err)
return ""
}
return syscall.UTF16ToString(pe.ExeFile[:])
}
func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
// From https://go.googlesource.com/go/+/go1.8.3/src/syscall/syscall_windows.go#941
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(snapshot)
var procEntry syscall.ProcessEntry32
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
return nil, err
}
for {
if procEntry.ProcessID == uint32(pid) {
return &procEntry, nil
}
if err := syscall.Process32Next(snapshot, &procEntry); err != nil {
return nil, err
}
}
}
func persistEnvChangesForSession() error {
return nil
}

View File

@ -1,19 +0,0 @@
#!/bin/bash
# Copyright 2017 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 ! command -v gsutil 2>&1 > /dev/null; then
echo "Install gsutil:"
echo
echo " https://cloud.google.com/storage/docs/gsutil_install#sdk-install"
fi
if [ ! -d build ]; then
echo "Run make.bash first"
fi
set -e -o -x
gsutil -m cp -a public-read build/* gs://golang/getgo

View File

@ -1,289 +0,0 @@
// Copyright 2017 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 go-contrib-init command helps new Go contributors get their development
// environment set up for the Go contribution process.
//
// It aims to be a complement or alternative to https://golang.org/doc/contribute.html.
package main
import (
"bytes"
"flag"
"fmt"
"go/build"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
)
var (
repo = flag.String("repo", detectrepo(), "Which go repo you want to contribute to. Use \"go\" for the core, or e.g. \"net\" for golang.org/x/net/*")
dry = flag.Bool("dry-run", false, "Fail with problems instead of trying to fix things.")
)
func main() {
log.SetFlags(0)
flag.Parse()
checkCLA()
checkGoroot()
checkWorkingDir()
checkGitOrigin()
checkGitCodeReview()
fmt.Print("All good. Happy hacking!\n" +
"Remember to squash your revised commits and preserve the magic Change-Id lines.\n" +
"Next steps: https://golang.org/doc/contribute.html#commit_changes\n")
}
func detectrepo() string {
wd, err := os.Getwd()
if err != nil {
return "go"
}
for _, path := range filepath.SplitList(build.Default.GOPATH) {
rightdir := filepath.Join(path, "src", "golang.org", "x") + string(os.PathSeparator)
if strings.HasPrefix(wd, rightdir) {
tail := wd[len(rightdir):]
end := strings.Index(tail, string(os.PathSeparator))
if end > 0 {
repo := tail[:end]
return repo
}
}
}
return "go"
}
var googleSourceRx = regexp.MustCompile(`(?m)^(go|go-review)?\.googlesource.com\b`)
func checkCLA() {
slurp, err := ioutil.ReadFile(cookiesFile())
if err != nil && !os.IsNotExist(err) {
log.Fatal(err)
}
if googleSourceRx.Match(slurp) {
// Probably good.
return
}
log.Fatal("Your .gitcookies file isn't configured.\n" +
"Next steps:\n" +
" * Submit a CLA (https://golang.org/doc/contribute.html#cla) if not done\n" +
" * Go to https://go.googlesource.com/ and click \"Generate Password\" at the top,\n" +
" then follow instructions.\n" +
" * Run go-contrib-init again.\n")
}
func expandUser(s string) string {
env := "HOME"
if runtime.GOOS == "windows" {
env = "USERPROFILE"
} else if runtime.GOOS == "plan9" {
env = "home"
}
home := os.Getenv(env)
if home == "" {
return s
}
if len(s) >= 2 && s[0] == '~' && os.IsPathSeparator(s[1]) {
if runtime.GOOS == "windows" {
s = filepath.ToSlash(filepath.Join(home, s[2:]))
} else {
s = filepath.Join(home, s[2:])
}
}
return os.Expand(s, func(env string) string {
if env == "HOME" {
return home
}
return os.Getenv(env)
})
}
func cookiesFile() string {
out, _ := exec.Command("git", "config", "http.cookiefile").Output()
if s := strings.TrimSpace(string(out)); s != "" {
if strings.HasPrefix(s, "~") {
s = expandUser(s)
}
return s
}
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("USERPROFILE"), ".gitcookies")
}
return filepath.Join(os.Getenv("HOME"), ".gitcookies")
}
func checkGoroot() {
v := os.Getenv("GOROOT")
if v == "" {
return
}
if *repo == "go" {
if strings.HasPrefix(v, "/usr/") {
log.Fatalf("Your GOROOT environment variable is set to %q\n"+
"This is almost certainly not what you want. Either unset\n"+
"your GOROOT or set it to the path of your development version\n"+
"of Go.", v)
}
slurp, err := ioutil.ReadFile(filepath.Join(v, "VERSION"))
if err == nil {
slurp = bytes.TrimSpace(slurp)
log.Fatalf("Your GOROOT environment variable is set to %q\n"+
"But that path is to a binary release of Go, with VERSION file %q.\n"+
"You should hack on Go in a fresh checkout of Go. Fix or unset your GOROOT.\n",
v, slurp)
}
}
}
func checkWorkingDir() {
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
if *repo == "go" {
if inGoPath(wd) {
log.Fatalf(`You can't work on Go from within your GOPATH. Please checkout Go outside of your GOPATH
Current directory: %s
GOPATH: %s
`, wd, os.Getenv("GOPATH"))
}
return
}
gopath := firstGoPath()
if gopath == "" {
log.Fatal("Your GOPATH is not set, please set it")
}
rightdir := filepath.Join(gopath, "src", "golang.org", "x", *repo)
if !strings.HasPrefix(wd, rightdir) {
dirExists, err := exists(rightdir)
if err != nil {
log.Fatal(err)
}
if !dirExists {
log.Fatalf("The repo you want to work on is currently not on your system.\n"+
"Run %q to obtain this repo\n"+
"then go to the directory %q\n",
"go get -d golang.org/x/"+*repo, rightdir)
}
log.Fatalf("Your current directory is:%q\n"+
"Working on golang/x/%v requires you be in %q\n",
wd, *repo, rightdir)
}
}
func firstGoPath() string {
list := filepath.SplitList(build.Default.GOPATH)
if len(list) < 1 {
return ""
}
return list[0]
}
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
func inGoPath(wd string) bool {
if os.Getenv("GOPATH") == "" {
return false
}
for _, path := range filepath.SplitList(os.Getenv("GOPATH")) {
if strings.HasPrefix(wd, filepath.Join(path, "src")) {
return true
}
}
return false
}
// mostly check that they didn't clone from github
func checkGitOrigin() {
if _, err := exec.LookPath("git"); err != nil {
log.Fatalf("You don't appear to have git installed. Do that.")
}
wantRemote := "https://go.googlesource.com/" + *repo
remotes, err := exec.Command("git", "remote", "-v").Output()
if err != nil {
msg := cmdErr(err)
if strings.Contains(msg, "Not a git repository") {
log.Fatalf("Your current directory is not in a git checkout of %s", wantRemote)
}
log.Fatalf("Error running git remote -v: %v", msg)
}
matches := 0
for _, line := range strings.Split(string(remotes), "\n") {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "origin") {
continue
}
if !strings.Contains(line, wantRemote) {
curRemote := strings.Fields(strings.TrimPrefix(line, "origin"))[0]
// TODO: if not in dryRun mode, just fix it?
log.Fatalf("Current directory's git was cloned from %q; origin should be %q", curRemote, wantRemote)
}
matches++
}
if matches == 0 {
log.Fatalf("git remote -v output didn't contain expected %q. Got:\n%s", wantRemote, remotes)
}
}
func cmdErr(err error) string {
if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
return fmt.Sprintf("%s: %s", err, ee.Stderr)
}
return fmt.Sprint(err)
}
func checkGitCodeReview() {
if _, err := exec.LookPath("git-codereview"); err != nil {
if *dry {
log.Fatalf("You don't appear to have git-codereview tool. While this is technically optional,\n" +
"almost all Go contributors use it. Our documentation and this tool assume it is used.\n" +
"To install it, run:\n\n\t$ go get golang.org/x/review/git-codereview\n\n(Then run go-contrib-init again)")
}
err := exec.Command("go", "get", "golang.org/x/review/git-codereview").Run()
if err != nil {
log.Fatalf("Error running go get golang.org/x/review/git-codereview: %v", cmdErr(err))
}
log.Printf("Installed git-codereview (ran `go get golang.org/x/review/git-codereview`)")
}
missing := false
for _, cmd := range []string{"change", "gofmt", "mail", "pending", "submit", "sync"} {
v, _ := exec.Command("git", "config", "alias."+cmd).Output()
if strings.Contains(string(v), "codereview") {
continue
}
if *dry {
log.Printf("Missing alias. Run:\n\t$ git config alias.%s \"codereview %s\"", cmd, cmd)
missing = true
} else {
err := exec.Command("git", "config", "alias."+cmd, "codereview "+cmd).Run()
if err != nil {
log.Fatalf("Error setting alias.%s: %v", cmd, cmdErr(err))
}
}
}
if missing {
log.Fatalf("Missing aliases. (While optional, this tool assumes you use them.)")
}
}

View File

@ -1,35 +0,0 @@
package main
import (
"os"
"runtime"
"testing"
)
func TestExpandUser(t *testing.T) {
env := "HOME"
if runtime.GOOS == "windows" {
env = "USERPROFILE"
} else if runtime.GOOS == "plan9" {
env = "home"
}
oldenv := os.Getenv(env)
os.Setenv(env, "/home/gopher")
defer os.Setenv(env, oldenv)
tests := []struct {
input string
want string
}{
{input: "~/foo", want: "/home/gopher/foo"},
{input: "${HOME}/foo", want: "/home/gopher/foo"},
{input: "/~/foo", want: "/~/foo"},
}
for _, tt := range tests {
got := expandUser(tt.input)
if got != tt.want {
t.Fatalf("want %q, but %q", tt.want, got)
}
}
}

View File

@ -1,69 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The godex command prints (dumps) exported information of packages
// or selected package objects.
//
// In contrast to godoc, godex extracts this information from compiled
// object files. Hence the exported data is truly what a compiler will
// see, at the cost of missing commentary.
//
// Usage: godex [flags] {path[.name]}
//
// Each argument must be a (possibly partial) package path, optionally
// followed by a dot and the name of a package object:
//
// godex math
// godex math.Sin
// godex math.Sin fmt.Printf
// godex go/types
//
// godex automatically tries all possible package path prefixes if only a
// partial package path is given. For instance, for the path "go/types",
// godex prepends "golang.org/x/tools".
//
// The prefixes are computed by searching the directories specified by
// the GOROOT and GOPATH environment variables (and by excluding the
// build OS- and architecture-specific directory names from the path).
// The search order is depth-first and alphabetic; for a partial path
// "foo", a package "a/foo" is found before "b/foo".
//
// Absolute and relative paths may be provided, which disable automatic
// prefix generation:
//
// godex $GOROOT/pkg/darwin_amd64/sort
// godex ./sort
//
// All but the last path element may contain dots; a dot in the last path
// element separates the package path from the package object name. If the
// last path element contains a dot, terminate the argument with another
// dot (indicating an empty object name). For instance, the path for a
// package foo.bar would be specified as in:
//
// godex foo.bar.
//
// The flags are:
//
// -s=""
// only consider packages from src, where src is one of the supported compilers
// -v=false
// verbose mode
//
// The following sources (-s arguments) are supported:
//
// gc
// gc-generated object files
// gccgo
// gccgo-generated object files
// gccgo-new
// gccgo-generated object files using a condensed format (experimental)
// source
// (uncompiled) source code (not yet implemented)
//
// If no -s argument is provided, godex will try to find a matching source.
//
package main // import "golang.org/x/tools/cmd/godex"
// BUG(gri): support for -s=source is not yet implemented
// BUG(gri): gccgo-importing appears to have occasional problems stalling godex; try -s=gc as work-around

View File

@ -1,13 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements access to gc-generated export data.
package main
import "go/importer"
func init() {
register("gc", importer.For("gc", nil))
}

View File

@ -1,34 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements access to gccgo-generated export data.
package main
import (
"go/importer"
"go/types"
)
func init() {
register("gccgo", importer.For("gccgo", nil))
}
// Print the extra gccgo compiler data for this package, if it exists.
func (p *printer) printGccgoExtra(pkg *types.Package) {
// Disabled for now.
// TODO(gri) address this at some point.
// if initdata, ok := initmap[pkg]; ok {
// p.printf("/*\npriority %d\n", initdata.Priority)
// p.printDecl("init", len(initdata.Inits), func() {
// for _, init := range initdata.Inits {
// p.printf("%s %s %d\n", init.Name, init.InitFunc, init.Priority)
// }
// })
// p.print("*/\n")
// }
}

View File

@ -1,211 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
var (
source = flag.String("s", "", "only consider packages from src, where src is one of the supported compilers")
verbose = flag.Bool("v", false, "verbose mode")
)
// lists of registered sources and corresponding importers
var (
sources []string
importers []types.Importer
importFailed = errors.New("import failed")
)
func usage() {
fmt.Fprintln(os.Stderr, "usage: godex [flags] {path|qualifiedIdent}")
flag.PrintDefaults()
os.Exit(2)
}
func report(msg string) {
fmt.Fprintln(os.Stderr, "error: "+msg)
os.Exit(2)
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() == 0 {
report("no package name, path, or file provided")
}
var imp types.Importer = new(tryImporters)
if *source != "" {
imp = lookup(*source)
if imp == nil {
report("source (-s argument) must be one of: " + strings.Join(sources, ", "))
}
}
for _, arg := range flag.Args() {
path, name := splitPathIdent(arg)
logf("\tprocessing %q: path = %q, name = %s\n", arg, path, name)
// generate possible package path prefixes
// (at the moment we do this for each argument - should probably cache the generated prefixes)
prefixes := make(chan string)
go genPrefixes(prefixes, !filepath.IsAbs(path) && !build.IsLocalImport(path))
// import package
pkg, err := tryPrefixes(prefixes, path, imp)
if err != nil {
logf("\t=> ignoring %q: %s\n", path, err)
continue
}
// filter objects if needed
var filter func(types.Object) bool
if name != "" {
filter = func(obj types.Object) bool {
// TODO(gri) perhaps use regular expression matching here?
return obj.Name() == name
}
}
// print contents
print(os.Stdout, pkg, filter)
}
}
func logf(format string, args ...interface{}) {
if *verbose {
fmt.Fprintf(os.Stderr, format, args...)
}
}
// splitPathIdent splits a path.name argument into its components.
// All but the last path element may contain dots.
func splitPathIdent(arg string) (path, name string) {
if i := strings.LastIndex(arg, "."); i >= 0 {
if j := strings.LastIndex(arg, "/"); j < i {
// '.' is not part of path
path = arg[:i]
name = arg[i+1:]
return
}
}
path = arg
return
}
// tryPrefixes tries to import the package given by (the possibly partial) path using the given importer imp
// by prepending all possible prefixes to path. It returns with the first package that it could import, or
// with an error.
func tryPrefixes(prefixes chan string, path string, imp types.Importer) (pkg *types.Package, err error) {
for prefix := range prefixes {
actual := path
if prefix == "" {
// don't use filepath.Join as it will sanitize the path and remove
// a leading dot and then the path is not recognized as a relative
// package path by the importers anymore
logf("\ttrying no prefix\n")
} else {
actual = filepath.Join(prefix, path)
logf("\ttrying prefix %q\n", prefix)
}
pkg, err = imp.Import(actual)
if err == nil {
break
}
logf("\t=> importing %q failed: %s\n", actual, err)
}
return
}
// tryImporters is an importer that tries all registered importers
// successively until one of them succeeds or all of them failed.
type tryImporters struct{}
func (t *tryImporters) Import(path string) (pkg *types.Package, err error) {
for i, imp := range importers {
logf("\t\ttrying %s import\n", sources[i])
pkg, err = imp.Import(path)
if err == nil {
break
}
logf("\t\t=> %s import failed: %s\n", sources[i], err)
}
return
}
type protector struct {
imp types.Importer
}
func (p *protector) Import(path string) (pkg *types.Package, err error) {
defer func() {
if recover() != nil {
pkg = nil
err = importFailed
}
}()
return p.imp.Import(path)
}
// protect protects an importer imp from panics and returns the protected importer.
func protect(imp types.Importer) types.Importer {
return &protector{imp}
}
// register registers an importer imp for a given source src.
func register(src string, imp types.Importer) {
if lookup(src) != nil {
panic(src + " importer already registered")
}
sources = append(sources, src)
importers = append(importers, protect(imp))
}
// lookup returns the importer imp for a given source src.
func lookup(src string) types.Importer {
for i, s := range sources {
if s == src {
return importers[i]
}
}
return nil
}
func genPrefixes(out chan string, all bool) {
out <- ""
if all {
platform := build.Default.GOOS + "_" + build.Default.GOARCH
dirnames := append([]string{build.Default.GOROOT}, filepath.SplitList(build.Default.GOPATH)...)
for _, dirname := range dirnames {
walkDir(filepath.Join(dirname, "pkg", platform), "", out)
}
}
close(out)
}
func walkDir(dirname, prefix string, out chan string) {
fiList, err := ioutil.ReadDir(dirname)
if err != nil {
return
}
for _, fi := range fiList {
if fi.IsDir() && !strings.HasPrefix(fi.Name(), ".") {
prefix := filepath.Join(prefix, fi.Name())
out <- prefix
walkDir(filepath.Join(dirname, fi.Name()), prefix, out)
}
}
}

View File

@ -1,13 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.9
package main
import "go/types"
func isAlias(obj *types.TypeName) bool {
return false // there are no type aliases before Go 1.9
}

View File

@ -1,13 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.9
package main
import "go/types"
func isAlias(obj *types.TypeName) bool {
return obj.IsAlias()
}

View File

@ -1,373 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"fmt"
"go/constant"
"go/token"
"go/types"
"io"
"math/big"
)
// TODO(gri) use tabwriter for alignment?
func print(w io.Writer, pkg *types.Package, filter func(types.Object) bool) {
var p printer
p.pkg = pkg
p.printPackage(pkg, filter)
p.printGccgoExtra(pkg)
io.Copy(w, &p.buf)
}
type printer struct {
pkg *types.Package
buf bytes.Buffer
indent int // current indentation level
last byte // last byte written
}
func (p *printer) print(s string) {
// Write the string one byte at a time. We care about the presence of
// newlines for indentation which we will see even in the presence of
// (non-corrupted) Unicode; no need to read one rune at a time.
for i := 0; i < len(s); i++ {
ch := s[i]
if ch != '\n' && p.last == '\n' {
// Note: This could lead to a range overflow for very large
// indentations, but it's extremely unlikely to happen for
// non-pathological code.
p.buf.WriteString("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"[:p.indent])
}
p.buf.WriteByte(ch)
p.last = ch
}
}
func (p *printer) printf(format string, args ...interface{}) {
p.print(fmt.Sprintf(format, args...))
}
// methodsFor returns the named type and corresponding methods if the type
// denoted by obj is not an interface and has methods. Otherwise it returns
// the zero value.
func methodsFor(obj *types.TypeName) (*types.Named, []*types.Selection) {
named, _ := obj.Type().(*types.Named)
if named == nil {
// A type name's type can also be the
// exported basic type unsafe.Pointer.
return nil, nil
}
if _, ok := named.Underlying().(*types.Interface); ok {
// ignore interfaces
return nil, nil
}
methods := combinedMethodSet(named)
if len(methods) == 0 {
return nil, nil
}
return named, methods
}
func (p *printer) printPackage(pkg *types.Package, filter func(types.Object) bool) {
// collect objects by kind
var (
consts []*types.Const
typem []*types.Named // non-interface types with methods
typez []*types.TypeName // interfaces or types without methods
vars []*types.Var
funcs []*types.Func
builtins []*types.Builtin
methods = make(map[*types.Named][]*types.Selection) // method sets for named types
)
scope := pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if obj.Exported() {
// collect top-level exported and possibly filtered objects
if filter == nil || filter(obj) {
switch obj := obj.(type) {
case *types.Const:
consts = append(consts, obj)
case *types.TypeName:
// group into types with methods and types without
if named, m := methodsFor(obj); named != nil {
typem = append(typem, named)
methods[named] = m
} else {
typez = append(typez, obj)
}
case *types.Var:
vars = append(vars, obj)
case *types.Func:
funcs = append(funcs, obj)
case *types.Builtin:
// for unsafe.Sizeof, etc.
builtins = append(builtins, obj)
}
}
} else if filter == nil {
// no filtering: collect top-level unexported types with methods
if obj, _ := obj.(*types.TypeName); obj != nil {
// see case *types.TypeName above
if named, m := methodsFor(obj); named != nil {
typem = append(typem, named)
methods[named] = m
}
}
}
}
p.printf("package %s // %q\n", pkg.Name(), pkg.Path())
p.printDecl("const", len(consts), func() {
for _, obj := range consts {
p.printObj(obj)
p.print("\n")
}
})
p.printDecl("var", len(vars), func() {
for _, obj := range vars {
p.printObj(obj)
p.print("\n")
}
})
p.printDecl("type", len(typez), func() {
for _, obj := range typez {
p.printf("%s ", obj.Name())
typ := obj.Type()
if isAlias(obj) {
p.print("= ")
p.writeType(p.pkg, typ)
} else {
p.writeType(p.pkg, typ.Underlying())
}
p.print("\n")
}
})
// non-interface types with methods
for _, named := range typem {
first := true
if obj := named.Obj(); obj.Exported() {
if first {
p.print("\n")
first = false
}
p.printf("type %s ", obj.Name())
p.writeType(p.pkg, named.Underlying())
p.print("\n")
}
for _, m := range methods[named] {
if obj := m.Obj(); obj.Exported() {
if first {
p.print("\n")
first = false
}
p.printFunc(m.Recv(), obj.(*types.Func))
p.print("\n")
}
}
}
if len(funcs) > 0 {
p.print("\n")
for _, obj := range funcs {
p.printFunc(nil, obj)
p.print("\n")
}
}
// TODO(gri) better handling of builtins (package unsafe only)
if len(builtins) > 0 {
p.print("\n")
for _, obj := range builtins {
p.printf("func %s() // builtin\n", obj.Name())
}
}
p.print("\n")
}
func (p *printer) printDecl(keyword string, n int, printGroup func()) {
switch n {
case 0:
// nothing to do
case 1:
p.printf("\n%s ", keyword)
printGroup()
default:
p.printf("\n%s (\n", keyword)
p.indent++
printGroup()
p.indent--
p.print(")\n")
}
}
// absInt returns the absolute value of v as a *big.Int.
// v must be a numeric value.
func absInt(v constant.Value) *big.Int {
// compute big-endian representation of v
b := constant.Bytes(v) // little-endian
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return new(big.Int).SetBytes(b)
}
var (
one = big.NewRat(1, 1)
ten = big.NewRat(10, 1)
)
// floatString returns the string representation for a
// numeric value v in normalized floating-point format.
func floatString(v constant.Value) string {
if constant.Sign(v) == 0 {
return "0.0"
}
// x != 0
// convert |v| into a big.Rat x
x := new(big.Rat).SetFrac(absInt(constant.Num(v)), absInt(constant.Denom(v)))
// normalize x and determine exponent e
// (This is not very efficient, but also not speed-critical.)
var e int
for x.Cmp(ten) >= 0 {
x.Quo(x, ten)
e++
}
for x.Cmp(one) < 0 {
x.Mul(x, ten)
e--
}
// TODO(gri) Values such as 1/2 are easier to read in form 0.5
// rather than 5.0e-1. Similarly, 1.0e1 is easier to read as
// 10.0. Fine-tune best exponent range for readability.
s := x.FloatString(100) // good-enough precision
// trim trailing 0's
i := len(s)
for i > 0 && s[i-1] == '0' {
i--
}
s = s[:i]
// add a 0 if the number ends in decimal point
if len(s) > 0 && s[len(s)-1] == '.' {
s += "0"
}
// add exponent and sign
if e != 0 {
s += fmt.Sprintf("e%+d", e)
}
if constant.Sign(v) < 0 {
s = "-" + s
}
// TODO(gri) If v is a "small" fraction (i.e., numerator and denominator
// are just a small number of decimal digits), add the exact fraction as
// a comment. For instance: 3.3333...e-1 /* = 1/3 */
return s
}
// valString returns the string representation for the value v.
// Setting floatFmt forces an integer value to be formatted in
// normalized floating-point format.
// TODO(gri) Move this code into package constant.
func valString(v constant.Value, floatFmt bool) string {
switch v.Kind() {
case constant.Int:
if floatFmt {
return floatString(v)
}
case constant.Float:
return floatString(v)
case constant.Complex:
re := constant.Real(v)
im := constant.Imag(v)
var s string
if constant.Sign(re) != 0 {
s = floatString(re)
if constant.Sign(im) >= 0 {
s += " + "
} else {
s += " - "
im = constant.UnaryOp(token.SUB, im, 0) // negate im
}
}
// im != 0, otherwise v would be constant.Int or constant.Float
return s + floatString(im) + "i"
}
return v.String()
}
func (p *printer) printObj(obj types.Object) {
p.print(obj.Name())
typ, basic := obj.Type().Underlying().(*types.Basic)
if basic && typ.Info()&types.IsUntyped != 0 {
// don't write untyped types
} else {
p.print(" ")
p.writeType(p.pkg, obj.Type())
}
if obj, ok := obj.(*types.Const); ok {
floatFmt := basic && typ.Info()&(types.IsFloat|types.IsComplex) != 0
p.print(" = ")
p.print(valString(obj.Val(), floatFmt))
}
}
func (p *printer) printFunc(recvType types.Type, obj *types.Func) {
p.print("func ")
sig := obj.Type().(*types.Signature)
if recvType != nil {
p.print("(")
p.writeType(p.pkg, recvType)
p.print(") ")
}
p.print(obj.Name())
p.writeSignature(p.pkg, sig)
}
// combinedMethodSet returns the method set for a named type T
// merged with all the methods of *T that have different names than
// the methods of T.
//
// combinedMethodSet is analogous to types/typeutil.IntuitiveMethodSet
// but doesn't require a MethodSetCache.
// TODO(gri) If this functionality doesn't change over time, consider
// just calling IntuitiveMethodSet eventually.
func combinedMethodSet(T *types.Named) []*types.Selection {
// method set for T
mset := types.NewMethodSet(T)
var res []*types.Selection
for i, n := 0, mset.Len(); i < n; i++ {
res = append(res, mset.At(i))
}
// add all *T methods with names different from T methods
pmset := types.NewMethodSet(types.NewPointer(T))
for i, n := 0, pmset.Len(); i < n; i++ {
pm := pmset.At(i)
if obj := pm.Obj(); mset.Lookup(obj.Pkg(), obj.Name()) == nil {
res = append(res, pm)
}
}
return res
}

View File

@ -1,19 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements access to export data from source.
package main
import "go/types"
func init() {
register("source", sourceImporter{})
}
type sourceImporter struct{}
func (sourceImporter) Import(path string) (*types.Package, error) {
panic("unimplemented")
}

View File

@ -1,242 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements writing of types. The functionality is lifted
// directly from go/types, but now contains various modifications for
// nicer output.
//
// TODO(gri) back-port once we have a fixed interface and once the
// go/types API is not frozen anymore for the 1.3 release; and remove
// this implementation if possible.
package main
import "go/types"
func (p *printer) writeType(this *types.Package, typ types.Type) {
p.writeTypeInternal(this, typ, make([]types.Type, 8))
}
// From go/types - leave for now to ease back-porting this code.
const GcCompatibilityMode = false
func (p *printer) writeTypeInternal(this *types.Package, typ types.Type, visited []types.Type) {
// Theoretically, this is a quadratic lookup algorithm, but in
// practice deeply nested composite types with unnamed component
// types are uncommon. This code is likely more efficient than
// using a map.
for _, t := range visited {
if t == typ {
p.printf("○%T", typ) // cycle to typ
return
}
}
visited = append(visited, typ)
switch t := typ.(type) {
case nil:
p.print("<nil>")
case *types.Basic:
if t.Kind() == types.UnsafePointer {
p.print("unsafe.")
}
if GcCompatibilityMode {
// forget the alias names
switch t.Kind() {
case types.Byte:
t = types.Typ[types.Uint8]
case types.Rune:
t = types.Typ[types.Int32]
}
}
p.print(t.Name())
case *types.Array:
p.printf("[%d]", t.Len())
p.writeTypeInternal(this, t.Elem(), visited)
case *types.Slice:
p.print("[]")
p.writeTypeInternal(this, t.Elem(), visited)
case *types.Struct:
n := t.NumFields()
if n == 0 {
p.print("struct{}")
return
}
p.print("struct {\n")
p.indent++
for i := 0; i < n; i++ {
f := t.Field(i)
if !f.Anonymous() {
p.printf("%s ", f.Name())
}
p.writeTypeInternal(this, f.Type(), visited)
if tag := t.Tag(i); tag != "" {
p.printf(" %q", tag)
}
p.print("\n")
}
p.indent--
p.print("}")
case *types.Pointer:
p.print("*")
p.writeTypeInternal(this, t.Elem(), visited)
case *types.Tuple:
p.writeTuple(this, t, false, visited)
case *types.Signature:
p.print("func")
p.writeSignatureInternal(this, t, visited)
case *types.Interface:
// We write the source-level methods and embedded types rather
// than the actual method set since resolved method signatures
// may have non-printable cycles if parameters have anonymous
// interface types that (directly or indirectly) embed the
// current interface. For instance, consider the result type
// of m:
//
// type T interface{
// m() interface{ T }
// }
//
n := t.NumMethods()
if n == 0 {
p.print("interface{}")
return
}
p.print("interface {\n")
p.indent++
if GcCompatibilityMode {
// print flattened interface
// (useful to compare against gc-generated interfaces)
for i := 0; i < n; i++ {
m := t.Method(i)
p.print(m.Name())
p.writeSignatureInternal(this, m.Type().(*types.Signature), visited)
p.print("\n")
}
} else {
// print explicit interface methods and embedded types
for i, n := 0, t.NumExplicitMethods(); i < n; i++ {
m := t.ExplicitMethod(i)
p.print(m.Name())
p.writeSignatureInternal(this, m.Type().(*types.Signature), visited)
p.print("\n")
}
for i, n := 0, t.NumEmbeddeds(); i < n; i++ {
typ := t.Embedded(i)
p.writeTypeInternal(this, typ, visited)
p.print("\n")
}
}
p.indent--
p.print("}")
case *types.Map:
p.print("map[")
p.writeTypeInternal(this, t.Key(), visited)
p.print("]")
p.writeTypeInternal(this, t.Elem(), visited)
case *types.Chan:
var s string
var parens bool
switch t.Dir() {
case types.SendRecv:
s = "chan "
// chan (<-chan T) requires parentheses
if c, _ := t.Elem().(*types.Chan); c != nil && c.Dir() == types.RecvOnly {
parens = true
}
case types.SendOnly:
s = "chan<- "
case types.RecvOnly:
s = "<-chan "
default:
panic("unreachable")
}
p.print(s)
if parens {
p.print("(")
}
p.writeTypeInternal(this, t.Elem(), visited)
if parens {
p.print(")")
}
case *types.Named:
s := "<Named w/o object>"
if obj := t.Obj(); obj != nil {
if pkg := obj.Pkg(); pkg != nil {
if pkg != this {
p.print(pkg.Path())
p.print(".")
}
// TODO(gri): function-local named types should be displayed
// differently from named types at package level to avoid
// ambiguity.
}
s = obj.Name()
}
p.print(s)
default:
// For externally defined implementations of Type.
p.print(t.String())
}
}
func (p *printer) writeTuple(this *types.Package, tup *types.Tuple, variadic bool, visited []types.Type) {
p.print("(")
for i, n := 0, tup.Len(); i < n; i++ {
if i > 0 {
p.print(", ")
}
v := tup.At(i)
if name := v.Name(); name != "" {
p.print(name)
p.print(" ")
}
typ := v.Type()
if variadic && i == n-1 {
p.print("...")
typ = typ.(*types.Slice).Elem()
}
p.writeTypeInternal(this, typ, visited)
}
p.print(")")
}
func (p *printer) writeSignature(this *types.Package, sig *types.Signature) {
p.writeSignatureInternal(this, sig, make([]types.Type, 8))
}
func (p *printer) writeSignatureInternal(this *types.Package, sig *types.Signature, visited []types.Type) {
p.writeTuple(this, sig.Params(), sig.Variadic(), visited)
res := sig.Results()
n := res.Len()
if n == 0 {
// no result
return
}
p.print(" ")
if n == 1 && res.At(0).Name() == "" {
// single unnamed result
p.writeTypeInternal(this, res.At(0).Type(), visited)
return
}
// multiple or named result(s)
p.writeTuple(this, res, false, visited)
}

View File

@ -0,0 +1,56 @@
godoc on appengine
------------------
Prerequisites
-------------
* Go appengine SDK
https://developers.google.com/appengine/downloads#Google_App_Engine_SDK_for_Go
* Go sources at tip under $GOROOT
* Godoc sources at tip inside $GOPATH
(go get -d code.google.com/p/go.tools/cmd/godoc)
Directory structure
-------------------
* Let $APPDIR be the directory containing the app engine files.
(e.g., $APPDIR=$HOME/godoc-app)
* $APPDIR contains the following entries (this may change depending on
app-engine release and version of godoc):
app.yaml
code.google.com/p/go.tools/cmd/godoc
godoc.zip
index.split.*
* The app.yaml file is set up per app engine documentation.
For instance:
application: godoc-app
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
Configuring and running godoc
-----------------------------
To configure godoc, run
bash setup-godoc-app.bash
to prepare an $APPDIR as described above. See the script for details on usage.
To run godoc locally, using the App Engine development server, run
<path to go_appengine>/dev_appserver.py $APPDIR
godoc should come up at http://localhost:8080 .

63
cmd/godoc/appinit.go Normal file
View File

@ -0,0 +1,63 @@
// 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.
// +build appengine
package main
// This file replaces main.go when running godoc under app-engine.
// See README.godoc-app for details.
import (
"archive/zip"
"log"
"path"
"code.google.com/p/go.tools/godoc"
"code.google.com/p/go.tools/godoc/static"
"code.google.com/p/go.tools/godoc/vfs"
"code.google.com/p/go.tools/godoc/vfs/mapfs"
"code.google.com/p/go.tools/godoc/vfs/zipfs"
)
func init() {
playEnabled = true
log.Println("initializing godoc ...")
log.Printf(".zip file = %s", zipFilename)
log.Printf(".zip GOROOT = %s", zipGoroot)
log.Printf("index files = %s", indexFilenames)
goroot := path.Join("/", zipGoroot) // fsHttp paths are relative to '/'
// read .zip file and set up file systems
const zipfile = zipFilename
rc, err := zip.OpenReader(zipfile)
if err != nil {
log.Fatalf("%s: %s\n", zipfile, err)
}
// rc is never closed (app running forever)
fs.Bind("/", zipfs.New(rc, zipFilename), goroot, vfs.BindReplace)
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
corpus := godoc.NewCorpus(fs)
corpus.Verbose = false
corpus.IndexEnabled = true
corpus.IndexFiles = indexFilenames
if err := corpus.Init(); err != nil {
log.Fatal(err)
}
go corpus.RunIndexer()
pres = godoc.NewPresentation(corpus)
pres.TabWidth = 8
pres.ShowPlayground = true
pres.ShowExamples = true
pres.DeclLinks = true
readTemplates(pres, true)
registerHandlers(pres)
log.Println("godoc initialization complete")
}

View File

@ -15,13 +15,13 @@ import (
"strings" "strings"
"sync" "sync"
"golang.org/x/tools/blog" "code.google.com/p/go.tools/blog"
"golang.org/x/tools/godoc/redirect" "code.google.com/p/go.tools/godoc/redirect"
) )
const ( const (
blogRepo = "golang.org/x/blog" blogRepo = "code.google.com/p/go.blog"
blogURL = "https://blog.golang.org/" blogURL = "http://blog.golang.org/"
blogPath = "/blog/" blogPath = "/blog/"
) )
@ -34,19 +34,16 @@ var (
func init() { func init() {
// Initialize blog only when first accessed. // Initialize blog only when first accessed.
http.HandleFunc(blogPath, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc(blogPath, func(w http.ResponseWriter, r *http.Request) {
blogInitOnce.Do(func() { blogInitOnce.Do(blogInit)
blogInit(r.Host)
})
blogServer.ServeHTTP(w, r) blogServer.ServeHTTP(w, r)
}) })
} }
func blogInit(host string) { func blogInit() {
// Binary distributions included the blog content in "/blog". // Binary distributions will include the blog content in "/blog".
// We stopped including this in Go 1.11.
root := filepath.Join(runtime.GOROOT(), "blog") root := filepath.Join(runtime.GOROOT(), "blog")
// Prefer content from the golang.org/x/blog repository if present. // Prefer content from go.blog repository if present.
if pkg, err := build.Import(blogRepo, "", build.FindOnly); err == nil { if pkg, err := build.Import(blogRepo, "", build.FindOnly); err == nil {
root = pkg.Dir root = pkg.Dir
} }
@ -60,13 +57,12 @@ func blogInit(host string) {
} }
s, err := blog.NewServer(blog.Config{ s, err := blog.NewServer(blog.Config{
BaseURL: blogPath, BaseURL: blogPath,
BasePath: strings.TrimSuffix(blogPath, "/"), BasePath: strings.TrimSuffix(blogPath, "/"),
ContentPath: filepath.Join(root, "content"), ContentPath: filepath.Join(root, "content"),
TemplatePath: filepath.Join(root, "template"), TemplatePath: filepath.Join(root, "template"),
HomeArticles: 5, HomeArticles: 5,
PlayEnabled: playEnabled, PlayEnabled: playEnabled,
ServeLocalLinks: strings.HasPrefix(host, "localhost"),
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -29,8 +29,8 @@ import (
"text/template" "text/template"
"unicode/utf8" "unicode/utf8"
"golang.org/x/tools/godoc" "code.google.com/p/go.tools/godoc"
"golang.org/x/tools/godoc/vfs" "code.google.com/p/go.tools/godoc/vfs"
) )
var codewalkHTML, codewalkdirHTML *template.Template var codewalkHTML, codewalkdirHTML *template.Template

View File

@ -1,14 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import "net/http"
// Register a redirect handler for /dl/ to the golang.org download page.
// This file will not be included when deploying godoc to golang.org.
func init() {
http.Handle("/dl/", http.RedirectHandler("https://golang.org/dl/", http.StatusFound))
}

View File

@ -6,19 +6,48 @@
Godoc extracts and generates documentation for Go programs. Godoc extracts and generates documentation for Go programs.
It runs as a web server and presents the documentation as a It has two modes.
Without the -http flag, it runs in command-line mode and prints plain text
documentation to standard output and exits. If both a library package and
a command with the same name exists, using the prefix cmd/ will force
documentation on the command rather than the library package. If the -src
flag is specified, godoc prints the exported interface of a package in Go
source form, or the implementation of a specific exported language entity:
godoc fmt # documentation for package fmt
godoc fmt Printf # documentation for fmt.Printf
godoc cmd/go # force documentation for the go command
godoc -src fmt # fmt package interface in Go source form
godoc -src fmt Printf # implementation of fmt.Printf
In command-line mode, the -q flag enables search queries against a godoc running
as a webserver. If no explicit server address is specified with the -server flag,
godoc first tries localhost:6060 and then http://golang.org.
godoc -q Reader
godoc -q math.Sin
godoc -server=:6060 -q sin
With the -http flag, it runs as a web server and presents the documentation as a
web page. web page.
godoc -http=:6060 godoc -http=:6060
Usage: Usage:
godoc [flag] package [name ...]
godoc [flag]
The flags are: The flags are:
-v -v
verbose mode verbose mode
-q
arguments are considered search queries: a legal query is a
single identifier (such as ToLower) or a qualified identifier
(such as math.Sin).
-src
print (exported) source in command-line mode
-tabwidth=4
width of tabs in units of spaces
-timestamps=true -timestamps=true
show timestamps with directory listings show timestamps with directory listings
-index -index
@ -32,12 +61,7 @@ The flags are:
to the indexer (the indexer will never finish), a value of 1.0 to the indexer (the indexer will never finish), a value of 1.0
means that index creation is running at full throttle (other means that index creation is running at full throttle (other
goroutines may get no time while the index is built) goroutines may get no time while the index is built)
-index_interval=0 -links=true:
interval of indexing; a value of 0 sets it to 5 minutes, a
negative value indexes only once at startup
-play=false
enable playground
-links=true
link identifiers to their declarations link identifiers to their declarations
-write_index=false -write_index=false
write index to a file; the file name must be specified with write index to a file; the file name must be specified with
@ -48,17 +72,14 @@ The flags are:
-notes="BUG" -notes="BUG"
regular expression matching note markers to show regular expression matching note markers to show
(e.g., "BUG|TODO", ".*") (e.g., "BUG|TODO", ".*")
-html
print HTML in command-line mode
-goroot=$GOROOT -goroot=$GOROOT
Go root directory Go root directory
-http=addr -http=addr
HTTP service address (e.g., '127.0.0.1:6060' or just ':6060') HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
-analysis=type,pointer -server=addr
comma-separated list of analyses to perform webserver address for command line searches
"type": display identifier resolution, type info, method sets,
'implements', and static callees
"pointer": display channel peers, callers and dynamic callees
(significantly slower)
See https://golang.org/lib/godoc/analysis/help.html for details.
-templates="" -templates=""
directory containing alternate template files; if set, directory containing alternate template files; if set,
the directory may provide alternative template files the directory may provide alternative template files
@ -73,7 +94,7 @@ By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set
This behavior can be altered by providing an alternative $GOROOT with the -goroot This behavior can be altered by providing an alternative $GOROOT with the -goroot
flag. flag.
When the -index flag is set, a search index is maintained. When godoc runs as a web server and -index is set, a search index is maintained.
The index is created at startup. The index is created at startup.
The index contains both identifier and full text search information (searchable The index contains both identifier and full text search information (searchable
@ -81,19 +102,18 @@ via regular expressions). The maximum number of full text search results shown
can be set with the -maxresults flag; if set to 0, no full text results are can be set with the -maxresults flag; if set to 0, no full text results are
shown, and only an identifier index but no full text search index is created. shown, and only an identifier index but no full text search index is created.
By default, godoc uses the system's GOOS/GOARCH. You can provide the URL parameters
"GOOS" and "GOARCH" to set the output on the web page for the target system.
The presentation mode of web pages served by godoc can be controlled with the The presentation mode of web pages served by godoc can be controlled with the
"m" URL parameter; it accepts a comma-separated list of flag names as value: "m" URL parameter; it accepts a comma-separated list of flag names as value:
all show documentation for all declarations, not just the exported ones all show documentation for all declarations, not just the exported ones
methods show all embedded methods, not just those of unexported anonymous fields methods show all embedded methods, not just those of unexported anonymous fields
src show the original source code rather than the extracted documentation src show the original source code rather then the extracted documentation
text present the page in textual (command-line) form rather than HTML
flat present flat (not indented) directory listings using full paths flat present flat (not indented) directory listings using full paths
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation For instance, http://golang.org/pkg/math/big/?m=all,text shows the documentation
for all (not just the exported) declarations of package big. for all (not just the exported) declarations of package big, in textual form (as
it would appear when using godoc from the command line: "godoc -src math/big .*").
By default, godoc serves files from the file system of the underlying OS. By default, godoc serves files from the file system of the underlying OS.
Instead, a .zip file may be provided via the -zip flag, which contains Instead, a .zip file may be provided via the -zip flag, which contains
@ -102,18 +122,16 @@ slash ('/') as path separator; and they must be unrooted. $GOROOT (or -goroot)
must be set to the .zip file directory path containing the Go root directory. must be set to the .zip file directory path containing the Go root directory.
For instance, for a .zip file created by the command: For instance, for a .zip file created by the command:
zip -r go.zip $HOME/go zip go.zip $HOME/go
one may run godoc as follows: one may run godoc as follows:
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
Godoc documentation is converted to HTML or to text using the go/doc package; Godoc documentation is converted to HTML or to text using the go/doc package;
see https://golang.org/pkg/go/doc/#ToHTML for the exact rules. see http://golang.org/pkg/go/doc/#ToHTML for the exact rules.
Godoc also shows example code that is runnable by the testing package;
see https://golang.org/pkg/testing/#hdr-Examples for the conventions.
See "Godoc: documenting Go code" for how to write good comments for godoc: See "Godoc: documenting Go code" for how to write good comments for godoc:
https://golang.org/doc/articles/godoc_documenting_go_code.html http://golang.org/doc/articles/godoc_documenting_go_code.html
*/ */
package main // import "golang.org/x/tools/cmd/godoc" package main

View File

@ -5,48 +5,44 @@
package main_test package main_test
import ( import (
"bufio"
"bytes"
"fmt"
"go/build"
"io"
"io/ioutil" "io/ioutil"
"net"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"strings"
"testing" "testing"
"time"
) )
// buildGodoc builds the godoc executable. var godocTests = []struct {
// It returns its path, and a cleanup function. args []string
// matches []string // regular expressions
// TODO(adonovan): opt: do this at most once, and do the cleanup }{
// exactly once. How though? There's no atexit. {
func buildGodoc(t *testing.T) (bin string, cleanup func()) { []string{"fmt"},
if runtime.GOARCH == "arm" { []string{
t.Skip("skipping test on arm platforms; too slow") `import "fmt"`,
} `Package fmt implements formatted I/O`,
if runtime.GOOS == "android" { },
t.Skipf("the dependencies are not available on android") },
} {
[]string{"io", "WriteString"},
[]string{
`import "io"`,
`func WriteString\(`,
},
},
}
// Basic regression test for godoc command-line tool.
func TestGodoc(t *testing.T) {
tmp, err := ioutil.TempDir("", "godoc-regtest-") tmp, err := ioutil.TempDir("", "godoc-regtest-")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() { defer os.RemoveAll(tmp)
if cleanup == nil { // probably, go build failed.
os.RemoveAll(tmp)
}
}()
bin = filepath.Join(tmp, "godoc") bin := filepath.Join(tmp, "godoc")
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
bin += ".exe" bin += ".exe"
} }
@ -55,454 +51,24 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
t.Fatalf("Building godoc: %v", err) t.Fatalf("Building godoc: %v", err)
} }
return bin, func() { os.RemoveAll(tmp) } for _, test := range godocTests {
} cmd := exec.Command(bin, test.args...)
cmd.Args[0] = "godoc"
func serverAddress(t *testing.T) string { out, err := cmd.CombinedOutput()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
ln, err = net.Listen("tcp6", "[::1]:0")
}
if err != nil {
t.Fatal(err)
}
defer ln.Close()
return ln.Addr().String()
}
func waitForServerReady(t *testing.T, addr string) {
waitForServer(t,
fmt.Sprintf("http://%v/", addr),
"The Go Programming Language",
15*time.Second,
false)
}
func waitForSearchReady(t *testing.T, addr string) {
waitForServer(t,
fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr),
"The list of tokens.",
2*time.Minute,
false)
}
func waitUntilScanComplete(t *testing.T, addr string) {
waitForServer(t,
fmt.Sprintf("http://%v/pkg", addr),
"Scan is not yet complete",
2*time.Minute,
true,
)
// setting reverse as true, which means this waits
// until the string is not returned in the response anymore
}
const pollInterval = 200 * time.Millisecond
func waitForServer(t *testing.T, url, match string, timeout time.Duration, reverse bool) {
// "health check" duplicated from x/tools/cmd/tipgodoc/tip.go
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
time.Sleep(pollInterval)
res, err := http.Get(url)
if err != nil { if err != nil {
t.Errorf("Running with args %#v: %v", test.args, err)
continue continue
} }
rbody, err := ioutil.ReadAll(res.Body) logged := false
res.Body.Close() for _, pat := range test.matches {
if err == nil && res.StatusCode == http.StatusOK { re := regexp.MustCompile(pat)
if bytes.Contains(rbody, []byte(match)) && !reverse { if !re.Match(out) {
return if !logged {
} t.Logf("Output of running with args %#v:\n%s", test.args, out)
if !bytes.Contains(rbody, []byte(match)) && reverse { logged = true
return
}
}
}
t.Fatalf("Server failed to respond in %v", timeout)
}
// hasTag checks whether a given release tag is contained in the current version
// of the go binary.
func hasTag(t string) bool {
for _, v := range build.Default.ReleaseTags {
if t == v {
return true
}
}
return false
}
func killAndWait(cmd *exec.Cmd) {
cmd.Process.Kill()
cmd.Wait()
}
func TestURL(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; fails to start up quickly enough")
}
bin, cleanup := buildGodoc(t)
defer cleanup()
testcase := func(url string, contents string) func(t *testing.T) {
return func(t *testing.T) {
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
args := []string{fmt.Sprintf("-url=%s", url)}
cmd := exec.Command(bin, args...)
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Args[0] = "godoc"
// Set GOPATH variable to non-existing path
// and GOPROXY=off to disable module fetches.
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
// (We don't want the indexer looking at the local workspace during tests.)
cmd.Env = append(os.Environ(),
"GOPATH=does_not_exist",
"GOPROXY=off",
"GO111MODULE=off")
if err := cmd.Run(); err != nil {
t.Fatalf("failed to run godoc -url=%q: %s\nstderr:\n%s", url, err, stderr)
}
if !strings.Contains(stdout.String(), contents) {
t.Errorf("did not find substring %q in output of godoc -url=%q:\n%s", contents, url, stdout)
}
}
}
t.Run("index", testcase("/", "Go is an open source programming language"))
t.Run("fmt", testcase("/pkg/fmt", "Package fmt implements formatted I/O"))
}
// Basic integration test for godoc HTTP interface.
func TestWeb(t *testing.T) {
testWeb(t, false)
}
// Basic integration test for godoc HTTP interface.
func TestWebIndex(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in -short mode")
}
testWeb(t, true)
}
// Basic integration test for godoc HTTP interface.
func testWeb(t *testing.T, withIndex bool) {
if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; fails to start up quickly enough")
}
bin, cleanup := buildGodoc(t)
defer cleanup()
addr := serverAddress(t)
args := []string{fmt.Sprintf("-http=%s", addr)}
if withIndex {
args = append(args, "-index", "-index_interval=-1s")
}
cmd := exec.Command(bin, args...)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
cmd.Args[0] = "godoc"
// Set GOPATH variable to non-existing path
// and GOPROXY=off to disable module fetches.
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
// (We don't want the indexer looking at the local workspace during tests.)
cmd.Env = append(os.Environ(),
"GOPATH=does_not_exist",
"GOPROXY=off",
"GO111MODULE=off")
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start godoc: %s", err)
}
defer killAndWait(cmd)
if withIndex {
waitForSearchReady(t, addr)
} else {
waitForServerReady(t, addr)
waitUntilScanComplete(t, addr)
}
tests := []struct {
path string
contains []string // substring
match []string // regexp
notContains []string
needIndex bool
releaseTag string // optional release tag that must be in go/build.ReleaseTags
}{
{
path: "/",
contains: []string{"Go is an open source programming language"},
},
{
path: "/pkg/fmt/",
contains: []string{"Package fmt implements formatted I/O"},
},
{
path: "/src/fmt/",
contains: []string{"scan_test.go"},
},
{
path: "/src/fmt/print.go",
contains: []string{"// Println formats using"},
},
{
path: "/pkg",
contains: []string{
"Standard library",
"Package fmt implements formatted I/O",
},
notContains: []string{
"internal/syscall",
"cmd/gc",
},
},
{
path: "/pkg/?m=all",
contains: []string{
"Standard library",
"Package fmt implements formatted I/O",
"internal/syscall/?m=all",
},
notContains: []string{
"cmd/gc",
},
},
{
path: "/search?q=ListenAndServe",
contains: []string{
"/src",
},
notContains: []string{
"/pkg/bootstrap",
},
needIndex: true,
},
{
path: "/pkg/strings/",
contains: []string{
`href="/src/strings/strings.go"`,
},
},
{
path: "/cmd/compile/internal/amd64/",
contains: []string{
`href="/src/cmd/compile/internal/amd64/ssa.go"`,
},
},
{
path: "/pkg/math/bits/",
contains: []string{
`Added in Go 1.9`,
},
},
{
path: "/pkg/net/",
contains: []string{
`// IPv6 scoped addressing zone; added in Go 1.1`,
},
},
{
path: "/pkg/net/http/httptrace/",
match: []string{
`Got1xxResponse.*// Go 1\.11`,
},
releaseTag: "go1.11",
},
// Verify we don't add version info to a struct field added the same time
// as the struct itself:
{
path: "/pkg/net/http/httptrace/",
match: []string{
`(?m)GotFirstResponseByte func\(\)\s*$`,
},
},
// Remove trailing periods before adding semicolons:
{
path: "/pkg/database/sql/",
contains: []string{
"The number of connections currently in use; added in Go 1.11",
"The number of idle connections; added in Go 1.11",
},
releaseTag: "go1.11",
},
}
for _, test := range tests {
if test.needIndex && !withIndex {
continue
}
url := fmt.Sprintf("http://%s%s", addr, test.path)
resp, err := http.Get(url)
if err != nil {
t.Errorf("GET %s failed: %s", url, err)
continue
}
body, err := ioutil.ReadAll(resp.Body)
strBody := string(body)
resp.Body.Close()
if err != nil {
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
}
isErr := false
for _, substr := range test.contains {
if test.releaseTag != "" && !hasTag(test.releaseTag) {
continue
}
if !bytes.Contains(body, []byte(substr)) {
t.Errorf("GET %s: wanted substring %q in body", url, substr)
isErr = true
}
}
for _, re := range test.match {
if test.releaseTag != "" && !hasTag(test.releaseTag) {
continue
}
if ok, err := regexp.MatchString(re, strBody); !ok || err != nil {
if err != nil {
t.Fatalf("Bad regexp %q: %v", re, err)
} }
t.Errorf("GET %s: wanted to match %s in body", url, re) t.Errorf("Did not match /%v/", pat)
isErr = true
} }
} }
for _, substr := range test.notContains {
if bytes.Contains(body, []byte(substr)) {
t.Errorf("GET %s: didn't want substring %q in body", url, substr)
isErr = true
}
}
if isErr {
t.Errorf("GET %s: got:\n%s", url, body)
}
}
}
// Basic integration test for godoc -analysis=type (via HTTP interface).
func TestTypeAnalysis(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("skipping test on plan9 (issue #11974)") // see comment re: Plan 9 below
}
// Write a fake GOROOT/GOPATH.
tmpdir, err := ioutil.TempDir("", "godoc-analysis")
if err != nil {
t.Fatalf("ioutil.TempDir failed: %s", err)
}
defer os.RemoveAll(tmpdir)
for _, f := range []struct{ file, content string }{
{"goroot/src/lib/lib.go", `
package lib
type T struct{}
const C = 3
var V T
func (T) F() int { return C }
`},
{"gopath/src/app/main.go", `
package main
import "lib"
func main() { print(lib.V) }
`},
} {
file := filepath.Join(tmpdir, f.file)
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
t.Fatalf("MkdirAll(%s) failed: %s", filepath.Dir(file), err)
}
if err := ioutil.WriteFile(file, []byte(f.content), 0644); err != nil {
t.Fatal(err)
}
}
// Start the server.
bin, cleanup := buildGodoc(t)
defer cleanup()
addr := serverAddress(t)
cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-analysis=type")
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", filepath.Join(tmpdir, "goroot")))
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath")))
cmd.Env = append(cmd.Env, "GO111MODULE=off")
cmd.Env = append(cmd.Env, "GOPROXY=off")
cmd.Stdout = os.Stderr
stderr, err := cmd.StderrPipe()
if err != nil {
t.Fatal(err)
}
cmd.Args[0] = "godoc"
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start godoc: %s", err)
}
defer killAndWait(cmd)
waitForServerReady(t, addr)
// Wait for type analysis to complete.
reader := bufio.NewReader(stderr)
for {
s, err := reader.ReadString('\n') // on Plan 9 this fails
if err != nil {
t.Fatal(err)
}
fmt.Fprint(os.Stderr, s)
if strings.Contains(s, "Type analysis complete.") {
break
}
}
go io.Copy(os.Stderr, reader)
t0 := time.Now()
// Make an HTTP request and check for a regular expression match.
// The patterns are very crude checks that basic type information
// has been annotated onto the source view.
tryagain:
for _, test := range []struct{ url, pattern string }{
{"/src/lib/lib.go", "L2.*package .*Package docs for lib.*/lib"},
{"/src/lib/lib.go", "L3.*type .*type info for T.*struct"},
{"/src/lib/lib.go", "L5.*var V .*type T struct"},
{"/src/lib/lib.go", "L6.*func .*type T struct.*T.*return .*const C untyped int.*C"},
{"/src/app/main.go", "L2.*package .*Package docs for app"},
{"/src/app/main.go", "L3.*import .*Package docs for lib.*lib"},
{"/src/app/main.go", "L4.*func main.*package lib.*lib.*var lib.V lib.T.*V"},
} {
url := fmt.Sprintf("http://%s%s", addr, test.url)
resp, err := http.Get(url)
if err != nil {
t.Errorf("GET %s failed: %s", url, err)
continue
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
continue
}
if !bytes.Contains(body, []byte("Static analysis features")) {
// Type analysis results usually become available within
// ~4ms after godoc startup (for this input on my machine).
if elapsed := time.Since(t0); elapsed > 500*time.Millisecond {
t.Fatalf("type analysis results still unavailable after %s", elapsed)
}
time.Sleep(10 * time.Millisecond)
goto tryagain
}
match, err := regexp.Match(test.pattern, body)
if err != nil {
t.Errorf("regexp.Match(%q) failed: %s", test.pattern, err)
continue
}
if !match {
// This is a really ugly failure message.
t.Errorf("GET %s: body doesn't match %q, got:\n%s",
url, test.pattern, string(body))
}
} }
} }

View File

@ -1,74 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"os"
"path/filepath"
"runtime"
)
// Copies of functions from src/cmd/go/internal/cfg/cfg.go for
// finding the GOROOT.
// Keep them in sync until support is moved to a common place, if ever.
func findGOROOT() string {
if env := os.Getenv("GOROOT"); env != "" {
return filepath.Clean(env)
}
def := filepath.Clean(runtime.GOROOT())
if runtime.Compiler == "gccgo" {
// gccgo has no real GOROOT, and it certainly doesn't
// depend on the executable's location.
return def
}
exe, err := os.Executable()
if err == nil {
exe, err = filepath.Abs(exe)
if err == nil {
if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
// If def (runtime.GOROOT()) and dir are the same
// directory, prefer the spelling used in def.
if isSameDir(def, dir) {
return def
}
return dir
}
exe, err = filepath.EvalSymlinks(exe)
if err == nil {
if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
if isSameDir(def, dir) {
return def
}
return dir
}
}
}
}
return def
}
// isGOROOT reports whether path looks like a GOROOT.
//
// It does this by looking for the path/pkg/tool directory,
// which is necessary for useful operation of the cmd/go tool,
// and is not typically present in a GOPATH.
func isGOROOT(path string) bool {
stat, err := os.Stat(filepath.Join(path, "pkg", "tool"))
if err != nil {
return false
}
return stat.IsDir()
}
// isSameDir reports whether dir1 and dir2 are the same directory.
func isSameDir(dir1, dir2 string) bool {
if dir1 == dir2 {
return true
}
info1, err1 := os.Stat(dir1)
info2, err2 := os.Stat(dir2)
return err1 == nil && err2 == nil && os.SameFile(info1, info2)
}

View File

@ -13,17 +13,13 @@
package main package main
import ( import (
"encoding/json"
"go/format"
"log" "log"
"net/http" "net/http"
"strings"
"text/template" "text/template"
"golang.org/x/tools/godoc" "code.google.com/p/go.tools/godoc"
"golang.org/x/tools/godoc/golangorgenv" "code.google.com/p/go.tools/godoc/redirect"
"golang.org/x/tools/godoc/redirect" "code.google.com/p/go.tools/godoc/vfs"
"golang.org/x/tools/godoc/vfs"
) )
var ( var (
@ -31,65 +27,15 @@ var (
fs = vfs.NameSpace{} fs = vfs.NameSpace{}
) )
// hostEnforcerHandler redirects requests to "http://foo.golang.org/bar" func registerHandlers(pres *godoc.Presentation) {
// to "https://golang.org/bar".
// It permits requests to the host "godoc-test.golang.org" for testing and
// golang.google.cn for Chinese users.
type hostEnforcerHandler struct {
h http.Handler
}
func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !golangorgenv.EnforceHosts() {
h.h.ServeHTTP(w, r)
return
}
if !h.isHTTPS(r) || !h.validHost(r.Host) {
r.URL.Scheme = "https"
if h.validHost(r.Host) {
r.URL.Host = r.Host
} else {
r.URL.Host = "golang.org"
}
http.Redirect(w, r, r.URL.String(), http.StatusFound)
return
}
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
h.h.ServeHTTP(w, r)
}
func (h hostEnforcerHandler) isHTTPS(r *http.Request) bool {
return r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
}
func (h hostEnforcerHandler) validHost(host string) bool {
switch strings.ToLower(host) {
case "golang.org", "golang.google.cn":
return true
}
if strings.HasSuffix(host, "-dot-golang-org.appspot.com") {
// staging/test
return true
}
return false
}
func registerHandlers(pres *godoc.Presentation) *http.ServeMux {
if pres == nil { if pres == nil {
panic("nil Presentation") panic("nil Presentation")
} }
mux := http.NewServeMux() http.HandleFunc("/doc/codewalk/", codewalk)
mux.HandleFunc("/doc/codewalk/", codewalk) http.Handle("/doc/play/", pres.FileServer())
mux.Handle("/doc/play/", pres.FileServer()) http.Handle("/robots.txt", pres.FileServer())
mux.Handle("/robots.txt", pres.FileServer()) http.Handle("/", pres)
mux.Handle("/", pres) redirect.Register(nil)
mux.Handle("/pkg/C/", redirect.Handler("/cmd/cgo/"))
mux.HandleFunc("/fmt", fmtHandler)
redirect.Register(mux)
http.Handle("/", hostEnforcerHandler{mux})
return mux
} }
func readTemplate(name string) *template.Template { func readTemplate(name string) *template.Template {
@ -112,40 +58,19 @@ func readTemplate(name string) *template.Template {
return t return t
} }
func readTemplates(p *godoc.Presentation) { func readTemplates(p *godoc.Presentation, html bool) {
codewalkHTML = readTemplate("codewalk.html") p.PackageText = readTemplate("package.txt")
codewalkdirHTML = readTemplate("codewalkdir.html") p.SearchText = readTemplate("search.txt")
p.CallGraphHTML = readTemplate("callgraph.html")
p.DirlistHTML = readTemplate("dirlist.html")
p.ErrorHTML = readTemplate("error.html")
p.ExampleHTML = readTemplate("example.html")
p.GodocHTML = readTemplate("godoc.html")
p.ImplementsHTML = readTemplate("implements.html")
p.MethodSetHTML = readTemplate("methodset.html")
p.PackageHTML = readTemplate("package.html")
p.PackageRootHTML = readTemplate("packageroot.html")
p.SearchHTML = readTemplate("search.html")
p.SearchDocHTML = readTemplate("searchdoc.html")
p.SearchCodeHTML = readTemplate("searchcode.html")
p.SearchTxtHTML = readTemplate("searchtxt.html")
p.SearchDescXML = readTemplate("opensearch.xml")
}
type fmtResponse struct { if html {
Body string codewalkHTML = readTemplate("codewalk.html")
Error string codewalkdirHTML = readTemplate("codewalkdir.html")
} p.DirlistHTML = readTemplate("dirlist.html")
p.ErrorHTML = readTemplate("error.html")
// fmtHandler takes a Go program in its "body" form value, formats it with p.ExampleHTML = readTemplate("example.html")
// standard gofmt formatting, and writes a fmtResponse as a JSON object. p.GodocHTML = readTemplate("godoc.html")
func fmtHandler(w http.ResponseWriter, r *http.Request) { p.PackageHTML = readTemplate("package.html")
resp := new(fmtResponse) p.SearchHTML = readTemplate("search.html")
body, err := format.Source([]byte(r.FormValue("body"))) p.SearchDescXML = readTemplate("opensearch.xml")
if err != nil {
resp.Error = err.Error()
} else {
resp.Body = string(body)
} }
w.Header().Set("Content-type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(resp)
} }

View File

@ -1,11 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import "strings"
func indexDirectoryDefault(dir string) bool {
return dir != "/pkg" && !strings.HasPrefix(dir, "/pkg/")
}

View File

@ -14,6 +14,16 @@
// (idea is if you say import "compress/zlib", you go to // (idea is if you say import "compress/zlib", you go to
// http://godoc/pkg/compress/zlib) // http://godoc/pkg/compress/zlib)
// //
// Command-line interface:
//
// godoc packagepath [name ...]
//
// godoc compress/zlib
// - prints doc for package compress/zlib
// godoc crypto/block Cipher NewCMAC
// - prints doc for Cipher and NewCMAC in package crypto/block
// +build !appengine
package main package main
@ -23,27 +33,32 @@ import (
_ "expvar" // to serve /debug/vars _ "expvar" // to serve /debug/vars
"flag" "flag"
"fmt" "fmt"
"go/ast"
"go/build" "go/build"
"go/printer"
"log" "log"
"net/http" "net/http"
"net/http/httptest"
_ "net/http/pprof" // to serve /debug/pprof/* _ "net/http/pprof" // to serve /debug/pprof/*
"net/url" "net/url"
"os" "os"
pathpkg "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"strings" "strings"
"golang.org/x/tools/godoc" "code.google.com/p/go.tools/godoc"
"golang.org/x/tools/godoc/analysis" "code.google.com/p/go.tools/godoc/static"
"golang.org/x/tools/godoc/static" "code.google.com/p/go.tools/godoc/vfs"
"golang.org/x/tools/godoc/vfs" "code.google.com/p/go.tools/godoc/vfs/mapfs"
"golang.org/x/tools/godoc/vfs/gatefs" "code.google.com/p/go.tools/godoc/vfs/zipfs"
"golang.org/x/tools/godoc/vfs/mapfs"
"golang.org/x/tools/godoc/vfs/zipfs"
) )
const defaultAddr = "localhost:6060" // default webserver address const (
defaultAddr = ":6060" // default webserver address
toolsPath = "code.google.com/p/go.tools/cmd/"
)
var ( var (
// file system to serve // file system to serve
@ -53,30 +68,36 @@ var (
// file-based index // file-based index
writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files") writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files")
analysisFlag = flag.String("analysis", "", `comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html`)
// network // network
httpAddr = flag.String("http", defaultAddr, "HTTP service address") httpAddr = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')")
serverAddr = flag.String("server", "", "webserver address for command line searches")
// layout control // layout control
html = flag.Bool("html", false, "print HTML in command-line mode")
srcMode = flag.Bool("src", false, "print (exported) source in command-line mode")
urlFlag = flag.String("url", "", "print HTML for named URL") urlFlag = flag.String("url", "", "print HTML for named URL")
// command-line searches
query = flag.Bool("q", false, "arguments are considered search queries")
verbose = flag.Bool("v", false, "verbose mode") verbose = flag.Bool("v", false, "verbose mode")
// file system roots // file system roots
// TODO(gri) consider the invariant that goroot always end in '/' // TODO(gri) consider the invariant that goroot always end in '/'
goroot = flag.String("goroot", findGOROOT(), "Go root directory") goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
// layout control // layout control
tabWidth = flag.Int("tabwidth", 4, "tab width")
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings") showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
templateDir = flag.String("templates", "", "load templates/JS/CSS from disk in this directory") templateDir = flag.String("templates", "", "directory containing alternate template files")
showPlayground = flag.Bool("play", false, "enable playground") showPlayground = flag.Bool("play", false, "enable playground in web interface")
showExamples = flag.Bool("ex", false, "show examples in command line mode")
declLinks = flag.Bool("links", true, "link identifiers to their declarations") declLinks = flag.Bool("links", true, "link identifiers to their declarations")
// search index // search index
indexEnabled = flag.Bool("index", false, "enable search index") indexEnabled = flag.Bool("index", false, "enable search index")
indexFiles = flag.String("index_files", "", "glob pattern specifying index files; if not empty, the index is read from these files in sorted order") indexFiles = flag.String("index_files", "", "glob pattern specifying index files;"+
indexInterval = flag.Duration("index_interval", 0, "interval of indexing; 0 for default (5m), negative to only index once at startup") "if not empty, the index is read from these files in sorted order")
maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown") maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle") indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
@ -84,19 +105,10 @@ var (
notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show") notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show")
) )
// An httpResponseRecorder is an http.ResponseWriter
type httpResponseRecorder struct {
body *bytes.Buffer
header http.Header
code int
}
func (w *httpResponseRecorder) Header() http.Header { return w.header }
func (w *httpResponseRecorder) Write(b []byte) (int, error) { return w.body.Write(b) }
func (w *httpResponseRecorder) WriteHeader(code int) { w.code = code }
func usage() { func usage() {
fmt.Fprintf(os.Stderr, "usage: godoc -http="+defaultAddr+"\n") fmt.Fprintf(os.Stderr,
"usage: godoc package [name ...]\n"+
" godoc -http="+defaultAddr+"\n")
flag.PrintDefaults() flag.PrintDefaults()
os.Exit(2) os.Exit(2)
} }
@ -108,6 +120,32 @@ func loggingHandler(h http.Handler) http.Handler {
}) })
} }
// Does s look like a regular expression?
func isRegexp(s string) bool {
return strings.IndexAny(s, ".(|)*+?^$[]") >= 0
}
// Make a regular expression of the form
// names[0]|names[1]|...names[len(names)-1].
// Returns nil if the regular expression is illegal.
func makeRx(names []string) (rx *regexp.Regexp) {
if len(names) > 0 {
s := ""
for i, name := range names {
if i > 0 {
s += "|"
}
if isRegexp(name) {
s += name
} else {
s += "^" + name + "$" // must match exactly
}
}
rx, _ = regexp.Compile(s) // rx is nil if there's a compilation error
}
return
}
func handleURLFlag() { func handleURLFlag() {
// Try up to 10 fetches, following redirects. // Try up to 10 fetches, following redirects.
urlstr := *urlFlag urlstr := *urlFlag
@ -123,64 +161,42 @@ func handleURLFlag() {
// Invoke default HTTP handler to serve request // Invoke default HTTP handler to serve request
// to our buffering httpWriter. // to our buffering httpWriter.
w := &httpResponseRecorder{code: 200, header: make(http.Header), body: new(bytes.Buffer)} w := httptest.NewRecorder()
http.DefaultServeMux.ServeHTTP(w, req) http.DefaultServeMux.ServeHTTP(w, req)
// Return data, error, or follow redirect. // Return data, error, or follow redirect.
switch w.code { switch w.Code {
case 200: // ok case 200: // ok
os.Stdout.Write(w.body.Bytes()) os.Stdout.Write(w.Body.Bytes())
return return
case 301, 302, 303, 307: // redirect case 301, 302, 303, 307: // redirect
redirect := w.header.Get("Location") redirect := w.HeaderMap.Get("Location")
if redirect == "" { if redirect == "" {
log.Fatalf("HTTP %d without Location header", w.code) log.Fatalf("HTTP %d without Location header", w.Code)
} }
urlstr = redirect urlstr = redirect
default: default:
log.Fatalf("HTTP error %d", w.code) log.Fatalf("HTTP error %d", w.Code)
} }
} }
log.Fatalf("too many redirects") log.Fatalf("too many redirects")
} }
func initCorpus(corpus *godoc.Corpus) {
err := corpus.Init()
if err != nil {
log.Fatal(err)
}
}
func main() { func main() {
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
if certInit != nil {
certInit()
}
playEnabled = *showPlayground playEnabled = *showPlayground
// Check usage. // Check usage: either server and no args, command line and args, or index creation mode
if flag.NArg() > 0 { if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex {
fmt.Fprintln(os.Stderr, `Unexpected arguments. Use "go doc" for command-line help output instead. For example, "go doc fmt.Printf".`)
usage() usage()
} }
if *httpAddr == "" && *urlFlag == "" && !*writeIndex {
fmt.Fprintln(os.Stderr, "At least one of -http, -url, or -write_index must be set to a non-zero value.")
usage()
}
// Set the resolved goroot.
vfs.GOROOT = *goroot
fsGate := make(chan bool, 20)
// Determine file system to use. // Determine file system to use.
if *zipfile == "" { if *zipfile == "" {
// use file system of underlying OS // use file system of underlying OS
rootfs := gatefs.New(vfs.OS(*goroot), fsGate) fs.Bind("/", vfs.OS(*goroot), "/", vfs.BindReplace)
fs.Bind("/", rootfs, "/", vfs.BindReplace)
} else { } else {
// use file system specified via .zip file (path separator must be '/') // use file system specified via .zip file (path separator must be '/')
rc, err := zip.OpenReader(*zipfile) rc, err := zip.OpenReader(*zipfile)
@ -198,55 +214,36 @@ func main() {
// Bind $GOPATH trees into Go root. // Bind $GOPATH trees into Go root.
for _, p := range filepath.SplitList(build.Default.GOPATH) { for _, p := range filepath.SplitList(build.Default.GOPATH) {
fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter) fs.Bind("/src/pkg", vfs.OS(p), "/src", vfs.BindAfter)
} }
var typeAnalysis, pointerAnalysis bool httpMode := *httpAddr != ""
if *analysisFlag != "" {
for _, a := range strings.Split(*analysisFlag, ",") {
switch a {
case "type":
typeAnalysis = true
case "pointer":
pointerAnalysis = true
default:
log.Fatalf("unknown analysis: %s", a)
}
}
}
corpus := godoc.NewCorpus(fs) corpus := godoc.NewCorpus(fs)
corpus.Verbose = *verbose corpus.Verbose = *verbose
corpus.MaxResults = *maxResults corpus.IndexEnabled = *indexEnabled && httpMode
corpus.IndexEnabled = *indexEnabled
if *maxResults == 0 {
corpus.IndexFullText = false
}
corpus.IndexFiles = *indexFiles corpus.IndexFiles = *indexFiles
corpus.IndexDirectory = indexDirectoryDefault
corpus.IndexThrottle = *indexThrottle corpus.IndexThrottle = *indexThrottle
corpus.IndexInterval = *indexInterval if *writeIndex {
if *writeIndex || *urlFlag != "" {
corpus.IndexThrottle = 1.0 corpus.IndexThrottle = 1.0
corpus.IndexEnabled = true
initCorpus(corpus)
} else {
go initCorpus(corpus)
} }
if *writeIndex || httpMode || *urlFlag != "" {
// Initialize the version info before readTemplates, which saves if err := corpus.Init(); err != nil {
// the map value in a method value. log.Fatal(err)
corpus.InitVersionInfo() }
}
pres = godoc.NewPresentation(corpus) pres = godoc.NewPresentation(corpus)
pres.TabWidth = *tabWidth
pres.ShowTimestamps = *showTimestamps pres.ShowTimestamps = *showTimestamps
pres.ShowPlayground = *showPlayground pres.ShowPlayground = *showPlayground
pres.ShowExamples = *showExamples
pres.DeclLinks = *declLinks pres.DeclLinks = *declLinks
if *notesRx != "" { if *notesRx != "" {
pres.NotesRx = regexp.MustCompile(*notesRx) pres.NotesRx = regexp.MustCompile(*notesRx)
} }
readTemplates(pres) readTemplates(pres, httpMode || *urlFlag != "" || *html)
registerHandlers(pres) registerHandlers(pres)
if *writeIndex { if *writeIndex {
@ -266,7 +263,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
index, _ := corpus.CurrentIndex() index, _ := corpus.CurrentIndex()
_, err = index.WriteTo(f) err = index.Write(f)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -281,58 +278,186 @@ func main() {
return return
} }
var handler http.Handler = http.DefaultServeMux if httpMode {
if *verbose { // HTTP server mode.
log.Printf("Go Documentation Server") var handler http.Handler = http.DefaultServeMux
log.Printf("version = %s", runtime.Version()) if *verbose {
log.Printf("address = %s", *httpAddr) log.Printf("Go Documentation Server")
log.Printf("goroot = %s", *goroot) log.Printf("version = %s", runtime.Version())
switch { log.Printf("address = %s", *httpAddr)
case !*indexEnabled: log.Printf("goroot = %s", *goroot)
log.Print("search index disabled") log.Printf("tabwidth = %d", *tabWidth)
case *maxResults > 0: switch {
log.Printf("full text index enabled (maxresults = %d)", *maxResults) case !*indexEnabled:
default: log.Print("search index disabled")
log.Print("identifier search index enabled") case *maxResults > 0:
} log.Printf("full text index enabled (maxresults = %d)", *maxResults)
fs.Fprint(os.Stderr) default:
handler = loggingHandler(handler) log.Print("identifier search index enabled")
}
// Initialize search index.
if *indexEnabled {
go corpus.RunIndexer()
}
// Start type/pointer analysis.
if typeAnalysis || pointerAnalysis {
go analysis.Run(pointerAnalysis, &corpus.Analysis)
}
if runHTTPS != nil {
go func() {
if err := runHTTPS(handler); err != nil {
log.Fatalf("ListenAndServe TLS: %v", err)
} }
}() fs.Fprint(os.Stderr)
handler = loggingHandler(handler)
}
// Initialize search index.
if *indexEnabled {
go corpus.RunIndexer()
}
// Start http server.
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
}
return
} }
// Start http server. packageText := pres.PackageText
if *verbose {
log.Println("starting HTTP server") // Command line mode.
if *html {
packageText = pres.PackageHTML
} }
if wrapHTTPMux != nil {
handler = wrapHTTPMux(handler) if *query {
handleRemoteSearch()
return
} }
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err) // Determine paths.
//
// If we are passed an operating system path like . or ./foo or /foo/bar or c:\mysrc,
// we need to map that path somewhere in the fs name space so that routines
// like getPageInfo will see it. We use the arbitrarily-chosen virtual path "/target"
// for this. That is, if we get passed a directory like the above, we map that
// directory so that getPageInfo sees it as /target.
const target = "/target"
const cmdPrefix = "cmd/"
path := flag.Arg(0)
var forceCmd bool
var abspath, relpath string
if filepath.IsAbs(path) {
fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
abspath = target
} else if build.IsLocalImport(path) {
cwd, _ := os.Getwd() // ignore errors
path = filepath.Join(cwd, path)
fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
abspath = target
} else if strings.HasPrefix(path, cmdPrefix) {
path = strings.TrimPrefix(path, cmdPrefix)
forceCmd = true
} else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
fs.Bind(target, vfs.OS(bp.Dir), "/", vfs.BindReplace)
abspath = target
relpath = bp.ImportPath
} else {
abspath = pathpkg.Join(pres.PkgFSRoot(), path)
}
if relpath == "" {
relpath = abspath
}
var mode godoc.PageInfoMode
if relpath == "builtin" {
// the fake built-in package contains unexported identifiers
mode = godoc.NoFiltering | godoc.NoFactoryFuncs
}
if *srcMode {
// only filter exports if we don't have explicit command-line filter arguments
if flag.NArg() > 1 {
mode |= godoc.NoFiltering
}
mode |= godoc.ShowSource
}
// First, try as package unless forced as command.
var info *godoc.PageInfo
if !forceCmd {
info = pres.GetPkgPageInfo(abspath, relpath, mode)
}
// Second, try as command (if the path is not absolute).
var cinfo *godoc.PageInfo
if !filepath.IsAbs(path) {
// First try go.tools/cmd.
abspath = pathpkg.Join(pres.PkgFSRoot(), toolsPath+path)
cinfo = pres.GetCmdPageInfo(abspath, relpath, mode)
if cinfo.IsEmpty() {
// Then try $GOROOT/cmd.
abspath = pathpkg.Join(pres.CmdFSRoot(), path)
cinfo = pres.GetCmdPageInfo(abspath, relpath, mode)
}
}
// determine what to use
if info == nil || info.IsEmpty() {
if cinfo != nil && !cinfo.IsEmpty() {
// only cinfo exists - switch to cinfo
info = cinfo
}
} else if cinfo != nil && !cinfo.IsEmpty() {
// both info and cinfo exist - use cinfo if info
// contains only subdirectory information
if info.PAst == nil && info.PDoc == nil {
info = cinfo
} else {
fmt.Printf("use 'godoc %s%s' for documentation on the %s command \n\n", cmdPrefix, relpath, relpath)
}
}
if info == nil {
log.Fatalf("%s: no such directory or package", flag.Arg(0))
}
if info.Err != nil {
log.Fatalf("%v", info.Err)
}
if info.PDoc != nil && info.PDoc.ImportPath == target {
// Replace virtual /target with actual argument from command line.
info.PDoc.ImportPath = flag.Arg(0)
}
// If we have more than one argument, use the remaining arguments for filtering.
if flag.NArg() > 1 {
args := flag.Args()[1:]
rx := makeRx(args)
if rx == nil {
log.Fatalf("illegal regular expression from %v", args)
}
filter := func(s string) bool { return rx.MatchString(s) }
switch {
case info.PAst != nil:
cmap := ast.NewCommentMap(info.FSet, info.PAst, info.PAst.Comments)
ast.FilterFile(info.PAst, filter)
// Special case: Don't use templates for printing
// so we only get the filtered declarations without
// package clause or extra whitespace.
for i, d := range info.PAst.Decls {
// determine the comments associated with d only
comments := cmap.Filter(d).Comments()
cn := &printer.CommentedNode{Node: d, Comments: comments}
if i > 0 {
fmt.Println()
}
if *html {
var buf bytes.Buffer
pres.WriteNode(&buf, info.FSet, cn)
godoc.FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil)
} else {
pres.WriteNode(os.Stdout, info.FSet, cn)
}
fmt.Println()
}
return
case info.PDoc != nil:
info.PDoc.Filter(filter)
}
}
if err := packageText.Execute(os.Stdout, info); err != nil {
log.Printf("packageText.Execute: %s", err)
} }
} }
// Hooks that are set non-nil in autocert.go if the "autocert" build tag
// is used.
var (
certInit func()
runHTTPS func(http.Handler) error
wrapHTTPMux func(http.Handler) http.Handler
)

View File

@ -4,6 +4,41 @@
package main package main
// This package registers "/compile" and "/share" handlers import (
// that redirect to the golang.org playground. "encoding/json"
import _ "golang.org/x/tools/playground" "fmt"
"go/format"
"net/http"
// This package registers "/compile" and "/share" handlers
// that redirect to the golang.org playground.
_ "code.google.com/p/go.tools/playground"
)
func init() {
http.HandleFunc("/fmt", fmtHandler)
}
type fmtResponse struct {
Body string
Error string
}
// fmtHandler takes a Go program in its "body" form value, formats it with
// standard gofmt formatting, and writes a fmtResponse as a JSON object.
func fmtHandler(w http.ResponseWriter, r *http.Request) {
resp := new(fmtResponse)
body, err := format.Source([]byte(r.FormValue("body")))
if err != nil {
resp.Error = err.Error()
} else {
resp.Body = string(body)
}
json.NewEncoder(w).Encode(resp)
}
// disabledHandler serves a 501 "Not Implemented" response.
func disabledHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
fmt.Fprint(w, "This functionality is not available via local godoc.")
}

Some files were not shown because too many files have changed in this diff Show More