Compare commits

..

5 Commits

Author SHA1 Message Date
Andrew Bonventre 3b2b05e9f5 [release-branch.go1.8] godoc: add GoogleCN property to pages
Use that property to determine whether to show share functionality
or link to sites that are blocked in mainland China.

This change requires https://go-review.googlesource.com/c/52872

Change-Id: I47327f9dbd2624206564fa99eb1cc6a10b4f46db
Reviewed-on: https://go-review.googlesource.com/52873
Reviewed-by: Chris Broadfoot <cbro@golang.org>
Reviewed-on: https://go-review.googlesource.com/81075
Reviewed-by: Russ Cox <rsc@golang.org>
2017-11-30 21:11:23 +00:00
Andrew Bonventre 77187f3c2d [release-branch.go1.8] x/tools/godoc: fix redirect to Gerrit
Redirects to /r/NNNN broke due to crbug.com/gerrit/6888.
Alternative URLs are available in the meantime: /NNNN and /c/NNNN.
This change uses the /NNNN format.

Fixes golang/go#21235

Change-Id: Ie30e01bedd7a8277aedd4070b5f82a754521ed03
Reviewed-on: https://go-review.googlesource.com/52150
Reviewed-by: Kevin Burke <kev@inburke.com>
Reviewed-on: https://go-review.googlesource.com/52170
Reviewed-by: Andrew Bonventre <andybons@golang.org>
2017-07-31 17:59:59 +00:00
Gerrit Code Review 5682db0e91 Merge "[release-branch.go1.8] all: merge master into release-branch.go1.8" into release-branch.go1.8 2017-01-26 20:51:04 +00:00
Chris Broadfoot 9f028c45f3 [release-branch.go1.8] all: merge master into release-branch.go1.8
f8ed2e40 cmd/guru: fix typo of 'hyphen' to rename to 'comma'
61efd711 godoc: fix quadratic and ASCII-only struct field linkification
721f2184 imports: remove unnecessary string conversion
fcfba28e go/internal/gccgoimporter: support for type aliases
add1aac0 static: don't use the jQuery func for looking up based on hash
608c3b09 present: fix typo
0d047c8d cmd/godex: handle printing of type aliases
de557280 go/gcimporter15: update import/export to handle type aliases
5d76f8ce static: detect platform to hide/show unix/windows instructions

Change-Id: If3753b030500781b5bf086e56485e75343744799
2017-01-26 10:47:28 -08:00
Chris Broadfoot 8187b5f3e6 [release-branch.go1.8] all: merge master into release-branch.go1.8
add1aac0 static: don't use the jQuery func for looking up based on hash
608c3b09 present: fix typo
0d047c8d cmd/godex: handle printing of type aliases
de557280 go/gcimporter15: update import/export to handle type aliases
5d76f8ce static: detect platform to hide/show unix/windows instructions

Change-Id: I327abdef58298a72d2c2cdcd24da6bfd096bd262
2017-01-19 12:44:55 -08:00
959 changed files with 86782 additions and 104846 deletions

View File

@ -4,15 +4,16 @@ Go is an open source project.
It is the work of hundreds of contributors. We appreciate your help! It is the work of hundreds of contributors. We appreciate your help!
## Filing issues ## Filing issues
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions: 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`)? 1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using? 2. What operating system and processor architecture are you using?
3. What did you do? 3. What did you do?
4. What did you expect to see? 4. What did you expect to see?
5. What did you see instead? 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. 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. The gophers there will answer or ask you to file an issue if you've tripped over a bug.
@ -22,5 +23,9 @@ The gophers there will answer or ask you to file an issue if you've tripped over
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
before sending patches. before sending patches.
**We do not accept GitHub pull requests**
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
Unless otherwise noted, the Go source files are distributed under Unless otherwise noted, the Go source files are distributed under
the BSD-style license found in the LICENSE file. the BSD-style license found in the LICENSE file.

10
README Normal file
View File

@ -0,0 +1,10 @@
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.
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

@ -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

@ -24,14 +24,7 @@ import (
"golang.org/x/tools/present" "golang.org/x/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)
}
}
}

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 +0,0 @@
testdata/out.got

View File

@ -88,7 +88,6 @@ import (
"go/build" "go/build"
"go/format" "go/format"
"go/parser" "go/parser"
"go/printer"
"go/token" "go/token"
"go/types" "go/types"
"io/ioutil" "io/ioutil"
@ -105,8 +104,8 @@ var (
outputFile = flag.String("o", "", "write output to `file` (default standard output)") outputFile = flag.String("o", "", "write output to `file` (default standard output)")
dstPath = flag.String("dst", "", "set destination import `path` (default taken from current directory)") 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)") 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)") prefix = flag.String("prefix", "", "set bundled identifier prefix to `p` (default source package name + \"_\")")
underscore = flag.Bool("underscore", false, "rewrite golang.org/x/* to internal/x/* imports; temporary workaround for golang.org/issue/16333") underscore = flag.Bool("underscore", false, "rewrite golang.org to golang_org in imports; temporary workaround for golang.org/issue/16333")
importMap = map[string]string{} importMap = map[string]string{}
) )
@ -200,11 +199,10 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
return nil, err return nil, err
} }
// Because there was a single Import call and Load succeeded, info := lprog.Package(src)
// InitialPackages is guaranteed to hold the sole requested package. if prefix == "" {
info := lprog.InitialPackages()[0] pkgName := info.Files[0].Name.Name
if strings.Contains(prefix, "&") { prefix = pkgName + "_"
prefix = strings.Replace(prefix, "&", info.Files[0].Name.Name, -1)
} }
objsToUpdate := make(map[types.Object]bool) objsToUpdate := make(map[types.Object]bool)
@ -238,7 +236,7 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
var out bytes.Buffer var out bytes.Buffer
fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.\n") fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle.\n")
if *outputFile != "" { if *outputFile != "" {
fmt.Fprintf(&out, "//go:generate bundle %s\n", strings.Join(os.Args[1:], " ")) fmt.Fprintf(&out, "//go:generate bundle %s\n", strings.Join(os.Args[1:], " "))
} else { } else {
@ -298,7 +296,7 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
pkgStd[spec] = true pkgStd[spec] = true
} else { } else {
if *underscore { if *underscore {
spec = strings.Replace(spec, "golang.org/x/", "internal/x/", 1) spec = strings.Replace(spec, "golang.org/", "golang_org/", 1)
} }
pkgExt[spec] = true pkgExt[spec] = true
} }
@ -348,41 +346,25 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
return true 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. // Pretty-print package-level declarations.
// but no package or import declarations. // but no package or import declarations.
//
// TODO(adonovan): this may cause loss of comments
// preceding or associated with the package or import
// declarations or not associated with any declaration.
// Check.
var buf bytes.Buffer var buf bytes.Buffer
for _, decl := range f.Decls { for _, decl := range f.Decls {
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
continue continue
} }
beg, end := sourceRange(decl)
printComments(&out, f.Comments, last, beg)
buf.Reset() buf.Reset()
format.Node(&buf, lprog.Fset, &printer.CommentedNode{Node: decl, Comments: f.Comments}) format.Node(&buf, lprog.Fset, decl)
// Remove each "@@@." in the output. // Remove each "@@@." in the output.
// TODO(adonovan): not hygienic. // TODO(adonovan): not hygienic.
out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1)) out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
last = printSameLineComment(&out, f.Comments, lprog.Fset, end)
out.WriteString("\n\n") out.WriteString("\n\n")
} }
printLastComments(&out, f.Comments, last)
} }
// Now format the entire thing. // Now format the entire thing.
@ -394,69 +376,6 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
return result, nil 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) type flagFunc func(string)
func (f flagFunc) Set(s string) error { func (f flagFunc) Set(s string) error {

View File

@ -2,8 +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.
// +build go1.9
package main package main
import ( import (

View File

@ -1,4 +1,4 @@
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT. // Code generated by golang.org/x/tools/cmd/bundle.
// $ bundle // $ bundle
// The package doc comment // The package doc comment
@ -23,38 +23,19 @@ func init() { prefixfoo() }
type prefixS struct { type prefixS struct {
prefixt prefixt
u int 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 // Function bar.
func prefixbar(s *prefixS) { fmt.Println(s.prefixt, s.u) }
type prefixt int // type1 type prefixt int
// const1 const prefixc = 1
const prefixc = 1 // const2
func prefixfoo() { func prefixfoo() {
fmt.Println(importdecl.F()) fmt.Println(importdecl.F())
} }
// zinit
const (
prefixz1 = iota // z1
prefixz2 // z2
) // zend
func prefixbaz() { func prefixbaz() {
renamedfmt.Println() renamedfmt.Println()
renamedfmt2.Println() renamedfmt2.Println()

View File

@ -1,6 +1,6 @@
package initial package initial
import "fmt" // this comment should not be visible import "fmt"
// init functions are not renamed // init functions are not renamed
func init() { foo() } func init() { foo() }
@ -9,19 +9,7 @@ func init() { foo() }
type S struct { type S struct {
t t
u int 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 // Function bar.
func bar(s *S) { fmt.Println(s.t, s.u) }

View File

@ -7,17 +7,10 @@ import (
"domain.name/importdecl" "domain.name/importdecl"
) )
type t int // type1 type t int
// const1 const c = 1
const c = 1 // const2
func foo() { func foo() {
fmt.Println(importdecl.F()) fmt.Println(importdecl.F())
} }
// zinit
const (
z1 = iota // z1
z2 // z2
) // zend

View File

@ -37,7 +37,7 @@ import (
"golang.org/x/tools/go/callgraph/cha" "golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/callgraph/rta" "golang.org/x/tools/go/callgraph/rta"
"golang.org/x/tools/go/callgraph/static" "golang.org/x/tools/go/callgraph/static"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/loader"
"golang.org/x/tools/go/pointer" "golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/ssa/ssautil"
@ -67,7 +67,7 @@ const Usage = `callgraph: display the the call graph of a Go program.
Usage: Usage:
callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] package... callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] <args>...
Flags: Flags:
@ -114,10 +114,12 @@ Flags:
Caller and Callee are *ssa.Function values, which print as Caller and Callee are *ssa.Function values, which print as
"(*sync/atomic.Mutex).Lock", but other attributes may be "(*sync/atomic.Mutex).Lock", but other attributes may be
derived from them, e.g. Caller.Pkg.Pkg.Path yields the derived from them, e.g. Caller.Pkg.Object.Path yields the
import path of the enclosing package. Consult the go/ssa import path of the enclosing package. Consult the go/ssa
API documentation for details. API documentation for details.
` + loader.FromArgsUsage + `
Examples: Examples:
Show the call graph of the trivial web server application: Show the call graph of the trivial web server application:
@ -126,7 +128,7 @@ Examples:
Same, but show only the packages of each function: Same, but show only the packages of each function:
callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \ callgraph -format '{{.Caller.Pkg.Object.Path}} -> {{.Callee.Pkg.Object.Path}}' \
$GOROOT/src/net/http/triv.go | sort | uniq $GOROOT/src/net/http/triv.go | sort | uniq
Show functions that make dynamic calls into the 'fmt' test package, Show functions that make dynamic calls into the 'fmt' test package,
@ -156,7 +158,7 @@ func init() {
func main() { func main() {
flag.Parse() flag.Parse()
if err := doCallgraph("", "", *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil { if err := doCallgraph(&build.Default, *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
fmt.Fprintf(os.Stderr, "callgraph: %s\n", err) fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
os.Exit(1) os.Exit(1)
} }
@ -164,30 +166,28 @@ func main() {
var stdout io.Writer = os.Stdout var stdout io.Writer = os.Stdout
func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) error { func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error {
conf := loader.Config{Build: ctxt}
if len(args) == 0 { if len(args) == 0 {
fmt.Fprintln(os.Stderr, Usage) fmt.Fprintln(os.Stderr, Usage)
return nil return nil
} }
cfg := &packages.Config{ // Use the initial packages from the command line.
Mode: packages.LoadAllSyntax, args, err := conf.FromArgs(args, tests)
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 { if err != nil {
return err return err
} }
if packages.PrintErrors(initial) > 0 {
return fmt.Errorf("packages contain errors") // Load, parse and type-check the whole program.
iprog, err := conf.Load()
if err != nil {
return err
} }
// Create and build SSA-form program representation. // Create and build SSA-form program representation.
prog, pkgs := ssautil.AllPackages(initial, 0) prog := ssautil.CreateProgram(iprog, 0)
prog.Build() prog.Build()
// -- call graph construction ------------------------------------------ // -- call graph construction ------------------------------------------
@ -221,7 +221,7 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
} }
} }
mains, err := mainPackages(pkgs) mains, err := mainPackages(prog, tests)
if err != nil { if err != nil {
return err return err
} }
@ -237,7 +237,7 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
cg = ptares.CallGraph cg = ptares.CallGraph
case "rta": case "rta":
mains, err := mainPackages(pkgs) mains, err := mainPackages(prog, tests)
if err != nil { if err != nil {
return err return err
} }
@ -305,13 +305,25 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
// mainPackages returns the main packages to analyze. // mainPackages returns the main packages to analyze.
// Each resulting package is named "main" and has a main function. // Each resulting package is named "main" and has a main function.
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) { func mainPackages(prog *ssa.Program, tests bool) ([]*ssa.Package, error) {
pkgs := prog.AllPackages() // TODO(adonovan): use only initial packages
// If tests, create a "testmain" package for each test.
var mains []*ssa.Package var mains []*ssa.Package
for _, p := range pkgs { if tests {
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil { for _, pkg := range pkgs {
mains = append(mains, p) if main := prog.CreateTestMainPackage(pkg); main != nil {
mains = append(mains, main)
}
} }
if mains == nil {
return nil, fmt.Errorf("no tests")
}
return mains, nil
} }
// Otherwise, use the main packages.
mains = append(mains, ssautil.MainPackages(pkgs)...)
if len(mains) == 0 { if len(mains) == 0 {
return nil, fmt.Errorf("no main packages") return nil, fmt.Errorf("no main packages")
} }

View File

@ -5,44 +5,31 @@
// No testdata on Android. // No testdata on Android.
// +build !android // +build !android
// +build go1.11
package main package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log" "go/build"
"os" "reflect"
"path/filepath" "sort"
"strings" "strings"
"testing" "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) { func TestCallgraph(t *testing.T) {
gopath, err := filepath.Abs("testdata") ctxt := build.Default // copy
if err != nil { ctxt.GOPATH = "testdata"
t.Fatal(err)
} const format = "{{.Caller}} --> {{.Callee}}"
for _, test := range []struct { for _, test := range []struct {
algo string algo, format string
tests bool tests bool
want []string want []string
}{ }{
{"rta", false, []string{ {"rta", format, false, []string{
// rta imprecisely shows cross product of {main,main2} x {C,D} // rta imprecisely shows cross product of {main,main2} x {C,D}
`pkg.main --> (pkg.C).f`, `pkg.main --> (pkg.C).f`,
`pkg.main --> (pkg.D).f`, `pkg.main --> (pkg.D).f`,
@ -50,7 +37,7 @@ func TestCallgraph(t *testing.T) {
`pkg.main2 --> (pkg.C).f`, `pkg.main2 --> (pkg.C).f`,
`pkg.main2 --> (pkg.D).f`, `pkg.main2 --> (pkg.D).f`,
}}, }},
{"pta", false, []string{ {"pta", format, false, []string{
// pta distinguishes main->C, main2->D. Also has a root node. // pta distinguishes main->C, main2->D. Also has a root node.
`<root> --> pkg.init`, `<root> --> pkg.init`,
`<root> --> pkg.main`, `<root> --> pkg.main`,
@ -58,42 +45,37 @@ func TestCallgraph(t *testing.T) {
`pkg.main --> pkg.main2`, `pkg.main --> pkg.main2`,
`pkg.main2 --> (pkg.D).f`, `pkg.main2 --> (pkg.D).f`,
}}, }},
// tests: both the package's main and the test's main are called. // tests: main is not called.
// The callgraph includes all the guts of the "testing" package. {"rta", format, true, []string{
{"rta", true, []string{ `pkg$testmain.init --> pkg.init`,
`pkg.test.main --> testing.MainStart`,
`testing.runExample --> pkg.Example`,
`pkg.Example --> (pkg.C).f`, `pkg.Example --> (pkg.C).f`,
`pkg.main --> (pkg.C).f`,
}}, }},
{"pta", true, []string{ {"pta", format, true, []string{
`<root> --> pkg.test.main`, `<root> --> pkg$testmain.init`,
`<root> --> pkg.main`, `<root> --> pkg.Example`,
`pkg.test.main --> testing.MainStart`, `pkg$testmain.init --> pkg.init`,
`testing.runExample --> pkg.Example`,
`pkg.Example --> (pkg.C).f`, `pkg.Example --> (pkg.C).f`,
`pkg.main --> (pkg.C).f`,
}}, }},
} { } {
const format = "{{.Caller}} --> {{.Callee}}"
stdout = new(bytes.Buffer) stdout = new(bytes.Buffer)
if err := doCallgraph("testdata/src", gopath, test.algo, format, test.tests, []string{"pkg"}); err != nil { if err := doCallgraph(&ctxt, test.algo, test.format, test.tests, []string{"pkg"}); err != nil {
t.Error(err) t.Error(err)
continue continue
} }
edges := make(map[string]bool) got := sortedLines(fmt.Sprint(stdout))
for _, line := range strings.Split(fmt.Sprint(stdout), "\n") { if !reflect.DeepEqual(got, test.want) {
edges[line] = true t.Errorf("callgraph(%q, %q, %t):\ngot:\n%s\nwant:\n%s",
} test.algo, test.format, test.tests,
for _, edge := range test.want { strings.Join(got, "\n"),
if !edges[edge] { strings.Join(test.want, "\n"))
t.Errorf("callgraph(%q, %t): missing edge: %s",
test.algo, test.tests, edge)
}
}
if t.Failed() {
t.Log("got:\n", stdout)
} }
} }
} }
func sortedLines(s string) []string {
s = strings.TrimSpace(s)
lines := strings.Split(s, "\n")
sort.Strings(lines)
return lines
}

View File

@ -1,10 +1,7 @@
package main package main
// An Example function must have an "Output:" comment for the go build // Don't import "testing", it adds a lot of callgraph edges.
// system to generate a call to it from the test main package.
func Example() { func Example() {
C(0).f() 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,83 +1,18 @@
// Copyright 2019 The Go Authors. All rights reserved. // The digraph command performs queries over unlabelled directed graphs
// Use of this source code is governed by a BSD-style // represented in text form. It is intended to integrate nicely with
// license that can be found in the LICENSE file. // typical UNIX command pipelines.
//
/* // Since directed graphs (import graphs, reference graphs, call graphs,
The digraph command performs queries over unlabelled directed graphs // etc) often arise during software tool development and debugging, this
represented in text form. It is intended to integrate nicely with // command is included in the go.tools repository.
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): // TODO(adonovan):
// - support input files other than stdin // - support input files other than stdin
// - support alternative formats (AT&T GraphViz, CSV, etc), // - suport alternative formats (AT&T GraphViz, CSV, etc),
// a comment syntax, etc. // a comment syntax, etc.
// - allow queries to nest, like Blaze query language. // - allow queries to nest, like Blaze query language.
//
package main // import "golang.org/x/tools/cmd/digraph"
import ( import (
"bufio" "bufio"
@ -93,41 +28,73 @@ import (
"unicode/utf8" "unicode/utf8"
) )
func usage() { const Usage = `digraph: queries over directed graphs in text form.
fmt.Fprintf(os.Stderr, `Usage: your-application | digraph [command]
The support commands are: Graph format:
nodes
the set of all nodes Each line contains zero or more words. Words are separated by
degree unquoted whitespace; words may contain Go-style double-quoted portions,
the in-degree and out-degree of each node allowing spaces and other characters to be expressed.
preds <node> ...
the set of immediate predecessors of the specified nodes Each field declares a node, and if there are more than one,
succs <node> ... an edge from the first to each subsequent one.
the set of immediate successors of the specified nodes The graph is provided on the standard input.
forward <node> ...
the set of nodes transitively reachable from the specified nodes For instance, the following (acyclic) graph specifies a partial order
reverse <node> ... among the subtasks of getting dressed:
the set of nodes that transitively reach the specified nodes
somepath <node> <node> % cat clothes.txt
the list of nodes on some arbitrary path from the first node to the second socks shoes
allpaths <node> <node> "boxer shorts" pants
the set of nodes on all paths from the first node to the second pants belt shoes
sccs shirt tie sweater
all strongly connected components (one per line) sweater jacket
scc <node> hat
the set of nodes nodes strongly connected to the specified one
`) The line "shirt tie sweater" indicates the two edges shirt -> tie and
os.Exit(2) shirt -> sweater, not shirt -> tie -> sweater.
}
Supported queries:
nodes
the set of all nodes
degree
the in-degree and out-degree of each node.
preds <label> ...
the set of immediate predecessors of the specified nodes
succs <label> ...
the set of immediate successors of the specified nodes
forward <label> ...
the set of nodes transitively reachable from the specified nodes
reverse <label> ...
the set of nodes that transitively reach the specified nodes
somepath <label> <label>
the list of nodes on some arbitrary path from the first node to the second
allpaths <label> <label>
the set of nodes on all paths from the first node to the second
sccs
all strongly connected components (one per line)
scc <label>
the set of nodes nodes strongly connected to the specified one
Example usage:
Show the transitive closure of imports of the digraph tool itself:
% go list -f '{{.ImportPath}}{{.Imports}}' ... | tr '[]' ' ' |
digraph forward golang.org/x/tools/cmd/digraph
Show which clothes (see above) must be donned before a jacket:
% digraph reverse jacket <clothes.txt
`
func main() { func main() {
flag.Usage = usage
flag.Parse() flag.Parse()
args := flag.Args() args := flag.Args()
if len(args) == 0 { if len(args) == 0 {
usage() fmt.Println(Usage)
return
} }
if err := digraph(args[0], args[1:]); err != nil { if err := digraph(args[0], args[1:]); err != nil {
@ -262,47 +229,6 @@ func (g graph) sccs() []nodeset {
return sccs 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) { func parse(rd io.Reader) (graph, error) {
g := make(graph) g := make(graph)
@ -325,7 +251,6 @@ func parse(rd io.Reader) (graph, error) {
return g, nil return g, nil
} }
// Overridable for testing purposes.
var stdin io.Reader = os.Stdin var stdin io.Reader = os.Stdin
var stdout io.Writer = os.Stdout var stdout io.Writer = os.Stdout
@ -440,7 +365,33 @@ func digraph(cmd string, args []string) error {
if g[to] == nil { if g[to] == nil {
return fmt.Errorf("no such 'to' node %q", to) return fmt.Errorf("no such 'to' node %q", to)
} }
g.allpaths(from, to)
seen := make(nodeset) // value of seen[x] indicates whether x is on some path to 'to'
var visit func(label string) bool
visit = func(label string) bool {
reachesTo, ok := seen[label]
if !ok {
reachesTo = label == to
seen[label] = reachesTo
for e := range g[label] {
if visit(e) {
reachesTo = true
}
}
seen[label] = reachesTo
}
return reachesTo
}
if !visit(from) {
return fmt.Errorf("no path from %q to %q", from, to)
}
for label, reachesTo := range seen {
if !reachesTo {
delete(seen, label)
}
}
seen.sort().println("\n")
case "sccs": case "sccs":
if len(args) != 0 { if len(args) != 0 {

View File

@ -1,6 +1,3 @@
// 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 package main
import ( import (
@ -29,34 +26,35 @@ d c
` `
for _, test := range []struct { for _, test := range []struct {
name string
input string input string
cmd string cmd string
args []string args []string
want string want string
}{ }{
{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"}, {g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"}, {g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"}, {g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"}, {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() {g2, "allpaths", []string{"a", "d"}, "a\nb\nc\nd\n"},
if got != test.want {
t.Errorf("digraph(%s, %s) = got %q, want %q", test.cmd, test.args, got, test.want) {g2, "sccs", nil, "a\nb\nc d\n"},
} {g2, "scc", []string{"d"}, "c\nd\n"},
}) {g2, "succs", []string{"a"}, "b\nc\n"},
{g2, "preds", []string{"c"}, "a\nd\n"},
{g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\n"},
} {
stdin = strings.NewReader(test.input)
stdout = new(bytes.Buffer)
if err := digraph(test.cmd, test.args); err != nil {
t.Error(err)
continue
}
got := stdout.(fmt.Stringer).String()
if got != test.want {
t.Errorf("digraph(%s, %s) = %q, want %q", test.cmd, test.args, got, test.want)
}
} }
// TODO(adonovan): // TODO(adonovan):
@ -64,110 +62,6 @@ d c
// - test errors // - 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) { func TestSplit(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
line string line string

View File

@ -7,8 +7,8 @@ import (
"flag" "flag"
"fmt" "fmt"
"go/build" "go/build"
"go/format"
"go/parser" "go/parser"
"go/printer"
"go/token" "go/token"
"os" "os"
"os/exec" "os/exec"
@ -139,7 +139,7 @@ func doMain() error {
hadErrors = true hadErrors = true
} }
} else { } else {
format.Node(os.Stdout, iprog.Fset, file) printer.Fprint(os.Stdout, iprog.Fset, file)
} }
} }
} }

View File

@ -491,18 +491,15 @@ func list(args ...string) ([]*listPackage, error) {
return pkgs, nil return pkgs, nil
} }
// cwd contains the current working directory of the tool. var cwd string
//
// It is initialized directly so that its value will be set for any other func init() {
// package variables or init functions that depend on it, such as the gopath var err error
// variable in main_test.go. cwd, err = os.Getwd()
var cwd string = func() string {
cwd, err := os.Getwd()
if err != nil { if err != nil {
log.Fatalf("os.Getwd: %v", err) log.Fatalf("os.Getwd: %v", err)
} }
return cwd }
}()
// shortPath returns an absolute or relative name for path, whatever is shorter. // shortPath returns an absolute or relative name for path, whatever is shorter.
// Plundered from $GOROOT/src/cmd/go/build.go. // Plundered from $GOROOT/src/cmd/go/build.go.

View File

@ -10,7 +10,6 @@ package main
import ( import (
"bytes" "bytes"
"log"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -33,25 +32,11 @@ import (
// titanic.biz/bar -- domain is sinking; package has jumped ship to new.com/bar // 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 // 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) { func TestFixImports(t *testing.T) {
gopath := filepath.Join(cwd, "testdata")
if err := os.Setenv("GOPATH", gopath); err != nil {
t.Fatalf("os.Setenv: %v", err)
}
defer func() { defer func() {
stderr = os.Stderr stderr = os.Stderr
*badDomains = "code.google.com" *badDomains = "code.google.com"
@ -239,6 +224,11 @@ import (
// TestDryRun tests that the -n flag suppresses calls to writeFile. // TestDryRun tests that the -n flag suppresses calls to writeFile.
func TestDryRun(t *testing.T) { func TestDryRun(t *testing.T) {
gopath := filepath.Join(cwd, "testdata")
if err := os.Setenv("GOPATH", gopath); err != nil {
t.Fatalf("os.Setenv: %v", err)
}
*dryrun = true *dryrun = true
defer func() { *dryrun = false }() // restore defer func() { *dryrun = false }() // restore
stderr = new(bytes.Buffer) stderr = new(bytes.Buffer)

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,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

@ -7,7 +7,7 @@ package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"go/constant" exact "go/constant"
"go/token" "go/token"
"go/types" "go/types"
"io" "io"
@ -213,9 +213,9 @@ func (p *printer) printDecl(keyword string, n int, printGroup func()) {
// absInt returns the absolute value of v as a *big.Int. // absInt returns the absolute value of v as a *big.Int.
// v must be a numeric value. // v must be a numeric value.
func absInt(v constant.Value) *big.Int { func absInt(v exact.Value) *big.Int {
// compute big-endian representation of v // compute big-endian representation of v
b := constant.Bytes(v) // little-endian b := exact.Bytes(v) // little-endian
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i] b[i], b[j] = b[j], b[i]
} }
@ -229,14 +229,14 @@ var (
// floatString returns the string representation for a // floatString returns the string representation for a
// numeric value v in normalized floating-point format. // numeric value v in normalized floating-point format.
func floatString(v constant.Value) string { func floatString(v exact.Value) string {
if constant.Sign(v) == 0 { if exact.Sign(v) == 0 {
return "0.0" return "0.0"
} }
// x != 0 // x != 0
// convert |v| into a big.Rat x // convert |v| into a big.Rat x
x := new(big.Rat).SetFrac(absInt(constant.Num(v)), absInt(constant.Denom(v))) x := new(big.Rat).SetFrac(absInt(exact.Num(v)), absInt(exact.Denom(v)))
// normalize x and determine exponent e // normalize x and determine exponent e
// (This is not very efficient, but also not speed-critical.) // (This is not very efficient, but also not speed-critical.)
@ -272,7 +272,7 @@ func floatString(v constant.Value) string {
if e != 0 { if e != 0 {
s += fmt.Sprintf("e%+d", e) s += fmt.Sprintf("e%+d", e)
} }
if constant.Sign(v) < 0 { if exact.Sign(v) < 0 {
s = "-" + s s = "-" + s
} }
@ -286,29 +286,29 @@ func floatString(v constant.Value) string {
// valString returns the string representation for the value v. // valString returns the string representation for the value v.
// Setting floatFmt forces an integer value to be formatted in // Setting floatFmt forces an integer value to be formatted in
// normalized floating-point format. // normalized floating-point format.
// TODO(gri) Move this code into package constant. // TODO(gri) Move this code into package exact.
func valString(v constant.Value, floatFmt bool) string { func valString(v exact.Value, floatFmt bool) string {
switch v.Kind() { switch v.Kind() {
case constant.Int: case exact.Int:
if floatFmt { if floatFmt {
return floatString(v) return floatString(v)
} }
case constant.Float: case exact.Float:
return floatString(v) return floatString(v)
case constant.Complex: case exact.Complex:
re := constant.Real(v) re := exact.Real(v)
im := constant.Imag(v) im := exact.Imag(v)
var s string var s string
if constant.Sign(re) != 0 { if exact.Sign(re) != 0 {
s = floatString(re) s = floatString(re)
if constant.Sign(im) >= 0 { if exact.Sign(im) >= 0 {
s += " + " s += " + "
} else { } else {
s += " - " s += " - "
im = constant.UnaryOp(token.SUB, im, 0) // negate im im = exact.UnaryOp(token.SUB, im, 0) // negate im
} }
} }
// im != 0, otherwise v would be constant.Int or constant.Float // im != 0, otherwise v would be exact.Int or exact.Float
return s + floatString(im) + "i" return s + floatString(im) + "i"
} }
return v.String() return v.String()

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 golang.org/x/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
golang.org/x/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 .

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

@ -0,0 +1,82 @@
// 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"
"net/http"
"path"
"regexp"
"golang.org/x/tools/godoc"
"golang.org/x/tools/godoc/dl"
"golang.org/x/tools/godoc/proxy"
"golang.org/x/tools/godoc/short"
"golang.org/x/tools/godoc/static"
"golang.org/x/tools/godoc/vfs"
"golang.org/x/tools/godoc/vfs/mapfs"
"golang.org/x/tools/godoc/vfs/zipfs"
"google.golang.org/appengine"
)
func init() {
enforceHosts = !appengine.IsDevAppServer()
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.MaxResults = 10000 // matches flag default in main.go
corpus.IndexEnabled = true
corpus.IndexFiles = indexFilenames
if err := corpus.Init(); err != nil {
log.Fatal(err)
}
corpus.IndexDirectory = indexDirectoryDefault
go corpus.RunIndexer()
pres = godoc.NewPresentation(corpus)
pres.TabWidth = 8
pres.ShowPlayground = true
pres.ShowExamples = true
pres.DeclLinks = true
pres.NotesRx = regexp.MustCompile("BUG")
readTemplates(pres, true)
mux := registerHandlers(pres)
dl.RegisterHandlers(mux)
short.RegisterHandlers(mux)
// Register /compile and /share handlers against the default serve mux
// so that other app modules can make plain HTTP requests to those
// hosts. (For reasons, HTTPS communication between modules is broken.)
proxy.RegisterHandlers(http.DefaultServeMux)
log.Println("godoc initialization complete")
}

77
cmd/godoc/autocert.go Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2016 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 autocert
// This file adds automatic TLS certificate support (using
// golang.org/x/crypto/acme/autocert), conditional on the use of the
// autocert build tag. It sets the serveAutoCertHook func variable
// non-nil. It is used by main.go.
//
// TODO: make this the default? We're in the Go 1.8 freeze now, so
// this is too invasive to be default, but we want it for
// https://beta.golang.org/
package main
import (
"crypto/tls"
"flag"
"net"
"net/http"
"time"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/net/http2"
)
var (
autoCertDirFlag = flag.String("autocert_cache_dir", "/var/cache/autocert", "Directory to cache TLS certs")
autoCertHostFlag = flag.String("autocert_hostname", "", "optional hostname to require in autocert SNI requests")
)
func init() {
serveAutoCertHook = serveAutoCert
}
func serveAutoCert(h http.Handler) error {
m := autocert.Manager{
Cache: autocert.DirCache(*autoCertDirFlag),
Prompt: autocert.AcceptTOS,
}
if *autoCertHostFlag != "" {
m.HostPolicy = autocert.HostWhitelist(*autoCertHostFlag)
}
srv := &http.Server{
Handler: h,
TLSConfig: &tls.Config{
GetCertificate: m.GetCertificate,
},
IdleTimeout: 60 * time.Second,
}
http2.ConfigureServer(srv, &http2.Server{})
ln, err := net.Listen("tcp", ":443")
if err != nil {
return err
}
return srv.Serve(tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig))
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}

View File

@ -21,7 +21,7 @@ import (
const ( const (
blogRepo = "golang.org/x/blog" blogRepo = "golang.org/x/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

@ -2,6 +2,8 @@
// 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.
// +build !appengine
package main package main
import "net/http" import "net/http"

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,21 @@ 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')
-server=addr
webserver address for command line searches
-analysis=type,pointer -analysis=type,pointer
comma-separated list of analyses to perform comma-separated list of analyses to perform
"type": display identifier resolution, type info, method sets, "type": display identifier resolution, type info, method sets,
'implements', and static callees 'implements', and static callees
"pointer": display channel peers, callers and dynamic callees "pointer" display channel peers, callers and dynamic callees
(significantly slower) (significantly slower)
See https://golang.org/lib/godoc/analysis/help.html for details. See http://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 +101,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 +109,23 @@ 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 By default, godoc uses the system's GOOS/GOARCH; in command-line mode you can
"GOOS" and "GOARCH" to set the output on the web page for the target system. set the GOOS/GOARCH environment variables to get output for the system specified.
If -http was specified you can provide the URL parameters "GOOS" and "GOARCH"
to set the output on the web page.
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
@ -109,11 +141,11 @@ 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; Godoc also shows example code that is runnable by the testing package;
see https://golang.org/pkg/testing/#hdr-Examples for the conventions. see http://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 // import "golang.org/x/tools/cmd/godoc"

View File

@ -8,7 +8,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"go/build"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@ -23,6 +22,50 @@ import (
"time" "time"
) )
var godocTests = []struct {
args []string
matches []string // regular expressions
dontmatch []string // regular expressions
}{
{
args: []string{"fmt"},
matches: []string{
`import "fmt"`,
`Package fmt implements formatted I/O`,
},
},
{
args: []string{"io", "WriteString"},
matches: []string{
`func WriteString\(`,
`WriteString writes the contents of the string s to w`,
},
},
{
args: []string{"nonexistingpkg"},
matches: []string{
// The last pattern (does not e) is for plan9:
// http://build.golang.org/log/2d8e5e14ed365bfa434b37ec0338cd9e6f8dd9bf
`no such file or directory|does not exist|cannot find the file|(?:' does not e)`,
},
},
{
args: []string{"fmt", "NonexistentSymbol"},
matches: []string{
`No match found\.`,
},
},
{
args: []string{"-src", "syscall", "Open"},
matches: []string{
`func Open\(`,
},
dontmatch: []string{
`No match found\.`,
},
},
}
// buildGodoc builds the godoc executable. // buildGodoc builds the godoc executable.
// It returns its path, and a cleanup function. // It returns its path, and a cleanup function.
// //
@ -32,10 +75,6 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
if runtime.GOARCH == "arm" { if runtime.GOARCH == "arm" {
t.Skip("skipping test on arm platforms; too slow") t.Skip("skipping test on arm platforms; too slow")
} }
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
tmp, err := ioutil.TempDir("", "godoc-regtest-") tmp, err := ioutil.TempDir("", "godoc-regtest-")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -58,6 +97,33 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
return bin, func() { os.RemoveAll(tmp) } return bin, func() { os.RemoveAll(tmp) }
} }
// Basic regression test for godoc command-line tool.
func TestCLI(t *testing.T) {
bin, cleanup := buildGodoc(t)
defer cleanup()
for _, test := range godocTests {
cmd := exec.Command(bin, test.args...)
cmd.Args[0] = "godoc"
out, err := cmd.CombinedOutput()
if err != nil {
t.Errorf("Running with args %#v: %v", test.args, err)
continue
}
for _, pat := range test.matches {
re := regexp.MustCompile(pat)
if !re.Match(out) {
t.Errorf("godoc %v =\n%s\nwanted /%v/", strings.Join(test.args, " "), out, pat)
}
}
for _, pat := range test.dontmatch {
re := regexp.MustCompile(pat)
if re.Match(out) {
t.Errorf("godoc %v =\n%s\ndid not want /%v/", strings.Join(test.args, " "), out, pat)
}
}
}
}
func serverAddress(t *testing.T) string { func serverAddress(t *testing.T) string {
ln, err := net.Listen("tcp", "127.0.0.1:0") ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
@ -74,32 +140,19 @@ func waitForServerReady(t *testing.T, addr string) {
waitForServer(t, waitForServer(t,
fmt.Sprintf("http://%v/", addr), fmt.Sprintf("http://%v/", addr),
"The Go Programming Language", "The Go Programming Language",
15*time.Second, 15*time.Second)
false)
} }
func waitForSearchReady(t *testing.T, addr string) { func waitForSearchReady(t *testing.T, addr string) {
waitForServer(t, waitForServer(t,
fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr), fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr),
"The list of tokens.", "The list of tokens.",
2*time.Minute, 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 const pollInterval = 200 * time.Millisecond
func waitForServer(t *testing.T, url, match string, timeout time.Duration, reverse bool) { func waitForServer(t *testing.T, url, match string, timeout time.Duration) {
// "health check" duplicated from x/tools/cmd/tipgodoc/tip.go // "health check" duplicated from x/tools/cmd/tipgodoc/tip.go
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) { for time.Now().Before(deadline) {
@ -110,74 +163,19 @@ func waitForServer(t *testing.T, url, match string, timeout time.Duration, rever
} }
rbody, err := ioutil.ReadAll(res.Body) rbody, err := ioutil.ReadAll(res.Body)
res.Body.Close() res.Body.Close()
if err == nil && res.StatusCode == http.StatusOK { if err == nil && res.StatusCode == http.StatusOK &&
if bytes.Contains(rbody, []byte(match)) && !reverse { bytes.Contains(rbody, []byte(match)) {
return return
}
if !bytes.Contains(rbody, []byte(match)) && reverse {
return
}
} }
} }
t.Fatalf("Server failed to respond in %v", timeout) 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) { func killAndWait(cmd *exec.Cmd) {
cmd.Process.Kill() cmd.Process.Kill()
cmd.Wait() 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. // Basic integration test for godoc HTTP interface.
func TestWeb(t *testing.T) { func TestWeb(t *testing.T) {
testWeb(t, false) testWeb(t, false)
@ -193,9 +191,6 @@ func TestWebIndex(t *testing.T) {
// Basic integration test for godoc HTTP interface. // Basic integration test for godoc HTTP interface.
func testWeb(t *testing.T, withIndex bool) { 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) bin, cleanup := buildGodoc(t)
defer cleanup() defer cleanup()
addr := serverAddress(t) addr := serverAddress(t)
@ -207,16 +202,7 @@ func testWeb(t *testing.T, withIndex bool) {
cmd.Stdout = os.Stderr cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Args[0] = "godoc" cmd.Args[0] = "godoc"
cmd.Env = godocEnv()
// 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 { if err := cmd.Start(); err != nil {
t.Fatalf("failed to start godoc: %s", err) t.Fatalf("failed to start godoc: %s", err)
} }
@ -226,112 +212,73 @@ func testWeb(t *testing.T, withIndex bool) {
waitForSearchReady(t, addr) waitForSearchReady(t, addr)
} else { } else {
waitForServerReady(t, addr) waitForServerReady(t, addr)
waitUntilScanComplete(t, addr)
} }
tests := []struct { tests := []struct {
path string path string
contains []string // substring match []string
match []string // regexp dontmatch []string
notContains []string needIndex bool
needIndex bool
releaseTag string // optional release tag that must be in go/build.ReleaseTags
}{ }{
{ {
path: "/", path: "/",
contains: []string{"Go is an open source programming language"}, match: []string{"Go is an open source programming language"},
}, },
{ {
path: "/pkg/fmt/", path: "/pkg/fmt/",
contains: []string{"Package fmt implements formatted I/O"}, match: []string{"Package fmt implements formatted I/O"},
}, },
{ {
path: "/src/fmt/", path: "/src/fmt/",
contains: []string{"scan_test.go"}, match: []string{"scan_test.go"},
}, },
{ {
path: "/src/fmt/print.go", path: "/src/fmt/print.go",
contains: []string{"// Println formats using"}, match: []string{"// Println formats using"},
}, },
{ {
path: "/pkg", path: "/pkg",
contains: []string{ match: []string{
"Standard library", "Standard library",
"Package fmt implements formatted I/O", "Package fmt implements formatted I/O",
}, },
notContains: []string{ dontmatch: []string{
"internal/syscall", "internal/syscall",
"cmd/gc", "cmd/gc",
}, },
}, },
{ {
path: "/pkg/?m=all", path: "/pkg/?m=all",
contains: []string{ match: []string{
"Standard library", "Standard library",
"Package fmt implements formatted I/O", "Package fmt implements formatted I/O",
"internal/syscall/?m=all", "internal/syscall",
}, },
notContains: []string{ dontmatch: []string{
"cmd/gc", "cmd/gc",
}, },
}, },
{ {
path: "/search?q=ListenAndServe", path: "/search?q=ListenAndServe",
contains: []string{ match: []string{
"/src", "/src",
}, },
notContains: []string{ dontmatch: []string{
"/pkg/bootstrap", "/pkg/bootstrap",
}, },
needIndex: true, needIndex: true,
}, },
{ {
path: "/pkg/strings/", path: "/pkg/strings/",
contains: []string{ match: []string{
`href="/src/strings/strings.go"`, `href="/src/strings/strings.go"`,
}, },
}, },
{ {
path: "/cmd/compile/internal/amd64/", 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{ match: []string{
`Got1xxResponse.*// Go 1\.11`, `href="/src/cmd/compile/internal/amd64/prog.go"`,
}, },
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 { for _, test := range tests {
@ -345,34 +292,18 @@ func testWeb(t *testing.T, withIndex bool) {
continue continue
} }
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
strBody := string(body)
resp.Body.Close() resp.Body.Close()
if err != nil { if err != nil {
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp) t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
} }
isErr := false isErr := false
for _, substr := range test.contains { for _, substr := range test.match {
if test.releaseTag != "" && !hasTag(test.releaseTag) {
continue
}
if !bytes.Contains(body, []byte(substr)) { if !bytes.Contains(body, []byte(substr)) {
t.Errorf("GET %s: wanted substring %q in body", url, substr) t.Errorf("GET %s: wanted substring %q in body", url, substr)
isErr = true isErr = true
} }
} }
for _, re := range test.match { for _, substr := range test.dontmatch {
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)
isErr = true
}
}
for _, substr := range test.notContains {
if bytes.Contains(body, []byte(substr)) { if bytes.Contains(body, []byte(substr)) {
t.Errorf("GET %s: didn't want substring %q in body", url, substr) t.Errorf("GET %s: didn't want substring %q in body", url, substr)
isErr = true isErr = true
@ -424,11 +355,14 @@ func main() { print(lib.V) }
defer cleanup() defer cleanup()
addr := serverAddress(t) addr := serverAddress(t)
cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-analysis=type") 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("GOROOT=%s", filepath.Join(tmpdir, "goroot")))
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath"))) cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath")))
cmd.Env = append(cmd.Env, "GO111MODULE=off") for _, e := range os.Environ() {
cmd.Env = append(cmd.Env, "GOPROXY=off") if strings.HasPrefix(e, "GOROOT=") || strings.HasPrefix(e, "GOPATH=") {
continue
}
cmd.Env = append(cmd.Env, e)
}
cmd.Stdout = os.Stderr cmd.Stdout = os.Stderr
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if err != nil { if err != nil {
@ -506,3 +440,15 @@ tryagain:
} }
} }
} }
// godocEnv returns the process environment without the GOPATH variable.
// (We don't want the indexer looking at the local workspace during tests.)
func godocEnv() (env []string) {
for _, v := range os.Environ() {
if strings.HasPrefix(v, "GOPATH=") {
continue
}
env = append(env, v)
}
return
}

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

@ -21,7 +21,6 @@ import (
"text/template" "text/template"
"golang.org/x/tools/godoc" "golang.org/x/tools/godoc"
"golang.org/x/tools/godoc/golangorgenv"
"golang.org/x/tools/godoc/redirect" "golang.org/x/tools/godoc/redirect"
"golang.org/x/tools/godoc/vfs" "golang.org/x/tools/godoc/vfs"
) )
@ -31,20 +30,21 @@ var (
fs = vfs.NameSpace{} fs = vfs.NameSpace{}
) )
var enforceHosts = false // set true in production on app engine
// hostEnforcerHandler redirects requests to "http://foo.golang.org/bar" // hostEnforcerHandler redirects requests to "http://foo.golang.org/bar"
// to "https://golang.org/bar". // to "https://golang.org/bar".
// It permits requests to the host "godoc-test.golang.org" for testing and // It permits requests to the host "godoc-test.golang.org" for testing.
// golang.google.cn for Chinese users.
type hostEnforcerHandler struct { type hostEnforcerHandler struct {
h http.Handler h http.Handler
} }
func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !golangorgenv.EnforceHosts() { if !enforceHosts {
h.h.ServeHTTP(w, r) h.h.ServeHTTP(w, r)
return return
} }
if !h.isHTTPS(r) || !h.validHost(r.Host) { if r.TLS == nil || !h.validHost(r.Host) {
r.URL.Scheme = "https" r.URL.Scheme = "https"
if h.validHost(r.Host) { if h.validHost(r.Host) {
r.URL.Host = r.Host r.URL.Host = r.Host
@ -54,21 +54,13 @@ func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, r.URL.String(), http.StatusFound) http.Redirect(w, r, r.URL.String(), http.StatusFound)
return return
} }
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload")
h.h.ServeHTTP(w, r) 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 { func (h hostEnforcerHandler) validHost(host string) bool {
switch strings.ToLower(host) { switch strings.ToLower(host) {
case "golang.org", "golang.google.cn": case "golang.org", "godoc-test.golang.org":
return true
}
if strings.HasSuffix(host, "-dot-golang-org.appspot.com") {
// staging/test
return true return true
} }
return false return false
@ -112,23 +104,27 @@ 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") if html || p.HTMLMode {
p.ErrorHTML = readTemplate("error.html") codewalkHTML = readTemplate("codewalk.html")
p.ExampleHTML = readTemplate("example.html") codewalkdirHTML = readTemplate("codewalkdir.html")
p.GodocHTML = readTemplate("godoc.html") p.CallGraphHTML = readTemplate("callgraph.html")
p.ImplementsHTML = readTemplate("implements.html") p.DirlistHTML = readTemplate("dirlist.html")
p.MethodSetHTML = readTemplate("methodset.html") p.ErrorHTML = readTemplate("error.html")
p.PackageHTML = readTemplate("package.html") p.ExampleHTML = readTemplate("example.html")
p.PackageRootHTML = readTemplate("packageroot.html") p.GodocHTML = readTemplate("godoc.html")
p.SearchHTML = readTemplate("search.html") p.ImplementsHTML = readTemplate("implements.html")
p.SearchDocHTML = readTemplate("searchdoc.html") p.MethodSetHTML = readTemplate("methodset.html")
p.SearchCodeHTML = readTemplate("searchcode.html") p.PackageHTML = readTemplate("package.html")
p.SearchTxtHTML = readTemplate("searchtxt.html") p.SearchHTML = readTemplate("search.html")
p.SearchDescXML = readTemplate("opensearch.xml") p.SearchDocHTML = readTemplate("searchdoc.html")
p.SearchCodeHTML = readTemplate("searchcode.html")
p.SearchTxtHTML = readTemplate("searchtxt.html")
p.SearchDescXML = readTemplate("opensearch.xml")
}
} }
type fmtResponse struct { type fmtResponse struct {

View File

@ -14,18 +14,28 @@
// (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
import ( import (
"archive/zip" "archive/zip"
"bytes"
_ "expvar" // to serve /debug/vars _ "expvar" // to serve /debug/vars
"flag" "flag"
"fmt" "fmt"
"go/build" "go/build"
"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"
@ -43,7 +53,10 @@ import (
"golang.org/x/tools/godoc/vfs/zipfs" "golang.org/x/tools/godoc/vfs/zipfs"
) )
const defaultAddr = "localhost:6060" // default webserver address const (
defaultAddr = ":6060" // default webserver address
toolsPath = "golang.org/x/tools/cmd/"
)
var ( var (
// file system to serve // file system to serve
@ -56,21 +69,29 @@ var (
analysisFlag = flag.String("analysis", "", `comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html`) 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", "", "load templates/JS/CSS from disk in this directory")
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
@ -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)
} }
@ -123,58 +135,40 @@ 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()
}
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() usage()
} }
// Set the resolved goroot. var fsGate chan bool
vfs.GOROOT = *goroot fsGate = make(chan bool, 20)
fsGate := make(chan bool, 20)
// Determine file system to use. // Determine file system to use.
if *zipfile == "" { if *zipfile == "" {
@ -201,6 +195,8 @@ func main() {
fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter) fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
} }
httpMode := *httpAddr != ""
var typeAnalysis, pointerAnalysis bool var typeAnalysis, pointerAnalysis bool
if *analysisFlag != "" { if *analysisFlag != "" {
for _, a := range strings.Split(*analysisFlag, ",") { for _, a := range strings.Split(*analysisFlag, ",") {
@ -218,7 +214,7 @@ func main() {
corpus := godoc.NewCorpus(fs) corpus := godoc.NewCorpus(fs)
corpus.Verbose = *verbose corpus.Verbose = *verbose
corpus.MaxResults = *maxResults corpus.MaxResults = *maxResults
corpus.IndexEnabled = *indexEnabled corpus.IndexEnabled = *indexEnabled && httpMode
if *maxResults == 0 { if *maxResults == 0 {
corpus.IndexFullText = false corpus.IndexFullText = false
} }
@ -226,27 +222,29 @@ func main() {
corpus.IndexDirectory = indexDirectoryDefault corpus.IndexDirectory = indexDirectoryDefault
corpus.IndexThrottle = *indexThrottle corpus.IndexThrottle = *indexThrottle
corpus.IndexInterval = *indexInterval corpus.IndexInterval = *indexInterval
if *writeIndex || *urlFlag != "" { if *writeIndex {
corpus.IndexThrottle = 1.0 corpus.IndexThrottle = 1.0
corpus.IndexEnabled = true corpus.IndexEnabled = true
initCorpus(corpus) }
} else { if *writeIndex || httpMode || *urlFlag != "" {
go initCorpus(corpus) if err := corpus.Init(); err != nil {
log.Fatal(err)
}
} }
// Initialize the version info before readTemplates, which saves
// the map value in a method value.
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
pres.SrcMode = *srcMode
pres.HTMLMode = *html
if *notesRx != "" { if *notesRx != "" {
pres.NotesRx = regexp.MustCompile(*notesRx) pres.NotesRx = regexp.MustCompile(*notesRx)
} }
readTemplates(pres) readTemplates(pres, httpMode || *urlFlag != "")
registerHandlers(pres) registerHandlers(pres)
if *writeIndex { if *writeIndex {
@ -281,58 +279,63 @@ 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 type/pointer analysis.
if typeAnalysis || pointerAnalysis {
go analysis.Run(pointerAnalysis, &corpus.Analysis)
}
if serveAutoCertHook != nil {
go func() {
if err := serveAutoCertHook(handler); err != nil {
log.Fatalf("ListenAndServe TLS: %v", err)
}
}()
}
// Start http server.
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
}
return
} }
// Start http server. if *query {
if *verbose { handleRemoteSearch()
log.Println("starting HTTP server") return
} }
if wrapHTTPMux != nil {
handler = wrapHTTPMux(handler) if err := godoc.CommandLine(os.Stdout, fs, pres, flag.Args()); err != nil {
} log.Print(err)
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
} }
} }
// Hooks that are set non-nil in autocert.go if the "autocert" build tag // serveAutoCertHook if non-nil specifies a function to listen on port 443.
// is used. // See autocert.go.
var ( var serveAutoCertHook func(http.Handler) error
certInit func()
runHTTPS func(http.Handler) error
wrapHTTPMux func(http.Handler) http.Handler
)

View File

@ -2,6 +2,8 @@
// 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.
// +build !appengine
package main package main
// This package registers "/compile" and "/share" handlers // This package registers "/compile" and "/share" handlers

72
cmd/godoc/remotesearch.go Normal file
View File

@ -0,0 +1,72 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
package main
import (
"errors"
"flag"
"io"
"log"
"net/http"
"net/url"
"os"
)
func handleRemoteSearch() {
// Command-line queries.
for i := 0; i < flag.NArg(); i++ {
res, err := remoteSearch(flag.Arg(i))
if err != nil {
log.Fatalf("remoteSearch: %s", err)
}
io.Copy(os.Stdout, res.Body)
}
return
}
// remoteSearchURL returns the search URL for a given query as needed by
// remoteSearch. If html is set, an html result is requested; otherwise
// the result is in textual form.
// Adjust this function as necessary if modeNames or FormValue parameters
// change.
func remoteSearchURL(query string, html bool) string {
s := "/search?m=text&q="
if html {
s = "/search?q="
}
return s + url.QueryEscape(query)
}
func remoteSearch(query string) (res *http.Response, err error) {
// list of addresses to try
var addrs []string
if *serverAddr != "" {
// explicit server address - only try this one
addrs = []string{*serverAddr}
} else {
addrs = []string{
defaultAddr,
"golang.org",
}
}
// remote search
search := remoteSearchURL(query, *html)
for _, addr := range addrs {
url := "http://" + addr + search
res, err = http.Get(url)
if err == nil && res.StatusCode == http.StatusOK {
break
}
}
if err == nil && res.StatusCode != http.StatusOK {
err = errors.New(res.Status)
}
return
}

134
cmd/godoc/setup-godoc-app.bash Executable file
View File

@ -0,0 +1,134 @@
#!/usr/bin/env bash
# Copyright 2011 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# This script creates a complete godoc app in $APPDIR.
# It copies the cmd/godoc and src/go/... sources from GOROOT,
# synthesizes an app.yaml file, and creates the .zip, index, and
# configuration files.
#
# If an argument is provided it is assumed to be the app-engine godoc directory.
# Without an argument, $APPDIR is used instead. If GOROOT is not set, "go env"
# is consulted to find the $GOROOT.
#
# The script creates a .zip file representing the $GOROOT file system
# and computes the correspondig search index files. These files are then
# copied to $APPDIR. A corresponding godoc configuration file is created
# in $APPDIR/appconfig.go.
ZIPFILE=godoc.zip
INDEXFILE=godoc.index
SPLITFILES=index.split.
GODOC=golang.org/x/tools/cmd/godoc
CONFIGFILE=$GODOC/appconfig.go
error() {
echo "error: $1"
exit 2
}
getArgs() {
if [ -z $APPENGINE_SDK ]; then
error "APPENGINE_SDK environment variable not set"
fi
if [ ! -x $APPENGINE_SDK/goapp ]; then
error "couldn't find goapp command in $APPENGINE_SDK"
fi
if [ -z $GOROOT ]; then
GOROOT=$(go env GOROOT)
echo "GOROOT not set explicitly, using go env value instead"
fi
if [ -z $APPDIR ]; then
if [ $# == 0 ]; then
error "APPDIR not set, and no argument provided"
fi
APPDIR=$1
echo "APPDIR not set, using argument instead"
fi
# safety checks
if [ ! -d $GOROOT ]; then
error "$GOROOT is not a directory"
fi
if [ -e $APPDIR ]; then
error "$APPDIR exists; check and remove it before trying again"
fi
# reporting
echo "GOROOT = $GOROOT"
echo "APPDIR = $APPDIR"
}
fetchGodoc() {
echo "*** Fetching godoc (if not already in GOPATH)"
unset GOBIN
go=$APPENGINE_SDK/goapp
$go get -d -tags appengine $GODOC
mkdir -p $APPDIR/$GODOC
cp $(find $($go list -f '{{.Dir}}' $GODOC) -mindepth 1 -maxdepth 1 -type f) $APPDIR/$GODOC/
}
makeAppYaml() {
echo "*** make $APPDIR/app.yaml"
cat > $APPDIR/app.yaml <<EOF
application: godoc
version: 1
runtime: go
api_version: go1.4beta
handlers:
- url: /.*
script: _go_app
EOF
}
makeZipfile() {
echo "*** make $APPDIR/$ZIPFILE"
zip -q -r $APPDIR/$ZIPFILE $GOROOT/*
}
makeIndexfile() {
echo "*** make $APPDIR/$INDEXFILE"
GOPATH= godoc -write_index -index_files=$APPDIR/$INDEXFILE -zip=$APPDIR/$ZIPFILE
}
splitIndexfile() {
echo "*** split $APPDIR/$INDEXFILE"
split -b8m $APPDIR/$INDEXFILE $APPDIR/$SPLITFILES
}
makeConfigfile() {
echo "*** make $APPDIR/$CONFIGFILE"
cat > $APPDIR/$CONFIGFILE <<EOF
package main
// GENERATED FILE - DO NOT MODIFY BY HAND.
// (generated by golang.org/x/tools/cmd/godoc/setup-godoc-app.bash)
const (
// .zip filename
zipFilename = "$ZIPFILE"
// goroot directory in .zip file
zipGoroot = "$GOROOT"
// glob pattern describing search index files
// (if empty, the index is built at run-time)
indexFilenames = "$SPLITFILES*"
)
EOF
}
getArgs "$@"
set -e
mkdir $APPDIR
fetchGodoc
makeAppYaml
makeZipfile
makeIndexfile
splitIndexfile
makeConfigfile
echo "*** setup complete"

90
cmd/godoc/x.go Normal file
View File

@ -0,0 +1,90 @@
// 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.
// This file contains the handlers that serve go-import redirects for Go
// sub-repositories. It specifies the mapping from import paths like
// "golang.org/x/tools" to the actual repository locations.
package main
import (
"html/template"
"log"
"net/http"
"strings"
)
const xPrefix = "/x/"
type xRepo struct {
URL, VCS string
}
var xMap = map[string]xRepo{
"codereview": {"https://code.google.com/p/go.codereview", "hg"},
"arch": {"https://go.googlesource.com/arch", "git"},
"benchmarks": {"https://go.googlesource.com/benchmarks", "git"},
"blog": {"https://go.googlesource.com/blog", "git"},
"build": {"https://go.googlesource.com/build", "git"},
"crypto": {"https://go.googlesource.com/crypto", "git"},
"debug": {"https://go.googlesource.com/debug", "git"},
"exp": {"https://go.googlesource.com/exp", "git"},
"image": {"https://go.googlesource.com/image", "git"},
"mobile": {"https://go.googlesource.com/mobile", "git"},
"net": {"https://go.googlesource.com/net", "git"},
"oauth2": {"https://go.googlesource.com/oauth2", "git"},
"perf": {"https://go.googlesource.com/perf", "git"},
"playground": {"https://go.googlesource.com/playground", "git"},
"review": {"https://go.googlesource.com/review", "git"},
"sync": {"https://go.googlesource.com/sync", "git"},
"sys": {"https://go.googlesource.com/sys", "git"},
"talks": {"https://go.googlesource.com/talks", "git"},
"term": {"https://go.googlesource.com/term", "git"},
"text": {"https://go.googlesource.com/text", "git"},
"time": {"https://go.googlesource.com/time", "git"},
"tools": {"https://go.googlesource.com/tools", "git"},
"tour": {"https://go.googlesource.com/tour", "git"},
}
func init() {
http.HandleFunc(xPrefix, xHandler)
}
func xHandler(w http.ResponseWriter, r *http.Request) {
head, tail := strings.TrimPrefix(r.URL.Path, xPrefix), ""
if i := strings.Index(head, "/"); i != -1 {
head, tail = head[:i], head[i:]
}
if head == "" {
http.Redirect(w, r, "https://godoc.org/-/subrepo", http.StatusTemporaryRedirect)
return
}
repo, ok := xMap[head]
if !ok {
http.NotFound(w, r)
return
}
data := struct {
Prefix, Head, Tail string
Repo xRepo
}{xPrefix, head, tail, repo}
if err := xTemplate.Execute(w, data); err != nil {
log.Println("xHandler:", err)
}
}
var xTemplate = template.Must(template.New("x").Parse(`<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="golang.org{{.Prefix}}{{.Head}} {{.Repo.VCS}} {{.Repo.URL}}">
<meta name="go-source" content="golang.org{{.Prefix}}{{.Head}} https://github.com/golang/{{.Head}}/ https://github.com/golang/{{.Head}}/tree/master{/dir} https://github.com/golang/{{.Head}}/blob/master{/dir}/{file}#L{line}">
<meta http-equiv="refresh" content="0; url=https://godoc.org/golang.org{{.Prefix}}{{.Head}}{{.Tail}}">
</head>
<body>
Nothing to see here; <a href="https://godoc.org/golang.org{{.Prefix}}{{.Head}}{{.Tail}}">move along</a>.
</body>
</html>
`))

View File

@ -5,14 +5,16 @@ adding missing ones and removing unreferenced ones.
$ go get golang.org/x/tools/cmd/goimports $ go get golang.org/x/tools/cmd/goimports
In addition to fixing imports, goimports also formats It's a drop-in replacement for your editor's gofmt-on-save hook.
your code in the same style as gofmt so it can be used It has the same command-line interface as gofmt and formats
as a replacement for your editor's gofmt-on-save hook. your code in the same way.
For emacs, make sure you have the latest go-mode.el: For emacs, make sure you have the latest go-mode.el:
https://github.com/dominikh/go-mode.el https://github.com/dominikh/go-mode.el
Then in your .emacs file: Then in your .emacs file:
(setq gofmt-command "goimports") (setq gofmt-command "goimports")
(add-to-list 'load-path "/home/you/somewhere/emacs/")
(require 'go-mode-load)
(add-hook 'before-save-hook 'gofmt-before-save) (add-hook 'before-save-hook 'gofmt-before-save)
For vim, set "gofmt_command" to "goimports": For vim, set "gofmt_command" to "goimports":

View File

@ -10,7 +10,6 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"go/build"
"go/scanner" "go/scanner"
"io" "io"
"io/ioutil" "io/ioutil"
@ -22,16 +21,15 @@ import (
"runtime/pprof" "runtime/pprof"
"strings" "strings"
"golang.org/x/tools/internal/imports" "golang.org/x/tools/imports"
) )
var ( var (
// main operation modes // main operation modes
list = flag.Bool("l", false, "list files whose formatting differs from goimport's") list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
write = flag.Bool("w", false, "write result to (source) file instead of stdout") write = flag.Bool("w", false, "write result to (source) file instead of stdout")
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files") doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.") srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
verbose bool // verbose logging verbose bool // verbose logging
cpuProfile = flag.String("cpuprofile", "", "CPU profile output") cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
@ -43,19 +41,13 @@ var (
TabIndent: true, TabIndent: true,
Comments: true, Comments: true,
Fragment: true, Fragment: true,
// This environment, and its caches, will be reused for the whole run.
Env: &imports.ProcessEnv{
GOPATH: build.Default.GOPATH,
GOROOT: build.Default.GOROOT,
},
} }
exitCode = 0 exitCode = 0
) )
func init() { func init() {
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)") flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
flag.StringVar(&options.Env.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; comma-separated list") flag.StringVar(&imports.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages")
flag.BoolVar(&options.FormatOnly, "format-only", false, "if true, don't fix imports and only format. In this mode, goimports is effectively gofmt, with the addition that imports are grouped into sections.")
} }
func report(err error) { func report(err error) {
@ -152,24 +144,17 @@ func processFile(filename string, in io.Reader, out io.Writer, argType argumentT
fmt.Fprintln(out, filename) fmt.Fprintln(out, filename)
} }
if *write { if *write {
if argType == fromStdin {
// filename is "<standard input>"
return errors.New("can't use -w on stdin")
}
err = ioutil.WriteFile(filename, res, 0) err = ioutil.WriteFile(filename, res, 0)
if err != nil { if err != nil {
return err return err
} }
} }
if *doDiff { if *doDiff {
if argType == fromStdin { data, err := diff(src, res)
filename = "stdin.go" // because <standard input>.orig looks silly
}
data, err := diff(src, res, filename)
if err != nil { if err != nil {
return fmt.Errorf("computing diff: %s", err) return fmt.Errorf("computing diff: %s", err)
} }
fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename)) fmt.Printf("diff %s gofmt/%s\n", filename, filename)
out.Write(data) out.Write(data)
} }
} }
@ -258,7 +243,7 @@ func gofmtMain() {
if verbose { if verbose {
log.SetFlags(log.LstdFlags | log.Lmicroseconds) log.SetFlags(log.LstdFlags | log.Lmicroseconds)
options.Env.Debug = true imports.Debug = true
} }
if options.TabWidth < 0 { if options.TabWidth < 0 {
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth) fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
@ -292,78 +277,33 @@ func gofmtMain() {
} }
} }
func writeTempFile(dir, prefix string, data []byte) (string, error) { func diff(b1, b2 []byte) (data []byte, err error) {
file, err := ioutil.TempFile(dir, prefix) f1, err := ioutil.TempFile("", "gofmt")
if err != nil {
return "", err
}
_, err = file.Write(data)
if err1 := file.Close(); err == nil {
err = err1
}
if err != nil {
os.Remove(file.Name())
return "", err
}
return file.Name(), nil
}
func diff(b1, b2 []byte, filename string) (data []byte, err error) {
f1, err := writeTempFile("", "gofmt", b1)
if err != nil { if err != nil {
return return
} }
defer os.Remove(f1) defer os.Remove(f1.Name())
defer f1.Close()
f2, err := writeTempFile("", "gofmt", b2) f2, err := ioutil.TempFile("", "gofmt")
if err != nil { if err != nil {
return return
} }
defer os.Remove(f2) defer os.Remove(f2.Name())
defer f2.Close()
cmd := "diff" f1.Write(b1)
if runtime.GOOS == "plan9" { f2.Write(b2)
cmd = "/bin/ape/diff"
}
data, err = exec.Command(cmd, "-u", f1, f2).CombinedOutput() data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
if len(data) > 0 { if len(data) > 0 {
// diff exits with a non-zero status when the files don't match. // diff exits with a non-zero status when the files don't match.
// Ignore that failure as long as we get output. // Ignore that failure as long as we get output.
return replaceTempFilename(data, filename) err = nil
} }
return return
} }
// replaceTempFilename replaces temporary filenames in diff with actual one.
//
// --- /tmp/gofmt316145376 2017-02-03 19:13:00.280468375 -0500
// +++ /tmp/gofmt617882815 2017-02-03 19:13:00.280468375 -0500
// ...
// ->
// --- path/to/file.go.orig 2017-02-03 19:13:00.280468375 -0500
// +++ path/to/file.go 2017-02-03 19:13:00.280468375 -0500
// ...
func replaceTempFilename(diff []byte, filename string) ([]byte, error) {
bs := bytes.SplitN(diff, []byte{'\n'}, 3)
if len(bs) < 3 {
return nil, fmt.Errorf("got unexpected diff for %s", filename)
}
// Preserve timestamps.
var t0, t1 []byte
if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 {
t0 = bs[0][i:]
}
if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 {
t1 = bs[1][i:]
}
// Always print filepath with slash separator.
f := filepath.ToSlash(filename)
bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0))
bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1))
return bytes.Join(bs, []byte{'\n'}), nil
}
// isFile reports whether name is a file. // isFile reports whether name is a file.
func isFile(name string) bool { func isFile(name string) bool {
fi, err := os.Stat(name) fi, err := os.Stat(name)

View File

@ -19,7 +19,7 @@ import (
var ( var (
fromFlag = flag.String("from", "", "Import path of package to be moved") fromFlag = flag.String("from", "", "Import path of package to be moved")
toFlag = flag.String("to", "", "Destination import path for package") toFlag = flag.String("to", "", "Destination import path for package")
vcsMvCmdFlag = flag.String("vcs_mv_cmd", "", `A template for the version control system's "move directory" command, e.g. "git mv {{.Src}} {{.Dst}}"`) vcsMvCmdFlag = flag.String("vcs_mv_cmd", "", `A template for the version control system's "move directory" command, e.g. "git mv {{.Src}} {{.Dst}}`)
helpFlag = flag.Bool("help", false, "show usage message") helpFlag = flag.Bool("help", false, "show usage message")
) )

View File

@ -1,3 +0,0 @@
gopls*.vsix
out
node_modules

View File

@ -1,20 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm"
},
]
}

View File

@ -1,29 +0,0 @@
// Available variables which can be used inside of strings.
// ${workspaceFolder}: the root folder of the team
// ${file}: the current opened file
// ${fileBasename}: the current opened file's basename
// ${fileDirname}: the current opened file's dirname
// ${fileExtname}: the current opened file's extension
// ${cwd}: the current working directory of the spawned process
// A task runner that calls the Typescript compiler (tsc) and
// compiles the extension.
{
"version": "2.0.0",
// we want to run npm
"command": "npm",
// the command is a shell script
"type": "shell",
// show the output window only if unrecognized errors occur.
"presentation": {
"reveal": "silent"
},
// we run the custom script "compile" as defined in package.json
"args": [
"run",
"compile"
],
// The tsc compiler is started in watching mode
"isBackground": true,
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
"problemMatcher": "$tsc-watch"
}

View File

@ -1,21 +0,0 @@
# gopls testing extension
An extension for debugging the Go Language Server provided by
https://golang.org/x/tools/cmd/gopls. The code for this extension comes from
a combination of
https://github.com/Microsoft/vscode-extension-samples/blob/master/lsp-sample
and https://github.com/Microsoft/vscode-go.
## Features
* Diagnostics (on file change)
* Completion (Ctrl + Space)
* Jump to definition (F12 or right-click -> Go to Definition)
* Signature help (Ctrl + Shift + Space)
## Installation
To package the extension, run `vsce package` from this directory. To install
the extension, navigate to the "Extensions" panel in VSCode, and select
"Install from VSIX..." from the menu in the top right corner. Choose the
`gopls-1.0.0.vsix file` and reload VSCode.

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
{
"name": "gopls",
"description": "Go Language Server Client for testing",
"author": "The Go authors",
"license": "SEE LICENSE IN ../../../../LICENSE",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "https://golang.org/x/tools"
},
"publisher": "golang",
"engines": {
"vscode": "^1.23.0"
},
"activationEvents": [
"onLanguage:go"
],
"main": "./out/extension",
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"lint": "node ./node_modules/tslint/bin/tslint ./src/*.ts"
},
"extensionDependencies": [],
"dependencies": {
"vscode-languageclient": "~4.3.0"
},
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/node": "^8.10.39",
"tslint": "^5.11.0",
"typescript": "^3.1.3",
"vscode": "^1.1.24"
},
"contributes": {
"configuration": {
"title": "gopls",
"properties": {
"gopls.flags": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "Flags to pass to gopls",
"scope": "resource"
},
"gopls.command": {
"type": "string",
"default": "gopls",
"description": "Name of the gopls binary",
"scope": "resource"
}
}
}
}
}

View File

@ -1,96 +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.
'use strict';
import fs = require('fs');
import lsp = require('vscode-languageclient');
import vscode = require('vscode');
import path = require('path');
export function activate(ctx: vscode.ExtensionContext): void {
// The handleDiagnostics middleware writes to the diagnostics log, in order
// to confirm the order in which the extension received diagnostics.
let r = Math.floor(Math.random() * 100000000);
let diagnosticsLog = fs.openSync('/tmp/diagnostics' + r + '.log', 'w');
let document = vscode.window.activeTextEditor.document;
let config = vscode.workspace.getConfiguration('gopls', document.uri);
let goplsCommand: string = config['command'];
let goplsFlags: string[] = config['flags'];
let serverOptions:
lsp.ServerOptions = {command: getBinPath(goplsCommand), args: goplsFlags};
let clientOptions: lsp.LanguageClientOptions = {
initializationOptions: {},
documentSelector: ['go'],
uriConverters: {
code2Protocol: (uri: vscode.Uri): string =>
(uri.scheme ? uri : uri.with({scheme: 'file'})).toString(),
protocol2Code: (uri: string) => vscode.Uri.parse(uri),
},
middleware: {
handleDiagnostics: (uri: vscode.Uri, diagnostics: vscode.Diagnostic[], next: lsp.HandleDiagnosticsSignature) => {
let diagString = "-------------------------------------------\n";
diagString += uri.toString(); + ": " + diagnostics.length + "\n";
if (diagnostics.length > 0) {
diagString += "\n";
for (const diag of diagnostics) {
diagString += diag.message + "\n";
}
}
fs.writeSync(diagnosticsLog, diagString);
return next(uri, diagnostics);
}
},
revealOutputChannelOn: lsp.RevealOutputChannelOn.Never,
};
const c = new lsp.LanguageClient('gopls', serverOptions, clientOptions);
c.onReady().then(() => {
const capabilities = c.initializeResult && c.initializeResult.capabilities;
if (!capabilities) {
return vscode.window.showErrorMessage(
'The language server is not able to serve any features at the moment.');
}
});
ctx.subscriptions.push(c.start());
}
function getBinPath(toolName: string): string {
toolName = correctBinname(toolName);
let tool = findToolIn(toolName, 'PATH', false);
if (tool) {
return tool;
}
return findToolIn(toolName, 'GOPATH', true);
}
function findToolIn(
toolName: string, envVar: string, appendBinToPath: boolean): string {
let value = process.env[envVar];
if (value) {
let paths = value.split(path.delimiter);
for (let i = 0; i < paths.length; i++) {
let binpath = path.join(paths[i], appendBinToPath ? 'bin' : '', toolName);
if (fileExists(binpath)) {
return binpath;
}
}
}
return null;
}
function fileExists(filePath: string): boolean {
try {
return fs.statSync(filePath).isFile();
} catch (e) {
return false;
}
}
function correctBinname(toolName: string) {
if (process.platform === 'win32')
return toolName + '.exe';
else
return toolName;
}

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"rootDir": "src",
"lib": [
"es6"
],
"sourceMap": true
},
"exclude": [
"node_modules"
]
}

View File

@ -1,12 +0,0 @@
{
"rules": {
"indent": [
true,
"tabs"
],
"semicolon": [
true,
"always"
]
}
}

View File

@ -1,23 +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.
// The gopls command is an LSP server for Go.
// The Language Server Protocol allows any text editor
// to be extended with IDE-like features;
// see https://langserver.org/ for details.
package main // import "golang.org/x/tools/cmd/gopls"
import (
"context"
"os"
"golang.org/x/tools/internal/lsp/cmd"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/tool"
)
func main() {
debug.Version += "-cmd.gopls"
tool.Main(context.Background(), cmd.New("gopls-legacy", "", nil), os.Args[1:])
}

View File

@ -1,11 +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 cgo
package main_test
func init() {
haveCGO = true
}

View File

@ -1,385 +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.
package main_test
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
)
var haveCGO bool
type test struct {
offset, from, to string // specify the arguments
fileSpecified bool // true if the offset or from args specify a specific file
pkgs map[string][]string
wantErr bool
wantOut string // a substring expected to be in the output
packages map[string][]string // a map of the package name to the files contained within, which will be numbered by i.go where i is the index
}
// Test that renaming that would modify cgo files will produce an error and not modify the file.
func TestGeneratedFiles(t *testing.T) {
if !haveCGO {
t.Skipf("skipping test: no cgo")
}
tmp, bin, cleanup := buildGorename(t)
defer cleanup()
srcDir := filepath.Join(tmp, "src")
err := os.Mkdir(srcDir, os.ModePerm)
if err != nil {
t.Fatal(err)
}
var env = []string{fmt.Sprintf("GOPATH=%s", tmp)}
for _, envVar := range os.Environ() {
if !strings.HasPrefix(envVar, "GOPATH=") {
env = append(env, envVar)
}
}
// gorename currently requires GOPATH mode.
env = append(env, "GO111MODULE=off")
// Testing renaming in packages that include cgo files:
for iter, renameTest := range []test{
{
// Test: variable not used in any cgo file -> no error
from: `"mytest"::f`, to: "g",
packages: map[string][]string{
"mytest": []string{`package mytest; func f() {}`,
`package mytest
// #include <stdio.h>
import "C"
func z() {C.puts(nil)}`},
},
wantErr: false,
wantOut: "Renamed 1 occurrence in 1 file in 1 package.",
}, {
// Test: to name used in cgo file -> rename error
from: `"mytest"::f`, to: "g",
packages: map[string][]string{
"mytest": []string{`package mytest; func f() {}`,
`package mytest
// #include <stdio.h>
import "C"
func g() {C.puts(nil)}`},
},
wantErr: true,
wantOut: "conflicts with func in same block",
},
{
// Test: from name in package in cgo file -> error
from: `"mytest"::f`, to: "g",
packages: map[string][]string{
"mytest": []string{`package mytest
// #include <stdio.h>
import "C"
func f() { C.puts(nil); }
`},
},
wantErr: true,
wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:",
}, {
// Test: from name in cgo file -> error
from: filepath.Join("mytest", "0.go") + `::f`, to: "g",
fileSpecified: true,
packages: map[string][]string{
"mytest": []string{`package mytest
// #include <stdio.h>
import "C"
func f() { C.puts(nil); }
`},
},
wantErr: true,
wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:",
}, {
// Test: offset in cgo file -> identifier in cgo error
offset: filepath.Join("main", "0.go") + `:#78`, to: "bar",
fileSpecified: true,
wantErr: true,
packages: map[string][]string{
"main": {`package main
// #include <unistd.h>
import "C"
import "fmt"
func main() {
foo := 1
C.close(2)
fmt.Println(foo)
}
`},
},
wantOut: "cannot rename identifiers in generated file containing DO NOT EDIT marker:",
}, {
// Test: from identifier appears in cgo file in another package -> error
from: `"test"::Foo`, to: "Bar",
packages: map[string][]string{
"test": []string{
`package test
func Foo(x int) (int){
return x * 2
}
`,
},
"main": []string{
`package main
import "test"
import "fmt"
// #include <unistd.h>
import "C"
func fun() {
x := test.Foo(3)
C.close(3)
fmt.Println(x)
}
`,
},
},
wantErr: true,
wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:",
}, {
// Test: from identifier doesn't appear in cgo file that includes modified package -> rename successful
from: `"test".Foo::x`, to: "y",
packages: map[string][]string{
"test": []string{
`package test
func Foo(x int) (int){
return x * 2
}
`,
},
"main": []string{
`package main
import "test"
import "fmt"
// #include <unistd.h>
import "C"
func fun() {
x := test.Foo(3)
C.close(3)
fmt.Println(x)
}
`,
},
},
wantErr: false,
wantOut: "Renamed 2 occurrences in 1 file in 1 package.",
}, {
// Test: from name appears in cgo file in same package -> error
from: `"mytest"::f`, to: "g",
packages: map[string][]string{
"mytest": []string{`package mytest; func f() {}`,
`package mytest
// #include <stdio.h>
import "C"
func z() {C.puts(nil); f()}`,
`package mytest
// #include <unistd.h>
import "C"
func foo() {C.close(3); f()}`,
},
},
wantErr: true,
wantOut: "gorename: refusing to modify generated files containing DO NOT EDIT marker:",
}, {
// Test: from name in file, identifier not used in cgo file -> rename successful
from: filepath.Join("mytest", "0.go") + `::f`, to: "g",
fileSpecified: true,
packages: map[string][]string{
"mytest": []string{`package mytest; func f() {}`,
`package mytest
// #include <stdio.h>
import "C"
func z() {C.puts(nil)}`},
},
wantErr: false,
wantOut: "Renamed 1 occurrence in 1 file in 1 package.",
}, {
// Test: from identifier imported to another package but does not modify cgo file -> rename successful
from: `"test".Foo`, to: "Bar",
packages: map[string][]string{
"test": []string{
`package test
func Foo(x int) (int){
return x * 2
}
`,
},
"main": []string{
`package main
// #include <unistd.h>
import "C"
func fun() {
C.close(3)
}
`,
`package main
import "test"
import "fmt"
func g() { fmt.Println(test.Foo(3)) }
`,
},
},
wantErr: false,
wantOut: "Renamed 2 occurrences in 2 files in 2 packages.",
},
} {
// Write the test files
testCleanup := setUpPackages(t, srcDir, renameTest.packages)
// Set up arguments
var args []string
var arg, val string
if renameTest.offset != "" {
arg, val = "-offset", renameTest.offset
} else {
arg, val = "-from", renameTest.from
}
prefix := fmt.Sprintf("%d: %s %q -to %q", iter, arg, val, renameTest.to)
if renameTest.fileSpecified {
// add the src dir to the value of the argument
val = filepath.Join(srcDir, val)
}
args = append(args, arg, val, "-to", renameTest.to)
// Run command
cmd := exec.Command(bin, args...)
cmd.Args[0] = "gorename"
cmd.Env = env
// Check the output
out, err := cmd.CombinedOutput()
// errors should result in no changes to files
if err != nil {
if !renameTest.wantErr {
t.Errorf("%s: received unexpected error %s", prefix, err)
}
// Compare output
if ok := strings.Contains(string(out), renameTest.wantOut); !ok {
t.Errorf("%s: unexpected command output: %s (want: %s)", prefix, out, renameTest.wantOut)
}
// Check that no files were modified
if modified := modifiedFiles(t, srcDir, renameTest.packages); len(modified) != 0 {
t.Errorf("%s: files unexpectedly modified: %s", prefix, modified)
}
} else {
if !renameTest.wantErr {
if ok := strings.Contains(string(out), renameTest.wantOut); !ok {
t.Errorf("%s: unexpected command output: %s (want: %s)", prefix, out, renameTest.wantOut)
}
} else {
t.Errorf("%s: command succeeded unexpectedly, output: %s", prefix, out)
}
}
testCleanup()
}
}
// buildGorename builds the gorename executable.
// It returns its path, and a cleanup function.
func buildGorename(t *testing.T) (tmp, bin string, cleanup func()) {
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
tmp, err := ioutil.TempDir("", "gorename-regtest-")
if err != nil {
t.Fatal(err)
}
defer func() {
if cleanup == nil { // probably, go build failed.
os.RemoveAll(tmp)
}
}()
bin = filepath.Join(tmp, "gorename")
if runtime.GOOS == "windows" {
bin += ".exe"
}
cmd := exec.Command("go", "build", "-o", bin)
if err := cmd.Run(); err != nil {
t.Fatalf("Building gorename: %v", err)
}
return tmp, bin, func() { os.RemoveAll(tmp) }
}
// setUpPackages sets up the files in a temporary directory provided by arguments.
func setUpPackages(t *testing.T, dir string, packages map[string][]string) (cleanup func()) {
var pkgDirs []string
for pkgName, files := range packages {
// Create a directory for the package.
pkgDir := filepath.Join(dir, pkgName)
pkgDirs = append(pkgDirs, pkgDir)
if err := os.Mkdir(pkgDir, os.ModePerm); err != nil {
t.Fatal(err)
}
// Write the packages files
for i, val := range files {
file := filepath.Join(pkgDir, strconv.Itoa(i)+".go")
if err := ioutil.WriteFile(file, []byte(val), os.ModePerm); err != nil {
t.Fatal(err)
}
}
}
return func() {
for _, dir := range pkgDirs {
os.RemoveAll(dir)
}
}
}
// modifiedFiles returns a list of files that were renamed (without the prefix dir).
func modifiedFiles(t *testing.T, dir string, packages map[string][]string) (results []string) {
for pkgName, files := range packages {
pkgDir := filepath.Join(dir, pkgName)
for i, val := range files {
file := filepath.Join(pkgDir, strconv.Itoa(i)+".go")
// read file contents and compare to val
if contents, err := ioutil.ReadFile(file); err != nil {
t.Fatalf("File missing: %s", err)
} else if string(contents) != val {
results = append(results, strings.TrimPrefix(dir, file))
}
}
}
return results
}

62
cmd/gotype/doc.go Normal file
View File

@ -0,0 +1,62 @@
// 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.
/*
The gotype command does syntactic and semantic analysis of Go files
and packages like the front-end of a Go compiler. Errors are reported
if the analysis fails; otherwise gotype is quiet (unless -v is set).
Without a list of paths, gotype reads from standard input, which
must provide a single Go source file defining a complete package.
If a single path is specified that is a directory, gotype checks
the Go files in that directory; they must all belong to the same
package.
Otherwise, each path must be the filename of Go file belonging to
the same package.
Usage:
gotype [flags] [path...]
The flags are:
-a
use all (incl. _test.go) files when processing a directory
-e
report all errors (not just the first 10)
-v
verbose mode
-gccgo
use gccimporter instead of gcimporter
Debugging flags:
-seq
parse sequentially, rather than in parallel
-ast
print AST (forces -seq)
-trace
print parse trace (forces -seq)
-comments
parse comments (ignored unless -ast or -trace is provided)
Examples:
To check the files a.go, b.go, and c.go:
gotype a.go b.go c.go
To check an entire package in the directory dir and print the processed files:
gotype -v dir
To check an entire package including tests in the local directory:
gotype -a .
To verify the output of a pipe:
echo "package foo" | gotype
*/
package main // import "golang.org/x/tools/cmd/gotype"

View File

@ -2,87 +2,8 @@
// 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.
// gotype.go is a copy of the original source maintained // +build go1.5
// in $GOROOT/src/go/types/gotype.go, but with the call
// to types.SizesFor factored out so we can provide a local
// implementation when compiling against Go 1.8 and earlier.
//
// This code is here for the sole purpose of satisfying historic
// references to this location, and for making gotype accessible
// via 'go get'.
//
// Do NOT make changes to this version as they will not be maintained
// (and possibly overwritten). Any changes should be made to the original
// and then ported to here.
/*
The gotype command, like the front-end of a Go compiler, parses and
type-checks a single Go package. Errors are reported if the analysis
fails; otherwise gotype is quiet (unless -v is set).
Without a list of paths, gotype reads from standard input, which
must provide a single Go source file defining a complete package.
With a single directory argument, gotype checks the Go files in
that directory, comprising a single package. Use -t to include the
(in-package) _test.go files. Use -x to type check only external
test files.
Otherwise, each path must be the filename of a Go file belonging
to the same package.
Imports are processed by importing directly from the source of
imported packages (default), or by importing from compiled and
installed packages (by setting -c to the respective compiler).
The -c flag must be set to a compiler ("gc", "gccgo") when type-
checking packages containing imports with relative import paths
(import "./mypkg") because the source importer cannot know which
files to include for such packages.
Usage:
gotype [flags] [path...]
The flags are:
-t
include local test files in a directory (ignored if -x is provided)
-x
consider only external test files in a directory
-e
report all errors (not just the first 10)
-v
verbose mode
-c
compiler used for installed packages (gc, gccgo, or source); default: source
Flags controlling additional output:
-ast
print AST (forces -seq)
-trace
print parse trace (forces -seq)
-comments
parse comments (ignored unless -ast or -trace is provided)
Examples:
To check the files a.go, b.go, and c.go:
gotype a.go b.go c.go
To check an entire package including (in-package) tests in the directory dir and print the processed files:
gotype -t -v dir
To check the external test package (if any) in the current directory, based on installed packages compiled with
cmd/compile:
gotype -c=gc -x .
To verify the output of a pipe:
echo "package foo" | gotype
*/
package main package main
import ( import (
@ -98,19 +19,18 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"time" "time"
) )
var ( var (
// main operation modes // main operation modes
testFiles = flag.Bool("t", false, "include in-package test files in a directory") allFiles = flag.Bool("a", false, "use all (incl. _test.go) files when processing a directory")
xtestFiles = flag.Bool("x", false, "consider only external test files in a directory") allErrors = flag.Bool("e", false, "report all errors (not just the first 10)")
allErrors = flag.Bool("e", false, "report all errors, not just the first 10") verbose = flag.Bool("v", false, "verbose mode")
verbose = flag.Bool("v", false, "verbose mode") gccgo = flag.Bool("gccgo", false, "use gccgoimporter instead of gcimporter")
compiler = flag.String("c", defaultCompiler, "compiler used for installed packages (gc, gccgo, or source)")
// additional output control // debugging support
sequential = flag.Bool("seq", false, "parse sequentially, rather than in parallel")
printAST = flag.Bool("ast", false, "print AST (forces -seq)") printAST = flag.Bool("ast", false, "print AST (forces -seq)")
printTrace = flag.Bool("trace", false, "print parse trace (forces -seq)") printTrace = flag.Bool("trace", false, "print parse trace (forces -seq)")
parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)") parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
@ -119,55 +39,36 @@ var (
var ( var (
fset = token.NewFileSet() fset = token.NewFileSet()
errorCount = 0 errorCount = 0
sequential = false
parserMode parser.Mode parserMode parser.Mode
sizes types.Sizes
) )
func initParserMode() { func initParserMode() {
if *allErrors { if *allErrors {
parserMode |= parser.AllErrors parserMode |= parser.AllErrors
} }
if *printAST {
sequential = true
}
if *printTrace { if *printTrace {
parserMode |= parser.Trace parserMode |= parser.Trace
sequential = true
} }
if *parseComments && (*printAST || *printTrace) { if *parseComments && (*printAST || *printTrace) {
parserMode |= parser.ParseComments parserMode |= parser.ParseComments
} }
} }
const usageString = `usage: gotype [flags] [path ...] func initSizes() {
wordSize := 8
The gotype command, like the front-end of a Go compiler, parses and maxAlign := 8
type-checks a single Go package. Errors are reported if the analysis switch build.Default.GOARCH {
fails; otherwise gotype is quiet (unless -v is set). case "386", "arm":
wordSize = 4
Without a list of paths, gotype reads from standard input, which maxAlign = 4
must provide a single Go source file defining a complete package. // add more cases as needed
}
With a single directory argument, gotype checks the Go files in sizes = &types.StdSizes{WordSize: int64(wordSize), MaxAlign: int64(maxAlign)}
that directory, comprising a single package. Use -t to include the }
(in-package) _test.go files. Use -x to type check only external
test files.
Otherwise, each path must be the filename of a Go file belonging
to the same package.
Imports are processed by importing directly from the source of
imported packages (default), or by importing from compiled and
installed packages (by setting -c to the respective compiler).
The -c flag must be set to a compiler ("gc", "gccgo") when type-
checking packages containing imports with relative import paths
(import "./mypkg") because the source importer cannot know which
files to include for such packages.
`
func usage() { func usage() {
fmt.Fprintln(os.Stderr, usageString) fmt.Fprintln(os.Stderr, "usage: gotype [flags] [path ...]")
flag.PrintDefaults() flag.PrintDefaults()
os.Exit(2) os.Exit(2)
} }
@ -201,49 +102,60 @@ func parseStdin() (*ast.File, error) {
return parse("<standard input>", src) return parse("<standard input>", src)
} }
func parseFiles(dir string, filenames []string) ([]*ast.File, error) { func parseFiles(filenames []string) ([]*ast.File, error) {
files := make([]*ast.File, len(filenames)) files := make([]*ast.File, len(filenames))
errors := make([]error, len(filenames))
var wg sync.WaitGroup if *sequential {
for i, filename := range filenames { for i, filename := range filenames {
wg.Add(1) var err error
go func(i int, filepath string) { files[i], err = parse(filename, nil)
defer wg.Done() if err != nil {
files[i], errors[i] = parse(filepath, nil) return nil, err // leave unfinished goroutines hanging
}(i, filepath.Join(dir, filename)) }
if sequential { }
wg.Wait() } else {
type parseResult struct {
file *ast.File
err error
} }
}
wg.Wait()
// if there are errors, return the first one for deterministic results out := make(chan parseResult)
for _, err := range errors { for _, filename := range filenames {
if err != nil { go func(filename string) {
return nil, err file, err := parse(filename, nil)
out <- parseResult{file, err}
}(filename)
}
for i := range filenames {
res := <-out
if res.err != nil {
return nil, res.err // leave unfinished goroutines hanging
}
files[i] = res.file
} }
} }
return files, nil return files, nil
} }
func parseDir(dir string) ([]*ast.File, error) { func parseDir(dirname string) ([]*ast.File, error) {
ctxt := build.Default ctxt := build.Default
pkginfo, err := ctxt.ImportDir(dir, 0) pkginfo, err := ctxt.ImportDir(dirname, 0)
if _, nogo := err.(*build.NoGoError); err != nil && !nogo { if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
return nil, err return nil, err
} }
if *xtestFiles {
return parseFiles(dir, pkginfo.XTestGoFiles)
}
filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...) filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
if *testFiles { if *allFiles {
filenames = append(filenames, pkginfo.TestGoFiles...) filenames = append(filenames, pkginfo.TestGoFiles...)
} }
return parseFiles(dir, filenames)
// complete file names
for i, filename := range filenames {
filenames[i] = filepath.Join(dirname, filename)
}
return parseFiles(filenames)
} }
func getPkgFiles(args []string) ([]*ast.File, error) { func getPkgFiles(args []string) ([]*ast.File, error) {
@ -269,13 +181,15 @@ func getPkgFiles(args []string) ([]*ast.File, error) {
} }
// list of files // list of files
return parseFiles("", args) return parseFiles(args)
} }
func checkPkgFiles(files []*ast.File) { func checkPkgFiles(files []*ast.File) {
compiler := "gc"
if *gccgo {
compiler = "gccgo"
}
type bailout struct{} type bailout struct{}
// if checkPkgFiles is called multiple times, set up conf only once
conf := types.Config{ conf := types.Config{
FakeImportC: true, FakeImportC: true,
Error: func(err error) { Error: func(err error) {
@ -284,8 +198,8 @@ func checkPkgFiles(files []*ast.File) {
} }
report(err) report(err)
}, },
Importer: importer.For(*compiler, nil), Importer: importer.For(compiler, nil),
Sizes: SizesFor(build.Default.Compiler, build.Default.GOARCH), Sizes: sizes,
} }
defer func() { defer func() {
@ -320,7 +234,11 @@ func printStats(d time.Duration) {
func main() { func main() {
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
if *printAST || *printTrace {
*sequential = true
}
initParserMode() initParserMode()
initSizes()
start := time.Now() start := time.Now()

View File

@ -1,40 +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
// This file contains a copy of the implementation of types.SizesFor
// since this function is not available in go/types before Go 1.9.
package main
import "go/types"
const defaultCompiler = "gc"
var gcArchSizes = map[string]*types.StdSizes{
"386": {4, 4},
"arm": {4, 4},
"arm64": {8, 8},
"amd64": {8, 8},
"amd64p32": {4, 8},
"mips": {4, 4},
"mipsle": {4, 4},
"mips64": {8, 8},
"mips64le": {8, 8},
"ppc64": {8, 8},
"ppc64le": {8, 8},
"s390x": {8, 8},
}
func SizesFor(compiler, arch string) types.Sizes {
if compiler != "gc" {
return nil
}
s, ok := gcArchSizes[arch]
if !ok {
return nil
}
return s
}

View File

@ -1,15 +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"
const defaultCompiler = "source"
func SizesFor(compiler, arch string) types.Sizes {
return types.SizesFor(compiler, arch)
}

View File

@ -387,8 +387,6 @@ func setup() {
yaccpar = strings.Replace(yaccpartext, "$$", prefix, -1) yaccpar = strings.Replace(yaccpartext, "$$", prefix, -1)
openup() openup()
fmt.Fprintf(ftable, "// Code generated by goyacc %s. DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
defin(0, "$end") defin(0, "$end")
extval = PRIVATE // tokens start in unicode 'private use' extval = PRIVATE // tokens start in unicode 'private use'
defin(0, "error") defin(0, "error")
@ -408,7 +406,6 @@ outer:
break outer break outer
case ';': case ';':
// Do nothing.
case START: case START:
t = gettok() t = gettok()
@ -501,7 +498,7 @@ outer:
continue continue
case ';': case ';':
// Do nothing. break
case IDENTIFIER: case IDENTIFIER:
j = chfind(0, tokname) j = chfind(0, tokname)
@ -1228,7 +1225,9 @@ func writecode(code []rune) {
// skipcom is called after reading a '/' // skipcom is called after reading a '/'
// //
func skipcom() int { func skipcom() int {
c := getrune(finput) var c rune
c = getrune(finput)
if c == '/' { if c == '/' {
for c != EOF { for c != EOF {
if c == '\n' { if c == '\n' {
@ -1266,13 +1265,25 @@ l1:
return nl return nl
} }
func dumpprod(curprod []int, max int) {
fmt.Printf("\n")
for i := 0; i < max; i++ {
p := curprod[i]
if p < 0 {
fmt.Printf("[%v] %v\n", i, p)
} else {
fmt.Printf("[%v] %v\n", i, symnam(p))
}
}
}
// //
// copy action to the next ; or closing } // copy action to the next ; or closing }
// //
func cpyact(curprod []int, max int) { func cpyact(curprod []int, max int) {
if !lflag { if !lflag {
fmt.Fprintf(fcode, "\n//line %v:%v", infile, lineno) fmt.Fprintf(fcode, "\n\t\t//line %v:%v", infile, lineno)
} }
fmt.Fprint(fcode, "\n\t\t") fmt.Fprint(fcode, "\n\t\t")
@ -1292,6 +1303,8 @@ loop:
} }
case '{': case '{':
if brac == 0 {
}
brac++ brac++
case '$': case '$':
@ -1414,7 +1427,8 @@ loop:
if nnc == '/' { if nnc == '/' {
fcode.WriteRune('*') fcode.WriteRune('*')
fcode.WriteRune('/') fcode.WriteRune('/')
continue loop c = getrune(finput)
break swt
} }
ungetrune(finput, nnc) ungetrune(finput, nnc)
} }
@ -1551,6 +1565,21 @@ func cpres() {
} }
} }
func dumppres() {
for i := 0; i <= nnonter; i++ {
fmt.Printf("nonterm %d\n", i)
curres := pres[i]
for j := 0; j < len(curres); j++ {
fmt.Printf("\tproduction %d:", j)
prd := curres[j]
for k := 0; k < len(prd); k++ {
fmt.Printf(" %d", prd[k])
}
fmt.Print("\n")
}
}
}
// //
// mark nonterminals which derive the empty string // mark nonterminals which derive the empty string
// also, look for nonterminals which don't derive any token strings // also, look for nonterminals which don't derive any token strings
@ -1637,6 +1666,14 @@ again:
} }
} }
func dumpempty() {
for i := 0; i <= nnonter; i++ {
if pempty[i] == EMPTY {
fmt.Printf("non-term %d %s matches empty\n", i, symnam(i+NTBASE))
}
}
}
// //
// compute an array with the first of nonterminals // compute an array with the first of nonterminals
// //
@ -2234,7 +2271,11 @@ func output() {
fmt.Fprintf(ftable, "}\n") fmt.Fprintf(ftable, "}\n")
ftable.WriteRune('\n') ftable.WriteRune('\n')
fmt.Fprintf(ftable, "const %sNprod = %v\n", prefix, nprod)
fmt.Fprintf(ftable, "const %sPrivate = %v\n", prefix, PRIVATE) fmt.Fprintf(ftable, "const %sPrivate = %v\n", prefix, PRIVATE)
ftable.WriteRune('\n')
fmt.Fprintf(ftable, "var %sTokenNames []string\n", prefix)
fmt.Fprintf(ftable, "var %sStates []string\n", prefix)
} }
// //
@ -2244,7 +2285,7 @@ func output() {
// temp1[t] is changed to reflect the action // temp1[t] is changed to reflect the action
// //
func precftn(r, t, s int) { func precftn(r, t, s int) {
action := NOASC var action int
lp := levprd[r] lp := levprd[r]
lt := toklev[t] lt := toklev[t]
@ -3193,6 +3234,10 @@ func ungetrune(f *bufio.Reader, c rune) {
peekrune = c peekrune = c
} }
func write(f *bufio.Writer, b []byte, n int) int {
panic("write")
}
func open(s string) *bufio.Reader { func open(s string) *bufio.Reader {
fi, err := os.Open(s) fi, err := os.Open(s)
if err != nil { if err != nil {

View File

@ -18,7 +18,7 @@ import (
"golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/ssa/ssautil"
) )
// The callees function reports the possible callees of the function call site // Callees reports the possible callees of the function call site
// identified by the specified source location. // identified by the specified source location.
func callees(q *Query) error { func callees(q *Query) error {
lconf := loader.Config{Build: q.Build} lconf := loader.Config{Build: q.Build}
@ -240,7 +240,7 @@ func (r *calleesTypesResult) JSON(fset *token.FileSet) []byte {
Desc: "static function call", Desc: "static function call",
} }
j.Callees = []*serial.Callee{ j.Callees = []*serial.Callee{
{ &serial.Callee{
Name: r.callee.FullName(), Name: r.callee.FullName(),
Pos: fset.Position(r.callee.Pos()).String(), Pos: fset.Position(r.callee.Pos()).String(),
}, },

View File

@ -16,7 +16,7 @@ import (
"golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/ssa/ssautil"
) )
// The callers function reports the possible callers of the function // Callers reports the possible callers of the function
// immediately enclosing the specified source location. // immediately enclosing the specified source location.
// //
func callers(q *Query) error { func callers(q *Query) error {

View File

@ -16,7 +16,7 @@ import (
"golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/ssa/ssautil"
) )
// The callstack function displays an arbitrary path from a root of the callgraph // Callstack displays an arbitrary path from a root of the callgraph
// to the function at the current position. // to the function at the current position.
// //
// The information may be misleading in a context-insensitive // The information may be misleading in a context-insensitive

View File

@ -8,7 +8,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"go/ast" "go/ast"
"go/constant" exact "go/constant"
"go/token" "go/token"
"go/types" "go/types"
"os" "os"
@ -162,9 +162,6 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
path = append([]ast.Node{n.Name}, path...) path = append([]ast.Node{n.Name}, path...)
continue continue
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
return path, actionUnknown // uninteresting
case ast.Stmt: case ast.Stmt:
return path, actionStmt return path, actionStmt
@ -176,6 +173,9 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
*ast.ChanType: *ast.ChanType:
return path, actionType return path, actionType
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
return path, actionUnknown // uninteresting
case *ast.Ellipsis: case *ast.Ellipsis:
// Continue to enclosing node. // Continue to enclosing node.
// e.g. [...]T in ArrayType // e.g. [...]T in ArrayType
@ -327,9 +327,9 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error
return nil, fmt.Errorf("unexpected AST for expr: %T", n) return nil, fmt.Errorf("unexpected AST for expr: %T", n)
} }
typ := qpos.info.TypeOf(expr) t := qpos.info.TypeOf(expr)
if typ == nil { if t == nil {
typ = types.Typ[types.Invalid] t = types.Typ[types.Invalid]
} }
constVal := qpos.info.Types[expr].Value constVal := qpos.info.Types[expr].Value
if c, ok := obj.(*types.Const); ok { if c, ok := obj.(*types.Const); ok {
@ -339,45 +339,20 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error
return &describeValueResult{ return &describeValueResult{
qpos: qpos, qpos: qpos,
expr: expr, expr: expr,
typ: typ, typ: t,
names: appendNames(nil, typ),
constVal: constVal, constVal: constVal,
obj: obj, obj: obj,
methods: accessibleMethods(typ, qpos.info.Pkg), methods: accessibleMethods(t, qpos.info.Pkg),
fields: accessibleFields(typ, qpos.info.Pkg), fields: accessibleFields(t, qpos.info.Pkg),
}, nil }, nil
} }
// appendNames returns named types found within the Type by
// removing map, pointer, channel, slice, and array constructors.
// It does not descend into structs or interfaces.
func appendNames(names []*types.Named, typ types.Type) []*types.Named {
// elemType specifies type that has some element in it
// such as array, slice, chan, pointer
type elemType interface {
Elem() types.Type
}
switch t := typ.(type) {
case *types.Named:
names = append(names, t)
case *types.Map:
names = appendNames(names, t.Key())
names = appendNames(names, t.Elem())
case elemType:
names = appendNames(names, t.Elem())
}
return names
}
type describeValueResult struct { type describeValueResult struct {
qpos *queryPos qpos *queryPos
expr ast.Expr // query node expr ast.Expr // query node
typ types.Type // type of expression typ types.Type // type of expression
names []*types.Named // named types within typ constVal exact.Value // value of expression, if constant
constVal constant.Value // value of expression, if constant obj types.Object // var/func/const object, if expr was Ident
obj types.Object // var/func/const object, if expr was Ident
methods []*types.Selection methods []*types.Selection
fields []describeField fields []describeField
} }
@ -423,7 +398,6 @@ func (r *describeValueResult) PrintPlain(printf printfFunc) {
printMethods(printf, r.expr, r.methods) printMethods(printf, r.expr, r.methods)
printFields(printf, r.expr, r.fields) printFields(printf, r.expr, r.fields)
printNamedTypes(printf, r.expr, r.names)
} }
func (r *describeValueResult) JSON(fset *token.FileSet) []byte { func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
@ -435,23 +409,14 @@ func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
objpos = fset.Position(r.obj.Pos()).String() objpos = fset.Position(r.obj.Pos()).String()
} }
typesPos := make([]serial.Definition, len(r.names))
for i, t := range r.names {
typesPos[i] = serial.Definition{
ObjPos: fset.Position(t.Obj().Pos()).String(),
Desc: r.qpos.typeString(t),
}
}
return toJSON(&serial.Describe{ return toJSON(&serial.Describe{
Desc: astutil.NodeDescription(r.expr), Desc: astutil.NodeDescription(r.expr),
Pos: fset.Position(r.expr.Pos()).String(), Pos: fset.Position(r.expr.Pos()).String(),
Detail: "value", Detail: "value",
Value: &serial.DescribeValue{ Value: &serial.DescribeValue{
Type: r.qpos.typeString(r.typ), Type: r.qpos.typeString(r.typ),
TypesPos: typesPos, Value: value,
Value: value, ObjPos: objpos,
ObjPos: objpos,
}, },
}) })
} }
@ -460,46 +425,48 @@ func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) { func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
var description string var description string
var typ types.Type var t types.Type
switch n := path[0].(type) { switch n := path[0].(type) {
case *ast.Ident: case *ast.Ident:
obj := qpos.info.ObjectOf(n).(*types.TypeName) t = qpos.info.TypeOf(n)
typ = obj.Type() switch t := t.(type) {
if isAlias(obj) { case *types.Basic:
description = "alias of "
} else if obj.Pos() == n.Pos() {
description = "definition of " // (Named type)
} else if _, ok := typ.(*types.Basic); ok {
description = "reference to built-in " description = "reference to built-in "
} else {
description = "reference to " // (Named type) case *types.Named:
isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above
if isDef {
description = "definition of "
} else {
description = "reference to "
}
} }
case ast.Expr: case ast.Expr:
typ = qpos.info.TypeOf(n) t = qpos.info.TypeOf(n)
default: default:
// Unreachable? // Unreachable?
return nil, fmt.Errorf("unexpected AST for type: %T", n) return nil, fmt.Errorf("unexpected AST for type: %T", n)
} }
description = description + "type " + qpos.typeString(typ) description = description + "type " + qpos.typeString(t)
// Show sizes for structs and named types (it's fairly obvious for others). // Show sizes for structs and named types (it's fairly obvious for others).
switch typ.(type) { switch t.(type) {
case *types.Named, *types.Struct: case *types.Named, *types.Struct:
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64 szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
description = fmt.Sprintf("%s (size %d, align %d)", description, description = fmt.Sprintf("%s (size %d, align %d)", description,
szs.Sizeof(typ), szs.Alignof(typ)) szs.Sizeof(t), szs.Alignof(t))
} }
return &describeTypeResult{ return &describeTypeResult{
qpos: qpos, qpos: qpos,
node: path[0], node: path[0],
description: description, description: description,
typ: typ, typ: t,
methods: accessibleMethods(typ, qpos.info.Pkg), methods: accessibleMethods(t, qpos.info.Pkg),
fields: accessibleFields(typ, qpos.info.Pkg), fields: accessibleFields(t, qpos.info.Pkg),
}, nil }, nil
} }
@ -559,19 +526,6 @@ func printFields(printf printfFunc, node ast.Node, fields []describeField) {
} }
} }
func printNamedTypes(printf printfFunc, node ast.Node, names []*types.Named) {
if len(names) > 0 {
printf(node, "Named types:")
}
for _, t := range names {
// Print the type relative to the package
// in which it was defined, not the query package,
printf(t.Obj(), "\ttype %s defined here",
types.TypeString(t.Obj().Type(), types.RelativeTo(t.Obj().Pkg())))
}
}
func (r *describeTypeResult) PrintPlain(printf printfFunc) { func (r *describeTypeResult) PrintPlain(printf printfFunc) {
printf(r.node, "%s", r.description) printf(r.node, "%s", r.description)
@ -718,29 +672,23 @@ func formatMember(obj types.Object, maxname int) string {
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier)) fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
case *types.TypeName: case *types.TypeName:
typ := obj.Type()
if isAlias(obj) {
buf.WriteString(" = ")
} else {
buf.WriteByte(' ')
typ = typ.Underlying()
}
var typestr string
// Abbreviate long aggregate type names. // Abbreviate long aggregate type names.
switch typ := typ.(type) { var abbrev string
switch t := obj.Type().Underlying().(type) {
case *types.Interface: case *types.Interface:
if typ.NumMethods() > 1 { if t.NumMethods() > 1 {
typestr = "interface{...}" abbrev = "interface{...}"
} }
case *types.Struct: case *types.Struct:
if typ.NumFields() > 1 { if t.NumFields() > 1 {
typestr = "struct{...}" abbrev = "struct{...}"
} }
} }
if typestr == "" { if abbrev == "" {
typestr = types.TypeString(typ, qualifier) fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type().Underlying(), qualifier))
} else {
fmt.Fprintf(&buf, " %s", abbrev)
} }
buf.WriteString(typestr)
case *types.Var: case *types.Var:
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier)) fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
@ -751,26 +699,20 @@ func formatMember(obj types.Object, maxname int) string {
func (r *describePackageResult) JSON(fset *token.FileSet) []byte { func (r *describePackageResult) JSON(fset *token.FileSet) []byte {
var members []*serial.DescribeMember var members []*serial.DescribeMember
for _, mem := range r.members { for _, mem := range r.members {
obj := mem.obj typ := mem.obj.Type()
typ := obj.Type()
var val string var val string
var alias string switch mem := mem.obj.(type) {
switch obj := obj.(type) {
case *types.Const: case *types.Const:
val = obj.Val().String() val = mem.Val().String()
case *types.TypeName: case *types.TypeName:
if isAlias(obj) { typ = typ.Underlying()
alias = "= " // kludgy
} else {
typ = typ.Underlying()
}
} }
members = append(members, &serial.DescribeMember{ members = append(members, &serial.DescribeMember{
Name: obj.Name(), Name: mem.obj.Name(),
Type: alias + typ.String(), Type: typ.String(),
Value: val, Value: val,
Pos: fset.Position(obj.Pos()).String(), Pos: fset.Position(mem.obj.Pos()).String(),
Kind: tokenOf(obj), Kind: tokenOf(mem.obj),
Methods: methodsToSerial(r.pkg, mem.methods, fset), Methods: methodsToSerial(r.pkg, mem.methods, fset),
}) })
} }

View File

@ -65,6 +65,11 @@ func (qpos *queryPos) objectString(obj types.Object) string {
return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg)) return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg))
} }
// SelectionString prints selection sel relative to the query position.
func (qpos *queryPos) selectionString(sel *types.Selection) string {
return types.SelectionString(sel, types.RelativeTo(qpos.info.Pkg))
}
// A Query specifies a single guru query. // A Query specifies a single guru query.
type Query struct { type Query struct {
Pos string // query position Pos string // query position
@ -75,7 +80,7 @@ type Query struct {
PTALog io.Writer // (optional) pointer-analysis log file PTALog io.Writer // (optional) pointer-analysis log file
Reflection bool // model reflection soundly (currently slow). Reflection bool // model reflection soundly (currently slow).
// result-printing function, safe for concurrent use // result-printing function
Output func(*token.FileSet, QueryResult) Output func(*token.FileSet, QueryResult)
} }

View File

@ -6,7 +6,7 @@ package main_test
// This file defines a test framework for guru queries. // This file defines a test framework for guru queries.
// //
// The files beneath testdata/src contain Go programs containing // The files beneath testdata/src/main contain Go programs containing
// query annotations of the form: // query annotations of the form:
// //
// @verb id "select" // @verb id "select"
@ -35,7 +35,6 @@ import (
"go/token" "go/token"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -50,18 +49,6 @@ import (
guru "golang.org/x/tools/cmd/guru" guru "golang.org/x/tools/cmd/guru"
) )
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)
}
}
var updateFlag = flag.Bool("update", false, "Update the golden files.") var updateFlag = flag.Bool("update", false, "Update the golden files.")
type query struct { type query struct {
@ -223,11 +210,6 @@ func doQuery(out io.Writer, q *query, json bool) {
} }
func TestGuru(t *testing.T) { func TestGuru(t *testing.T) {
if testing.Short() {
// These tests are super slow.
// TODO: make a lighter version of the tests for short mode?
t.Skipf("skipping in short mode")
}
switch runtime.GOOS { switch runtime.GOOS {
case "android": case "android":
t.Skipf("skipping test on %q (no testdata dir)", runtime.GOOS) t.Skipf("skipping test on %q (no testdata dir)", runtime.GOOS)
@ -236,7 +218,6 @@ func TestGuru(t *testing.T) {
} }
for _, filename := range []string{ for _, filename := range []string{
"testdata/src/alias/alias.go",
"testdata/src/calls/main.go", "testdata/src/calls/main.go",
"testdata/src/describe/main.go", "testdata/src/describe/main.go",
"testdata/src/freevars/main.go", "testdata/src/freevars/main.go",
@ -262,55 +243,52 @@ func TestGuru(t *testing.T) {
"testdata/src/referrers-json/main.go", "testdata/src/referrers-json/main.go",
"testdata/src/what-json/main.go", "testdata/src/what-json/main.go",
} { } {
filename := filename if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
name := strings.Split(filename, "/")[2] // Disable this test on plan9 since it expects a particular
t.Run(name, func(t *testing.T) { // wording for a "no such file or directory" error.
t.Parallel() continue
if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" { }
// Disable this test on plan9 since it expects a particular
// wording for a "no such file or directory" error.
t.Skip()
}
json := strings.Contains(filename, "-json/")
queries := parseQueries(t, filename)
golden := filename + "lden"
got := filename + "t"
gotfh, err := os.Create(got)
if err != nil {
t.Fatalf("Create(%s) failed: %s", got, err)
}
defer os.Remove(got)
defer gotfh.Close()
// Run the guru on each query, redirecting its output json := strings.Contains(filename, "-json/")
// and error (if any) to the foo.got file. queries := parseQueries(t, filename)
for _, q := range queries { golden := filename + "lden"
doQuery(gotfh, q, json) got := filename + "t"
} gotfh, err := os.Create(got)
if err != nil {
t.Errorf("Create(%s) failed: %s", got, err)
continue
}
defer gotfh.Close()
defer os.Remove(got)
// Compare foo.got with foo.golden. // Run the guru on each query, redirecting its output
var cmd *exec.Cmd // and error (if any) to the foo.got file.
switch runtime.GOOS { for _, q := range queries {
case "plan9": doQuery(gotfh, q, json)
cmd = exec.Command("/bin/diff", "-c", golden, got) }
default:
cmd = exec.Command("/usr/bin/diff", "-u", golden, got)
}
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Errorf("Guru tests for %s failed: %s.\n%s\n",
filename, err, buf)
if *updateFlag { // Compare foo.got with foo.golden.
t.Logf("Updating %s...", golden) var cmd *exec.Cmd
if err := exec.Command("/bin/cp", got, golden).Run(); err != nil { switch runtime.GOOS {
t.Errorf("Update failed: %s", err) case "plan9":
} cmd = exec.Command("/bin/diff", "-c", golden, got)
default:
cmd = exec.Command("/usr/bin/diff", "-u", golden, got)
}
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Errorf("Guru tests for %s failed: %s.\n%s\n",
filename, err, buf)
if *updateFlag {
t.Logf("Updating %s...", golden)
if err := exec.Command("/bin/cp", got, golden).Run(); err != nil {
t.Errorf("Update failed: %s", err)
} }
} }
}) }
} }
} }

View File

@ -19,7 +19,7 @@ import (
"golang.org/x/tools/refactor/importgraph" "golang.org/x/tools/refactor/importgraph"
) )
// The implements function displays the "implements" relation as it pertains to the // Implements displays the "implements" relation as it pertains to the
// selected type. // selected type.
// If the selection is a method, 'implements' displays // If the selection is a method, 'implements' displays
// the corresponding methods of the types that would have been reported // the corresponding methods of the types that would have been reported
@ -102,20 +102,16 @@ func implements(q *Query) error {
} }
// Find all named types, even local types (which can have // Find all named types, even local types (which can have
// methods due to promotion) and the built-in "error". // methods via promotion) and the built-in "error".
// We ignore aliases 'type M = N' to avoid duplicate var allNamed []types.Type
// reporting of the Named type N.
var allNamed []*types.Named
for _, info := range lprog.AllPackages { for _, info := range lprog.AllPackages {
for _, obj := range info.Defs { for _, obj := range info.Defs {
if obj, ok := obj.(*types.TypeName); ok && !isAlias(obj) { if obj, ok := obj.(*types.TypeName); ok {
if named, ok := obj.Type().(*types.Named); ok { allNamed = append(allNamed, obj.Type())
allNamed = append(allNamed, named)
}
} }
} }
} }
allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named)) allNamed = append(allNamed, types.Universe.Lookup("error").Type())
var msets typeutil.MethodSetCache var msets typeutil.MethodSetCache

View File

@ -19,8 +19,6 @@ import (
"io" "io"
"log" "log"
"os" "os"
"path/filepath"
"runtime"
"runtime/pprof" "runtime/pprof"
"strings" "strings"
"sync" "sync"
@ -40,14 +38,6 @@ var (
func init() { func init() {
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc) flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
// gccgo does not provide a GOROOT with standard library sources.
// If we have one in the environment, force gc mode.
if build.Default.Compiler == "gccgo" {
if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime.go")); err == nil {
build.Default.Compiler = "gc"
}
}
} }
const useHelp = "Run 'guru -help' for more information.\n" const useHelp = "Run 'guru -help' for more information.\n"

View File

@ -9,25 +9,21 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/parser"
"go/token" "go/token"
"go/types" "go/types"
"io" "io"
"log" "log"
"os"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"golang.org/x/tools/cmd/guru/serial" "golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/buildutil" "golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/loader" "golang.org/x/tools/go/loader"
"golang.org/x/tools/imports"
"golang.org/x/tools/refactor/importgraph" "golang.org/x/tools/refactor/importgraph"
) )
// The referrers function reports all identifiers that resolve to the same object // Referrers reports all identifiers that resolve to the same object
// as the queried identifier, within any package in the workspace. // as the queried identifier, within any package in the workspace.
func referrers(q *Query) error { func referrers(q *Query) error {
fset := token.NewFileSet() fset := token.NewFileSet()
@ -38,12 +34,6 @@ func referrers(q *Query) error {
return err return err
} }
// Load tests of the query package
// even if the query location is not in the tests.
for path := range lconf.ImportPkgs {
lconf.ImportPkgs[path] = true
}
// Load/parse/type-check the query package. // Load/parse/type-check the query package.
lprog, err := lconf.Load() lprog, err := lconf.Load()
if err != nil { if err != nil {
@ -80,27 +70,23 @@ func referrers(q *Query) error {
return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name()) return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name())
} }
// For a globally accessible object defined in package P, we
// must load packages that depend on P. Specifically, for a
// package-level object, we need load only direct importers
// of P, but for a field or interface method, we must load
// any package that transitively imports P.
if global, pkglevel := classify(obj); global {
// We'll use the the object's position to identify it in the larger program.
objposn := fset.Position(obj.Pos())
defpkg := obj.Pkg().Path() // defining package
return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn, pkglevel)
}
q.Output(fset, &referrersInitialResult{ q.Output(fset, &referrersInitialResult{
qinfo: qpos.info, qinfo: qpos.info,
obj: obj, obj: obj,
}) })
// For a globally accessible object defined in package P, we
// must load packages that depend on P. Specifically, for a
// package-level object, we need load only direct importers
// of P, but for a field or method, we must load
// any package that transitively imports P.
if global, pkglevel := classify(obj); global {
if pkglevel {
return globalReferrersPkgLevel(q, obj, fset)
}
// We'll use the the object's position to identify it in the larger program.
objposn := fset.Position(obj.Pos())
defpkg := obj.Pkg().Path() // defining package
return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn)
}
outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg()) outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg())
return nil // success return nil // success
@ -224,16 +210,26 @@ func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Pac
} }
// globalReferrers reports references throughout the entire workspace to the // globalReferrers reports references throughout the entire workspace to the
// object (a field or method) at the specified source position. // object at the specified source position. Its defining package is defpkg,
// Its defining package is defpkg, and the query package is qpkg. // and the query package is qpkg. isPkgLevel indicates whether the object
func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) error { // is defined at package-level.
func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position, isPkgLevel bool) error {
// Scan the workspace and build the import graph. // Scan the workspace and build the import graph.
// Ignore broken packages. // Ignore broken packages.
_, rev, _ := importgraph.Build(q.Build) _, rev, _ := importgraph.Build(q.Build)
// Find the set of packages that depend on defpkg. // Find the set of packages that depend on defpkg.
// Only function bodies in those packages need type-checking. // Only function bodies in those packages need type-checking.
users := rev.Search(defpkg) // transitive importers var users map[string]bool
if isPkgLevel {
users = rev[defpkg] // direct importers
if users == nil {
users = make(map[string]bool)
}
users[defpkg] = true // plus the defining package itself
} else {
users = rev.Search(defpkg) // transitive importers
}
// Prepare to load the larger program. // Prepare to load the larger program.
fset := token.NewFileSet() fset := token.NewFileSet()
@ -265,8 +261,9 @@ func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) erro
// to completion. // to completion.
var ( var (
mu sync.Mutex mu sync.Mutex
qobj types.Object qobj types.Object
qinfo *loader.PackageInfo // info for qpkg
) )
// For efficiency, we scan each package for references // For efficiency, we scan each package for references
@ -290,6 +287,13 @@ func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) erro
log.Fatalf("object at %s not found in package %s", log.Fatalf("object at %s not found in package %s",
objposn, defpkg) objposn, defpkg)
} }
// Object found.
qinfo = info
q.Output(fset, &referrersInitialResult{
qinfo: qinfo,
obj: qobj,
})
} }
obj := qobj obj := qobj
mu.Unlock() mu.Unlock()
@ -312,287 +316,6 @@ func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) erro
return nil // success return nil // success
} }
// globalReferrersPkgLevel reports references throughout the entire workspace to the package-level object obj.
// It assumes that the query object itself has already been reported.
func globalReferrersPkgLevel(q *Query, obj types.Object, fset *token.FileSet) error {
// globalReferrersPkgLevel uses go/ast and friends instead of go/types.
// This affords a considerable performance benefit.
// It comes at the cost of some code complexity.
//
// Here's a high level summary.
//
// The goal is to find references to the query object p.Q.
// There are several possible scenarios, each handled differently.
//
// 1. We are looking in a package other than p, and p is not dot-imported.
// This is the simplest case. Q must be referred to as n.Q,
// where n is the name under which p is imported.
// We look at all imports of p to gather all names under which it is imported.
// (In the typical case, it is imported only once, under its default name.)
// Then we look at all selector expressions and report any matches.
//
// 2. We are looking in a package other than p, and p is dot-imported.
// In this case, Q will be referred to just as Q.
// Furthermore, go/ast's object resolution will not be able to resolve
// Q to any other object, unlike any local (file- or function- or block-scoped) object.
// So we look at all matching identifiers and report all unresolvable ones.
//
// 3. We are looking in package p.
// (Care must be taken to separate p and p_test (an xtest package),
// and make sure that they are treated as separate packages.)
// In this case, we give go/ast the entire package for object resolution,
// instead of going file by file.
// We then iterate over all identifiers that resolve to the query object.
// (The query object itself has already been reported, so we don't re-report it.)
//
// We always skip all files that don't contain the string Q, as they cannot be
// relevant to finding references to Q.
//
// We parse all files leniently. In the presence of parsing errors, results are best-effort.
// Scan the workspace and build the import graph.
// Ignore broken packages.
_, rev, _ := importgraph.Build(q.Build)
// Find the set of packages that directly import defpkg.
defpkg := obj.Pkg().Path()
defpkg = strings.TrimSuffix(defpkg, "_test") // package x_test actually has package name x
defpkg = imports.VendorlessPath(defpkg) // remove vendor goop
users := rev[defpkg]
if len(users) == 0 {
users = make(map[string]bool)
}
// We also need to check defpkg itself, and its xtests.
// For the reverse graph packages, we process xtests with the main package.
// defpkg gets special handling; we must distinguish between in-package vs out-of-package.
// To make the control flow below simpler, add defpkg and defpkg xtest placeholders.
// Use "!test" instead of "_test" because "!" is not a valid character in an import path.
// (More precisely, it is not guaranteed to be a valid character in an import path,
// so it is unlikely that it will be in use. See https://golang.org/ref/spec#Import_declarations.)
users[defpkg] = true
users[defpkg+"!test"] = true
cwd, err := os.Getwd()
if err != nil {
return err
}
defname := obj.Pkg().Name() // name of defining package, used for imports using import path only
isxtest := strings.HasSuffix(defname, "_test") // indicates whether the query object is defined in an xtest package
name := obj.Name()
namebytes := []byte(name) // byte slice version of query object name, for early filtering
objpos := fset.Position(obj.Pos()) // position of query object, used to prevent re-emitting original decl
sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
var wg sync.WaitGroup
for u := range users {
u := u
wg.Add(1)
go func() {
defer wg.Done()
uIsXTest := strings.HasSuffix(u, "!test") // indicates whether this package is the special defpkg xtest package
u = strings.TrimSuffix(u, "!test")
// Resolve package.
sema <- struct{}{} // acquire token
pkg, err := q.Build.Import(u, cwd, build.IgnoreVendor)
<-sema // release token
if err != nil {
return
}
// If we're not in the query package,
// the object is in another package regardless,
// so we want to process all files.
// If we are in the query package,
// we want to only process the files that are
// part of that query package;
// that set depends on whether the query package itself is an xtest.
inQueryPkg := u == defpkg && isxtest == uIsXTest
var files []string
if !inQueryPkg || !isxtest {
files = append(files, pkg.GoFiles...)
files = append(files, pkg.TestGoFiles...)
files = append(files, pkg.CgoFiles...) // use raw cgo files, as we're only parsing
}
if !inQueryPkg || isxtest {
files = append(files, pkg.XTestGoFiles...)
}
if len(files) == 0 {
return
}
var deffiles map[string]*ast.File
if inQueryPkg {
deffiles = make(map[string]*ast.File)
}
buf := new(bytes.Buffer) // reusable buffer for reading files
for _, file := range files {
if !buildutil.IsAbsPath(q.Build, file) {
file = buildutil.JoinPath(q.Build, pkg.Dir, file)
}
buf.Reset()
sema <- struct{}{} // acquire token
src, err := readFile(q.Build, file, buf)
<-sema // release token
if err != nil {
continue
}
// Fast path: If the object's name isn't present anywhere in the source, ignore the file.
if !bytes.Contains(src, namebytes) {
continue
}
if inQueryPkg {
// If we're in the query package, we defer final processing until we have
// parsed all of the candidate files in the package.
// Best effort; allow errors and use what we can from what remains.
f, _ := parser.ParseFile(fset, file, src, parser.AllErrors)
if f != nil {
deffiles[file] = f
}
continue
}
// We aren't in the query package. Go file by file.
// Parse out only the imports, to check whether the defining package
// was imported, and if so, under what names.
// Best effort; allow errors and use what we can from what remains.
f, _ := parser.ParseFile(fset, file, src, parser.ImportsOnly|parser.AllErrors)
if f == nil {
continue
}
// pkgnames is the set of names by which defpkg is imported in this file.
// (Multiple imports in the same file are legal but vanishingly rare.)
pkgnames := make([]string, 0, 1)
var isdotimport bool
for _, imp := range f.Imports {
path, err := strconv.Unquote(imp.Path.Value)
if err != nil || path != defpkg {
continue
}
switch {
case imp.Name == nil:
pkgnames = append(pkgnames, defname)
case imp.Name.Name == ".":
isdotimport = true
default:
pkgnames = append(pkgnames, imp.Name.Name)
}
}
if len(pkgnames) == 0 && !isdotimport {
// Defining package not imported, bail.
continue
}
// Re-parse the entire file.
// Parse errors are ok; we'll do the best we can with a partial AST, if we have one.
f, _ = parser.ParseFile(fset, file, src, parser.AllErrors)
if f == nil {
continue
}
// Walk the AST looking for references.
var refs []*ast.Ident
ast.Inspect(f, func(n ast.Node) bool {
// Check selector expressions.
// If the selector matches the target name,
// and the expression is one of the names
// that the defining package was imported under,
// then we have a match.
if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == name {
if id, ok := sel.X.(*ast.Ident); ok {
for _, n := range pkgnames {
if n == id.Name {
refs = append(refs, sel.Sel)
// Don't recurse further, to avoid duplicate entries
// from the dot import check below.
return false
}
}
}
}
// Dot imports are special.
// Objects imported from the defining package are placed in the package scope.
// go/ast does not resolve them to an object.
// At all other scopes (file, local), go/ast can do the resolution.
// So we're looking for object-free idents with the right name.
// The only other way to get something with the right name at the package scope
// is to *be* the defining package. We handle that case separately (inQueryPkg).
if isdotimport {
if id, ok := n.(*ast.Ident); ok && id.Obj == nil && id.Name == name {
refs = append(refs, id)
return false
}
}
return true
})
// Emit any references we found.
if len(refs) > 0 {
q.Output(fset, &referrersPackageResult{
pkg: types.NewPackage(pkg.ImportPath, pkg.Name),
build: q.Build,
fset: fset,
refs: refs,
})
}
}
// If we're in the query package, we've now collected all the files in the package.
// (Or at least the ones that might contain references to the object.)
// Find and emit refs.
if inQueryPkg {
// Bundle the files together into a package.
// This does package-level object resolution.
qpkg, _ := ast.NewPackage(fset, deffiles, nil, nil)
// Look up the query object; we know that it is defined in the package scope.
pkgobj := qpkg.Scope.Objects[name]
if pkgobj == nil {
panic("missing defpkg object for " + defpkg + "." + name)
}
// Find all references to the query object.
var refs []*ast.Ident
ast.Inspect(qpkg, func(n ast.Node) bool {
if id, ok := n.(*ast.Ident); ok {
// Check both that this is a reference to the query object
// and that it is not the query object itself;
// the query object itself was already emitted.
if id.Obj == pkgobj && objpos != fset.Position(id.Pos()) {
refs = append(refs, id)
return false
}
}
return true
})
if len(refs) > 0 {
q.Output(fset, &referrersPackageResult{
pkg: types.NewPackage(pkg.ImportPath, pkg.Name),
build: q.Build,
fset: fset,
refs: refs,
})
}
deffiles = nil // allow GC
}
}()
}
wg.Wait()
return nil
}
// findObject returns the object defined at the specified position. // findObject returns the object defined at the specified position.
func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object { func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object {
good := func(obj types.Object) bool { good := func(obj types.Object) bool {
@ -730,7 +453,7 @@ func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string))
// start asynchronous read. // start asynchronous read.
go func() { go func() {
sema <- struct{}{} // acquire token sema <- struct{}{} // acquire token
content, err := readFile(r.build, posn.Filename, nil) content, err := readFile(r.build, posn.Filename)
<-sema // release token <-sema // release token
if err != nil { if err != nil {
fi.data <- err fi.data <- err
@ -768,17 +491,14 @@ func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string))
// readFile is like ioutil.ReadFile, but // readFile is like ioutil.ReadFile, but
// it goes through the virtualized build.Context. // it goes through the virtualized build.Context.
// If non-nil, buf must have been reset. func readFile(ctxt *build.Context, filename string) ([]byte, error) {
func readFile(ctxt *build.Context, filename string, buf *bytes.Buffer) ([]byte, error) {
rc, err := buildutil.OpenFile(ctxt, filename) rc, err := buildutil.OpenFile(ctxt, filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rc.Close() defer rc.Close()
if buf == nil { var buf bytes.Buffer
buf = new(bytes.Buffer) if _, err := io.Copy(&buf, rc); err != nil {
}
if _, err := io.Copy(buf, rc); err != nil {
return nil, err return nil, err
} }
return buf.Bytes(), nil return buf.Bytes(), nil

View File

@ -193,10 +193,9 @@ type PointsTo struct {
// A DescribeValue is the additional result of a 'describe' query // A DescribeValue is the additional result of a 'describe' query
// if the selection indicates a value or expression. // if the selection indicates a value or expression.
type DescribeValue struct { type DescribeValue struct {
Type string `json:"type"` // type of the expression Type string `json:"type"` // type of the expression
Value string `json:"value,omitempty"` // value of the expression, if constant Value string `json:"value,omitempty"` // value of the expression, if constant
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
TypesPos []Definition `json:"typespos,omitempty"` // location of the named types, that type consist of
} }
type DescribeMethod struct { type DescribeMethod struct {

View File

@ -1,23 +0,0 @@
// Tests of Go 1.9 type aliases.
// See go.tools/guru/guru_test.go for explanation.
// See alias.golden for expected query results.
package alias // @describe describe-pkg "alias"
type I interface { // @implements implements-I "I"
f()
}
type N int
func (N) f() {}
type M = N // @describe describe-def-M "M"
var m M // @describe describe-ref-M "M"
type O N // @describe describe-O "O"
type P = struct{ N } // @describe describe-P "N"
type U = undefined // @describe describe-U "U"
type _ = undefined // @describe describe-undefined "undefined"

View File

@ -1,47 +0,0 @@
-------- @describe describe-pkg --------
definition of package "alias"
type I interface{f()}
method (I) f()
type M = N
method (N) f()
type N int
method (N) f()
type O int
type P = struct{N}
method (struct{N}) f()
type U = invalid type
var m N
-------- @implements implements-I --------
interface type I
is implemented by basic type N
-------- @describe describe-def-M --------
alias of type N (size 8, align 8)
defined as int
Methods:
method (N) f()
-------- @describe describe-ref-M --------
alias of type N (size 8, align 8)
defined as int
Methods:
method (N) f()
-------- @describe describe-O --------
definition of type O (size 8, align 8)
No methods.
-------- @describe describe-P --------
type struct{N} (size 8, align 8)
Methods:
method (struct{N}) f()
Fields:
N N
-------- @describe describe-U --------
alias of type invalid type
-------- @describe describe-undefined --------
identifier

54
cmd/guru/testdata/src/alias/main.golden vendored Normal file
View File

@ -0,0 +1,54 @@
-------- @describe pkg --------
definition of package "alias"
type S1 struct{aliaslib.T}
method (S1) Method(x *int) *int
type S2 struct{aliaslib.T}
method (S2) Method(x *int) *int
alias bad1 => ?
alias bad2 => ?
const c_ => aliaslib.C
func f_ => aliaslib.F
type t_ => aliaslib.T
var v_ => aliaslib.V
var x aliaslib.T
-------- @describe bad1 --------
identifier
-------- @describe bad2 --------
identifier
-------- @describe v --------
definition of var alias v_ int
-------- @describe t --------
alias of type aliaslib.T (size 8, align 8)
defined as int
Methods:
method (T) Method(x *int) *int
-------- @describe c --------
definition of const alias c_ untyped int of value 3
-------- @describe f --------
definition of func alias f_ func()
-------- @describe s1-field --------
reference to field T aliaslib.T
defined here
Methods:
method (T) Method(x *int) *int
-------- @describe s2-field --------
type struct{aliaslib.T} (size 8, align 8)
Methods:
method (struct{T}) Method(x *int) *int
Fields:
t_ aliaslib.T
-------- @describe var-x --------
alias of type aliaslib.T (size 8, align 8)
defined as int
Methods:
method (T) Method(x *int) *int

View File

@ -0,0 +1,11 @@
package aliaslib
type T int
func (T) Method(x *int) *int
func F()
const C = 3
var V = 0

View File

@ -53,10 +53,8 @@ Error: no object for identifier
Error: couldn't find declaration of Nonesuch in "lib" Error: couldn't find declaration of Nonesuch in "lib"
-------- @definition qualified-nopkg -------- -------- @definition qualified-nopkg --------
{
"objpos": "testdata/src/definition-json/main.go:12:2", Error: no object for identifier
"desc": "package nosuchpkg"
}
-------- @definition select-field -------- -------- @definition select-field --------
{ {
"objpos": "testdata/src/definition-json/main.go:40:16", "objpos": "testdata/src/definition-json/main.go:40:16",

View File

@ -25,5 +25,5 @@ type I interface {
type C int // @describe desc-type-C "C" type C int // @describe desc-type-C "C"
type D struct{} type D struct{}
func (c C) f() {} // @describe desc-param-c "\\bc\\b" func (c C) f() {}
func (d *D) f() {} // @describe desc-param-d "\\bd\\b" func (d *D) f() {}

View File

@ -68,13 +68,7 @@
"detail": "value", "detail": "value",
"value": { "value": {
"type": "I", "type": "I",
"objpos": "testdata/src/describe-json/main.go:12:6", "objpos": "testdata/src/describe-json/main.go:12:6"
"typespos": [
{
"objpos": "testdata/src/describe-json/main.go:21:6",
"desc": "I"
}
]
} }
} }
-------- @describe desc-stmt -------- -------- @describe desc-stmt --------
@ -100,35 +94,3 @@
] ]
} }
} }
-------- @describe desc-param-c --------
{
"desc": "identifier",
"pos": "testdata/src/describe-json/main.go:28:7",
"detail": "value",
"value": {
"type": "C",
"objpos": "testdata/src/describe-json/main.go:28:7",
"typespos": [
{
"objpos": "testdata/src/describe-json/main.go:25:6",
"desc": "C"
}
]
}
}
-------- @describe desc-param-d --------
{
"desc": "identifier",
"pos": "testdata/src/describe-json/main.go:29:7",
"detail": "value",
"value": {
"type": "*D",
"objpos": "testdata/src/describe-json/main.go:29:7",
"typespos": [
{
"objpos": "testdata/src/describe-json/main.go:26:6",
"desc": "D"
}
]
}
}

View File

@ -28,15 +28,14 @@ var global = new(string) // NB: ssa.Global is indirect, i.e. **string
func main() { // @describe func-def-main "main" func main() { // @describe func-def-main "main"
// func objects // func objects
_ = main // @describe func-ref-main "main" _ = main // @describe func-ref-main "main"
_ = (*C).f // @describe func-ref-*C.f "..C..f" _ = (*C).f // @describe func-ref-*C.f "..C..f"
_ = D.f // @describe func-ref-D.f "D.f" _ = D.f // @describe func-ref-D.f "D.f"
_ = I.f // @describe func-ref-I.f "I.f" _ = I.f // @describe func-ref-I.f "I.f"
var d D // @describe type-D "D" var d D // @describe type-D "D"
var i I // @describe type-I "I" var i I // @describe type-I "I"
_ = d.f // @describe func-ref-d.f "d.f" _ = d.f // @describe func-ref-d.f "d.f"
_ = i.f // @describe func-ref-i.f "i.f" _ = i.f // @describe func-ref-i.f "i.f"
var slice []D // @describe slice-of-D "slice"
var dptr *D // @describe ptr-with-nonptr-methods "dptr" var dptr *D // @describe ptr-with-nonptr-methods "dptr"
_ = dptr _ = dptr
@ -91,11 +90,6 @@ func main() { // @describe func-def-main "main"
var _ lib.Outer // @describe lib-outer "Outer" var _ lib.Outer // @describe lib-outer "Outer"
var mmm map[C]D // @describe var-map-of-C-D "mmm"
d := newD().ThirdField // @describe field-access "ThirdField"
astCopy := ast
unknown() // @describe call-unknown "\\(" unknown() // @describe call-unknown "\\("
} }
@ -107,10 +101,7 @@ type C int
type D struct { type D struct {
Field int Field int
AnotherField string AnotherField string
ThirdField C
} }
func (c *C) f() {} func (c *C) f() {}
func (d D) f() {} func (d D) f() {}
func newD() D { return D{} }

View File

@ -10,16 +10,15 @@ definition of package "describe"
type cake float64 type cake float64
var global *string var global *string
func main func() func main func()
func newD func() D
const pi untyped float = 3.141 const pi untyped float = 3.141
const pie cake = 3.141 const pie cake = 3.141
-------- @describe badimport1 -------- -------- @describe badimport1 --------
import of package "nosuchpkg"
Error: can't import package "nosuchpkg"
-------- @describe badimport2 -------- -------- @describe badimport2 --------
reference to package "nosuchpkg"
Error: can't import package "nosuchpkg"
-------- @describe unsafe -------- -------- @describe unsafe --------
import of package "unsafe" import of package "unsafe"
builtin Alignof builtin Alignof
@ -38,8 +37,6 @@ definition of const pi untyped float of value 3.141
-------- @describe const-def-pie -------- -------- @describe const-def-pie --------
definition of const pie cake of value 3.141 definition of const pie cake of value 3.141
Named types:
type cake defined here
-------- @describe const-ref-pi -------- -------- @describe const-ref-pi --------
reference to const pi untyped float of value 3.141 reference to const pi untyped float of value 3.141
@ -65,14 +62,13 @@ reference to interface method func (I).f()
defined here defined here
-------- @describe type-D -------- -------- @describe type-D --------
reference to type D (size 32, align 8) reference to type D (size 24, align 8)
defined as struct{Field int; AnotherField string; ThirdField C} defined as struct{Field int; AnotherField string}
Methods: Methods:
method (D) f() method (D) f()
Fields: Fields:
Field int Field int
AnotherField string AnotherField string
ThirdField C
-------- @describe type-I -------- -------- @describe type-I --------
reference to type I (size 16, align 8) reference to type I (size 16, align 8)
@ -88,11 +84,6 @@ defined here
reference to interface method func (I).f() reference to interface method func (I).f()
defined here defined here
-------- @describe slice-of-D --------
definition of var slice []D
Named types:
type D defined here
-------- @describe ptr-with-nonptr-methods -------- -------- @describe ptr-with-nonptr-methods --------
definition of var dptr *D definition of var dptr *D
Methods: Methods:
@ -100,9 +91,6 @@ Methods:
Fields: Fields:
Field int Field int
AnotherField string AnotherField string
ThirdField C
Named types:
type D defined here
-------- @describe ref-lexical-d -------- -------- @describe ref-lexical-d --------
reference to var d D reference to var d D
@ -112,9 +100,6 @@ Methods:
Fields: Fields:
Field int Field int
AnotherField string AnotherField string
ThirdField C
Named types:
type D defined here
-------- @describe ref-anon -------- -------- @describe ref-anon --------
reference to var anon func() reference to var anon func()
@ -144,32 +129,24 @@ reference to var i I
defined here defined here
Methods: Methods:
method (I) f() method (I) f()
Named types:
type I defined here
-------- @describe var-ref-i-D -------- -------- @describe var-ref-i-D --------
reference to var i I reference to var i I
defined here defined here
Methods: Methods:
method (I) f() method (I) f()
Named types:
type I defined here
-------- @describe var-ref-i -------- -------- @describe var-ref-i --------
reference to var i I reference to var i I
defined here defined here
Methods: Methods:
method (I) f() method (I) f()
Named types:
type I defined here
-------- @describe const-local-pi -------- -------- @describe const-local-pi --------
definition of const localpi untyped float of value 3.141 definition of const localpi untyped float of value 3.141
-------- @describe const-local-pie -------- -------- @describe const-local-pie --------
definition of const localpie cake of value 3.141 definition of const localpie cake of value 3.141
Named types:
type cake defined here
-------- @describe const-ref-localpi -------- -------- @describe const-ref-localpi --------
reference to const localpi untyped float of value 3.141 reference to const localpi untyped float of value 3.141
@ -228,20 +205,6 @@ Fields:
inner.C bool inner.C bool
inner.recursive.E bool inner.recursive.E bool
-------- @describe var-map-of-C-D --------
definition of var mmm map[C]D
Named types:
type C defined here
type D defined here
-------- @describe field-access --------
reference to field ThirdField C
defined here
Methods:
method (*C) f()
Named types:
type C defined here
-------- @describe call-unknown -------- -------- @describe call-unknown --------
function call of type invalid type function call of type invalid type

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