Compare commits
8 Commits
master
...
release-br
Author | SHA1 | Date |
---|---|---|
|
6220cba641 | |
|
ae17563914 | |
|
7771da7791 | |
|
9d32829149 | |
|
05ca1f09ae | |
|
46af848339 | |
|
26c35b4dcf | |
|
a84e830bb0 |
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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 oracle 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.
|
27
README.md
27
README.md
|
@ -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.
|
|
|
@ -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 {
|
||||||
|
|
44
blog/blog.go
44
blog/blog.go
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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>"golang.org/x/net/websocket"</code>.`,
|
|
||||||
`For instance, the <code>websocket</code> package from the <code>go.net</code> sub-repository has an import path of <code>"golang.org/x/net/websocket"</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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
testdata/out.got
|
|
|
@ -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 go1.5
|
||||||
|
|
||||||
// Bundle creates a single-source-file version of a source package
|
// Bundle creates a single-source-file version of a source package
|
||||||
// suitable for inclusion in a particular target package.
|
// suitable for inclusion in a particular target package.
|
||||||
//
|
//
|
||||||
|
@ -88,14 +90,12 @@ 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"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
|
@ -105,8 +105,7 @@ 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")
|
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -259,64 +257,46 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
|
||||||
|
|
||||||
fmt.Fprintf(&out, "package %s\n\n", dstpkg)
|
fmt.Fprintf(&out, "package %s\n\n", dstpkg)
|
||||||
|
|
||||||
// BUG(adonovan,shurcooL): bundle may generate incorrect code
|
// Print a single declaration that imports all necessary packages.
|
||||||
// due to shadowing between identifiers and imported package names.
|
// TODO(adonovan):
|
||||||
//
|
// - support renaming imports.
|
||||||
// The generated code will either fail to compile or
|
|
||||||
// (unlikely) compile successfully but have different behavior
|
|
||||||
// than the original package. The risk of this happening is higher
|
|
||||||
// when the original package has renamed imports (they're typically
|
|
||||||
// renamed in order to resolve a shadow inside that particular .go file).
|
|
||||||
|
|
||||||
// TODO(adonovan,shurcooL):
|
|
||||||
// - detect shadowing issues, and either return error or resolve them
|
|
||||||
// - preserve comments from the original import declarations.
|
// - preserve comments from the original import declarations.
|
||||||
|
|
||||||
// pkgStd and pkgExt are sets of printed import specs. This is done
|
|
||||||
// to deduplicate instances of the same import name and path.
|
|
||||||
var pkgStd = make(map[string]bool)
|
|
||||||
var pkgExt = make(map[string]bool)
|
|
||||||
for _, f := range info.Files {
|
for _, f := range info.Files {
|
||||||
for _, imp := range f.Imports {
|
for _, imp := range f.Imports {
|
||||||
path, err := strconv.Unquote(imp.Path.Value)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("invalid import path string: %v", err) // Shouldn't happen here since conf.Load succeeded.
|
|
||||||
}
|
|
||||||
if path == dst {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if newPath, ok := importMap[path]; ok {
|
|
||||||
path = newPath
|
|
||||||
}
|
|
||||||
|
|
||||||
var name string
|
|
||||||
if imp.Name != nil {
|
if imp.Name != nil {
|
||||||
name = imp.Name.Name
|
return nil, fmt.Errorf("%s: renaming imports not supported",
|
||||||
}
|
lprog.Fset.Position(imp.Pos()))
|
||||||
spec := fmt.Sprintf("%s %q", name, path)
|
|
||||||
if isStandardImportPath(path) {
|
|
||||||
pkgStd[spec] = true
|
|
||||||
} else {
|
|
||||||
if *underscore {
|
|
||||||
spec = strings.Replace(spec, "golang.org/x/", "internal/x/", 1)
|
|
||||||
}
|
|
||||||
pkgExt[spec] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print a single declaration that imports all necessary packages.
|
var pkgStd, pkgExt []string
|
||||||
|
for _, p := range info.Pkg.Imports() {
|
||||||
|
if p.Path() == dst {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
x, ok := importMap[p.Path()]
|
||||||
|
if !ok {
|
||||||
|
x = p.Path()
|
||||||
|
}
|
||||||
|
if isStandardImportPath(x) {
|
||||||
|
pkgStd = append(pkgStd, x)
|
||||||
|
} else {
|
||||||
|
pkgExt = append(pkgExt, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintln(&out, "import (")
|
fmt.Fprintln(&out, "import (")
|
||||||
for p := range pkgStd {
|
for _, p := range pkgStd {
|
||||||
fmt.Fprintf(&out, "\t%s\n", p)
|
fmt.Fprintf(&out, "\t%q\n", p)
|
||||||
}
|
}
|
||||||
if len(pkgExt) > 0 {
|
if len(pkgExt) > 0 {
|
||||||
fmt.Fprintln(&out)
|
fmt.Fprintln(&out)
|
||||||
|
for _, p := range pkgExt {
|
||||||
|
fmt.Fprintf(&out, "\t%q\n", p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for p := range pkgExt {
|
fmt.Fprintln(&out, ")\n")
|
||||||
fmt.Fprintf(&out, "\t%s\n", p)
|
|
||||||
}
|
|
||||||
fmt.Fprint(&out, ")\n\n")
|
|
||||||
|
|
||||||
// Modify and print each file.
|
// Modify and print each file.
|
||||||
for _, f := range info.Files {
|
for _, f := range info.Files {
|
||||||
|
@ -348,41 +328,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 +358,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 {
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -30,7 +28,6 @@ func TestBundle(t *testing.T) {
|
||||||
"initial": {
|
"initial": {
|
||||||
"a.go": load("testdata/src/initial/a.go"),
|
"a.go": load("testdata/src/initial/a.go"),
|
||||||
"b.go": load("testdata/src/initial/b.go"),
|
"b.go": load("testdata/src/initial/b.go"),
|
||||||
"c.go": load("testdata/src/initial/c.go"),
|
|
||||||
},
|
},
|
||||||
"domain.name/importdecl": {
|
"domain.name/importdecl": {
|
||||||
"p.go": load("testdata/src/domain.name/importdecl/p.go"),
|
"p.go": load("testdata/src/domain.name/importdecl/p.go"),
|
||||||
|
|
|
@ -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
|
||||||
|
@ -8,10 +8,6 @@ package dest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
. "fmt"
|
|
||||||
_ "fmt"
|
|
||||||
renamedfmt "fmt"
|
|
||||||
renamedfmt2 "fmt"
|
|
||||||
|
|
||||||
"domain.name/importdecl"
|
"domain.name/importdecl"
|
||||||
)
|
)
|
||||||
|
@ -23,40 +19,15 @@ 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() {
|
|
||||||
renamedfmt.Println()
|
|
||||||
renamedfmt2.Println()
|
|
||||||
Println()
|
|
||||||
}
|
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package initial
|
|
||||||
|
|
||||||
import _ "fmt"
|
|
||||||
import renamedfmt "fmt"
|
|
||||||
import renamedfmt2 "fmt"
|
|
||||||
import . "fmt"
|
|
||||||
|
|
||||||
func baz() {
|
|
||||||
renamedfmt.Println()
|
|
||||||
renamedfmt2.Println()
|
|
||||||
Println()
|
|
||||||
}
|
|
|
@ -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,12 +221,12 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mains, err := mainPackages(pkgs)
|
main, err := mainPackage(prog, tests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config := &pointer.Config{
|
config := &pointer.Config{
|
||||||
Mains: mains,
|
Mains: []*ssa.Package{main},
|
||||||
BuildCallGraph: true,
|
BuildCallGraph: true,
|
||||||
Log: ptalog,
|
Log: ptalog,
|
||||||
}
|
}
|
||||||
|
@ -237,13 +237,13 @@ 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)
|
main, err := mainPackage(prog, tests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var roots []*ssa.Function
|
roots := []*ssa.Function{
|
||||||
for _, main := range mains {
|
main.Func("init"),
|
||||||
roots = append(roots, main.Func("init"), main.Func("main"))
|
main.Func("main"),
|
||||||
}
|
}
|
||||||
rtares := rta.Analyze(roots, true)
|
rtares := rta.Analyze(roots, true)
|
||||||
cg = rtares.CallGraph
|
cg = rtares.CallGraph
|
||||||
|
@ -303,19 +303,35 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mainPackages returns the main packages to analyze.
|
// mainPackage returns the main package to analyze.
|
||||||
// Each resulting package is named "main" and has a main function.
|
// The resulting package has a main() function.
|
||||||
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
|
func mainPackage(prog *ssa.Program, tests bool) (*ssa.Package, error) {
|
||||||
var mains []*ssa.Package
|
pkgs := prog.AllPackages()
|
||||||
for _, p := range pkgs {
|
|
||||||
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
|
// TODO(adonovan): allow independent control over tests, mains and libraries.
|
||||||
mains = append(mains, p)
|
// TODO(adonovan): put this logic in a library; we keep reinventing it.
|
||||||
|
|
||||||
|
if tests {
|
||||||
|
// If -test, use all packages' tests.
|
||||||
|
if len(pkgs) > 0 {
|
||||||
|
if main := prog.CreateTestMainPackage(pkgs...); main != nil {
|
||||||
|
return main, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, use the first package named main.
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
if pkg.Pkg.Name() == "main" {
|
||||||
|
if pkg.Func("main") == nil {
|
||||||
|
return nil, fmt.Errorf("no func main() in main package")
|
||||||
|
}
|
||||||
|
return pkg, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(mains) == 0 {
|
|
||||||
return nil, fmt.Errorf("no main packages")
|
return nil, fmt.Errorf("no main package")
|
||||||
}
|
|
||||||
return mains, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Edge struct {
|
type Edge struct {
|
||||||
|
|
|
@ -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.test.main --> testing.MainStart`,
|
|
||||||
`testing.runExample --> pkg.Example`,
|
|
||||||
`pkg.Example --> (pkg.C).f`,
|
`pkg.Example --> (pkg.C).f`,
|
||||||
`pkg.main --> (pkg.C).f`,
|
`test$main.init --> pkg.init`,
|
||||||
}},
|
}},
|
||||||
{"pta", true, []string{
|
{"pta", format, true, []string{
|
||||||
`<root> --> pkg.test.main`,
|
`<root> --> pkg.Example`,
|
||||||
`<root> --> pkg.main`,
|
`<root> --> test$main.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`,
|
`test$main.init --> pkg.init`,
|
||||||
}},
|
}},
|
||||||
} {
|
} {
|
||||||
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
|
||||||
|
}
|
||||||
|
|
|
@ -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:
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -67,9 +67,6 @@ func htmlOutput(profile, outfile string) error {
|
||||||
} else {
|
} else {
|
||||||
out, err = os.Create(outfile)
|
out, err = os.Create(outfile)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = htmlTemplate.Execute(out, d)
|
err = htmlTemplate.Execute(out, d)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = out.Close()
|
err = out.Close()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -215,10 +200,7 @@ import (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare stderr output.
|
// Compare stderr output.
|
||||||
if got := stderr.(*bytes.Buffer).String(); got != test.wantStderr {
|
if stderr.(*bytes.Buffer).String() != test.wantStderr {
|
||||||
if strings.Contains(got, "vendor/golang_org/x/text/unicode/norm") {
|
|
||||||
t.Skip("skipping known-broken test; see golang.org/issue/17417")
|
|
||||||
}
|
|
||||||
t.Errorf("#%d. stderr: got <<%s>>, want <<%s>>",
|
t.Errorf("#%d. stderr: got <<%s>>, want <<%s>>",
|
||||||
i, stderr, test.wantStderr)
|
i, stderr, test.wantStderr)
|
||||||
}
|
}
|
||||||
|
@ -239,6 +221,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)
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
.git
|
|
||||||
.dockerignore
|
|
||||||
LICENSE
|
|
||||||
README.md
|
|
||||||
.gitignore
|
|
|
@ -1,3 +0,0 @@
|
||||||
build
|
|
||||||
testgetgo
|
|
||||||
getgo
|
|
|
@ -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
|
|
|
@ -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`.
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
# getgo server
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
```
|
|
||||||
gcloud app deploy --promote --project golang-org
|
|
||||||
```
|
|
|
@ -1,7 +0,0 @@
|
||||||
runtime: go
|
|
||||||
service: get
|
|
||||||
api_version: go1
|
|
||||||
|
|
||||||
handlers:
|
|
||||||
- url: /.*
|
|
||||||
script: _go_app
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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.)")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 go1.5
|
||||||
|
|
||||||
// This file implements access to gc-generated export data.
|
// This file implements access to gc-generated export data.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
|
@ -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 go1.5
|
||||||
|
|
||||||
// This file implements access to gccgo-generated export data.
|
// This file implements access to gccgo-generated export data.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
|
@ -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 go1.5
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.9
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "go/types"
|
|
||||||
|
|
||||||
func isAlias(obj *types.TypeName) bool {
|
|
||||||
return false // there are no type aliases before Go 1.9
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "go/types"
|
|
||||||
|
|
||||||
func isAlias(obj *types.TypeName) bool {
|
|
||||||
return obj.IsAlias()
|
|
||||||
}
|
|
|
@ -2,12 +2,14 @@
|
||||||
// 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.5
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/constant"
|
exact "go/constant"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"io"
|
"io"
|
||||||
|
@ -141,13 +143,7 @@ func (p *printer) printPackage(pkg *types.Package, filter func(types.Object) boo
|
||||||
p.printDecl("type", len(typez), func() {
|
p.printDecl("type", len(typez), func() {
|
||||||
for _, obj := range typez {
|
for _, obj := range typez {
|
||||||
p.printf("%s ", obj.Name())
|
p.printf("%s ", obj.Name())
|
||||||
typ := obj.Type()
|
p.writeType(p.pkg, obj.Type().Underlying())
|
||||||
if isAlias(obj) {
|
|
||||||
p.print("= ")
|
|
||||||
p.writeType(p.pkg, typ)
|
|
||||||
} else {
|
|
||||||
p.writeType(p.pkg, typ.Underlying())
|
|
||||||
}
|
|
||||||
p.print("\n")
|
p.print("\n")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -213,9 +209,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 +225,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 +268,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 +282,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()
|
||||||
|
|
|
@ -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 go1.5
|
||||||
|
|
||||||
// This file implements access to export data from source.
|
// This file implements access to export data from source.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
|
@ -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 go1.5
|
||||||
|
|
||||||
// This file implements writing of types. The functionality is lifted
|
// This file implements writing of types. The functionality is lifted
|
||||||
// directly from go/types, but now contains various modifications for
|
// directly from go/types, but now contains various modifications for
|
||||||
// nicer output.
|
// nicer output.
|
||||||
|
|
|
@ -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 .
|
|
@ -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")
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
@ -102,18 +134,18 @@ slash ('/') as path separator; and they must be unrooted. $GOROOT (or -goroot)
|
||||||
must be set to the .zip file directory path containing the Go root directory.
|
must be set to the .zip file directory path containing the Go root directory.
|
||||||
For instance, for a .zip file created by the command:
|
For instance, for a .zip file created by the command:
|
||||||
|
|
||||||
zip -r go.zip $HOME/go
|
zip go.zip $HOME/go
|
||||||
|
|
||||||
one may run godoc as follows:
|
one may run godoc as follows:
|
||||||
|
|
||||||
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
|
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
|
||||||
|
|
||||||
Godoc documentation is converted to HTML or to text using the go/doc package;
|
Godoc documentation is converted to HTML or to text using the go/doc package;
|
||||||
see https://golang.org/pkg/go/doc/#ToHTML for the exact rules.
|
see http://golang.org/pkg/go/doc/#ToHTML for the exact rules.
|
||||||
Godoc also shows example code that is runnable by the testing package;
|
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"
|
||||||
|
|
|
@ -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,
|
5*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=notwithstanding",
|
||||||
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/reg.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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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", "", "directory containing alternate template files")
|
||||||
showPlayground = flag.Bool("play", false, "enable playground")
|
showPlayground = flag.Bool("play", false, "enable playground in web interface")
|
||||||
|
showExamples = flag.Bool("ex", false, "show examples in command line mode")
|
||||||
declLinks = flag.Bool("links", true, "link identifiers to their declarations")
|
declLinks = flag.Bool("links", true, "link identifiers to their declarations")
|
||||||
|
|
||||||
// search index
|
// search index
|
||||||
|
@ -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,51 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
|
||||||
// is used.
|
|
||||||
var (
|
|
||||||
certInit func()
|
|
||||||
runHTTPS func(http.Handler) error
|
|
||||||
wrapHTTPMux func(http.Handler) http.Handler
|
|
||||||
)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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"
|
|
@ -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>
|
||||||
|
`))
|
|
@ -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":
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
|
||||||
"go/scanner"
|
"go/scanner"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -20,42 +19,37 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
"runtime/trace"
|
||||||
"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 = flag.Bool("v", false, "verbose logging")
|
||||||
verbose bool // verbose logging
|
|
||||||
|
|
||||||
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
|
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
|
||||||
memProfile = flag.String("memprofile", "", "memory profile output")
|
memProfile = flag.String("memprofile", "", "memory profile output")
|
||||||
memProfileRate = flag.Int("memrate", 0, "if > 0, sets runtime.MemProfileRate")
|
memProfileRate = flag.Int("memrate", 0, "if > 0, sets runtime.MemProfileRate")
|
||||||
|
traceProfile = flag.String("trace", "", "trace profile output")
|
||||||
|
|
||||||
options = &imports.Options{
|
options = &imports.Options{
|
||||||
TabWidth: 8,
|
TabWidth: 8,
|
||||||
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 +146,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,8 +195,6 @@ func main() {
|
||||||
// parseFlags parses command line flags and returns the paths to process.
|
// parseFlags parses command line flags and returns the paths to process.
|
||||||
// It's a var so that custom implementations can replace it in other files.
|
// It's a var so that custom implementations can replace it in other files.
|
||||||
var parseFlags = func() []string {
|
var parseFlags = func() []string {
|
||||||
flag.BoolVar(&verbose, "v", false, "verbose logging")
|
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
return flag.Args()
|
return flag.Args()
|
||||||
}
|
}
|
||||||
|
@ -240,10 +225,12 @@ func gofmtMain() {
|
||||||
defer flush()
|
defer flush()
|
||||||
defer pprof.StopCPUProfile()
|
defer pprof.StopCPUProfile()
|
||||||
}
|
}
|
||||||
// doTrace is a conditionally compiled wrapper around runtime/trace. It is
|
if *traceProfile != "" {
|
||||||
// used to allow goimports to compile under gccgo, which does not support
|
bw, flush := bufferedFileWriter(*traceProfile)
|
||||||
// runtime/trace. See https://golang.org/issue/15544.
|
trace.Start(bw)
|
||||||
defer doTrace()()
|
defer flush()
|
||||||
|
defer trace.Stop()
|
||||||
|
}
|
||||||
if *memProfileRate > 0 {
|
if *memProfileRate > 0 {
|
||||||
runtime.MemProfileRate = *memProfileRate
|
runtime.MemProfileRate = *memProfileRate
|
||||||
bw, flush := bufferedFileWriter(*memProfile)
|
bw, flush := bufferedFileWriter(*memProfile)
|
||||||
|
@ -256,9 +243,9 @@ 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 +279,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)
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// 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 gc
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"runtime/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
var traceProfile = flag.String("trace", "", "trace profile output")
|
|
||||||
|
|
||||||
func doTrace() func() {
|
|
||||||
if *traceProfile != "" {
|
|
||||||
bw, flush := bufferedFileWriter(*traceProfile)
|
|
||||||
trace.Start(bw)
|
|
||||||
return func() {
|
|
||||||
flush()
|
|
||||||
trace.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return func() {}
|
|
||||||
}
|
|
|
@ -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")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
gopls*.vsix
|
|
||||||
out
|
|
||||||
node_modules
|
|
|
@ -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"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es6",
|
|
||||||
"outDir": "out",
|
|
||||||
"rootDir": "src",
|
|
||||||
"lib": [
|
|
||||||
"es6"
|
|
||||||
],
|
|
||||||
"sourceMap": true
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
true,
|
|
||||||
"tabs"
|
|
||||||
],
|
|
||||||
"semicolon": [
|
|
||||||
true,
|
|
||||||
"always"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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:])
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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"
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Goyacc is a version of yacc for Go.
|
|
||||||
It is written in Go and generates parsers written in Go.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
goyacc args...
|
|
||||||
|
|
||||||
It is largely transliterated from the Inferno version written in Limbo
|
|
||||||
which in turn was largely transliterated from the Plan 9 version
|
|
||||||
written in C and documented at
|
|
||||||
|
|
||||||
https://9p.io/magic/man2html/1/yacc
|
|
||||||
|
|
||||||
Adepts of the original yacc will have no trouble adapting to this
|
|
||||||
form of the tool.
|
|
||||||
|
|
||||||
The directory $GOPATH/src/golang.org/x/tools/cmd/goyacc/testdata/expr
|
|
||||||
is a yacc program for a very simple expression parser. See expr.y and
|
|
||||||
main.go in that directory for examples of how to write and build
|
|
||||||
goyacc programs.
|
|
||||||
|
|
||||||
The generated parser is reentrant. The parsing function yyParse expects
|
|
||||||
to be given an argument that conforms to the following interface:
|
|
||||||
|
|
||||||
type yyLexer interface {
|
|
||||||
Lex(lval *yySymType) int
|
|
||||||
Error(e string)
|
|
||||||
}
|
|
||||||
|
|
||||||
Lex should return the token identifier, and place other token
|
|
||||||
information in lval (which replaces the usual yylval).
|
|
||||||
Error is equivalent to yyerror in the original yacc.
|
|
||||||
|
|
||||||
Code inside the grammar actions may refer to the variable yylex,
|
|
||||||
which holds the yyLexer passed to yyParse.
|
|
||||||
|
|
||||||
Clients that need to understand more about the parser state can
|
|
||||||
create the parser separately from invoking it. The function yyNewParser
|
|
||||||
returns a yyParser conforming to the following interface:
|
|
||||||
|
|
||||||
type yyParser interface {
|
|
||||||
Parse(yyLex) int
|
|
||||||
Lookahead() int
|
|
||||||
}
|
|
||||||
|
|
||||||
Parse runs the parser; the top-level call yyParse(yylex) is equivalent
|
|
||||||
to yyNewParser().Parse(yylex).
|
|
||||||
|
|
||||||
Lookahead can be called during grammar actions to read (but not consume)
|
|
||||||
the value of the current lookahead token, as returned by yylex.Lex.
|
|
||||||
If there is no current lookahead token (because the parser has not called Lex
|
|
||||||
or has consumed the token returned by the most recent call to Lex),
|
|
||||||
Lookahead returns -1. Calling Lookahead is equivalent to reading
|
|
||||||
yychar from within in a grammar action.
|
|
||||||
|
|
||||||
Multiple grammars compiled into a single program should be placed in
|
|
||||||
distinct packages. If that is impossible, the "-p prefix" flag to
|
|
||||||
goyacc sets the prefix, by default yy, that begins the names of
|
|
||||||
symbols, including types, the parser, and the lexer, generated and
|
|
||||||
referenced by yacc's generated code. Setting it to distinct values
|
|
||||||
allows multiple grammars to be placed in a single package.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package main
|
|
|
@ -1,20 +0,0 @@
|
||||||
This directory contains a simple program demonstrating how to use
|
|
||||||
the Go version of yacc.
|
|
||||||
|
|
||||||
To build it:
|
|
||||||
|
|
||||||
$ go generate
|
|
||||||
$ go build
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
$ go generate
|
|
||||||
$ go run expr.go
|
|
||||||
|
|
||||||
The file main.go contains the "go generate" command to run yacc to
|
|
||||||
create expr.go from expr.y. It also has the package doc comment,
|
|
||||||
as godoc will not scan the .y file.
|
|
||||||
|
|
||||||
The actual implementation is in expr.y.
|
|
||||||
|
|
||||||
The program is not installed in the binary distributions of Go.
|
|
|
@ -1,202 +0,0 @@
|
||||||
// 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 is an example of a goyacc program.
|
|
||||||
// To build it:
|
|
||||||
// goyacc -p "expr" expr.y (produces y.go)
|
|
||||||
// go build -o expr y.go
|
|
||||||
// expr
|
|
||||||
// > <type an expression>
|
|
||||||
|
|
||||||
%{
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
%}
|
|
||||||
|
|
||||||
%union {
|
|
||||||
num *big.Rat
|
|
||||||
}
|
|
||||||
|
|
||||||
%type <num> expr expr1 expr2 expr3
|
|
||||||
|
|
||||||
%token '+' '-' '*' '/' '(' ')'
|
|
||||||
|
|
||||||
%token <num> NUM
|
|
||||||
|
|
||||||
%%
|
|
||||||
|
|
||||||
top:
|
|
||||||
expr
|
|
||||||
{
|
|
||||||
if $1.IsInt() {
|
|
||||||
fmt.Println($1.Num().String())
|
|
||||||
} else {
|
|
||||||
fmt.Println($1.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expr:
|
|
||||||
expr1
|
|
||||||
| '+' expr
|
|
||||||
{
|
|
||||||
$$ = $2
|
|
||||||
}
|
|
||||||
| '-' expr
|
|
||||||
{
|
|
||||||
$$ = $2.Neg($2)
|
|
||||||
}
|
|
||||||
|
|
||||||
expr1:
|
|
||||||
expr2
|
|
||||||
| expr1 '+' expr2
|
|
||||||
{
|
|
||||||
$$ = $1.Add($1, $3)
|
|
||||||
}
|
|
||||||
| expr1 '-' expr2
|
|
||||||
{
|
|
||||||
$$ = $1.Sub($1, $3)
|
|
||||||
}
|
|
||||||
|
|
||||||
expr2:
|
|
||||||
expr3
|
|
||||||
| expr2 '*' expr3
|
|
||||||
{
|
|
||||||
$$ = $1.Mul($1, $3)
|
|
||||||
}
|
|
||||||
| expr2 '/' expr3
|
|
||||||
{
|
|
||||||
$$ = $1.Quo($1, $3)
|
|
||||||
}
|
|
||||||
|
|
||||||
expr3:
|
|
||||||
NUM
|
|
||||||
| '(' expr ')'
|
|
||||||
{
|
|
||||||
$$ = $2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
%%
|
|
||||||
|
|
||||||
// The parser expects the lexer to return 0 on EOF. Give it a name
|
|
||||||
// for clarity.
|
|
||||||
const eof = 0
|
|
||||||
|
|
||||||
// The parser uses the type <prefix>Lex as a lexer. It must provide
|
|
||||||
// the methods Lex(*<prefix>SymType) int and Error(string).
|
|
||||||
type exprLex struct {
|
|
||||||
line []byte
|
|
||||||
peek rune
|
|
||||||
}
|
|
||||||
|
|
||||||
// The parser calls this method to get each new token. This
|
|
||||||
// implementation returns operators and NUM.
|
|
||||||
func (x *exprLex) Lex(yylval *exprSymType) int {
|
|
||||||
for {
|
|
||||||
c := x.next()
|
|
||||||
switch c {
|
|
||||||
case eof:
|
|
||||||
return eof
|
|
||||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
||||||
return x.num(c, yylval)
|
|
||||||
case '+', '-', '*', '/', '(', ')':
|
|
||||||
return int(c)
|
|
||||||
|
|
||||||
// Recognize Unicode multiplication and division
|
|
||||||
// symbols, returning what the parser expects.
|
|
||||||
case '×':
|
|
||||||
return '*'
|
|
||||||
case '÷':
|
|
||||||
return '/'
|
|
||||||
|
|
||||||
case ' ', '\t', '\n', '\r':
|
|
||||||
default:
|
|
||||||
log.Printf("unrecognized character %q", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lex a number.
|
|
||||||
func (x *exprLex) num(c rune, yylval *exprSymType) int {
|
|
||||||
add := func(b *bytes.Buffer, c rune) {
|
|
||||||
if _, err := b.WriteRune(c); err != nil {
|
|
||||||
log.Fatalf("WriteRune: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
add(&b, c)
|
|
||||||
L: for {
|
|
||||||
c = x.next()
|
|
||||||
switch c {
|
|
||||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E':
|
|
||||||
add(&b, c)
|
|
||||||
default:
|
|
||||||
break L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c != eof {
|
|
||||||
x.peek = c
|
|
||||||
}
|
|
||||||
yylval.num = &big.Rat{}
|
|
||||||
_, ok := yylval.num.SetString(b.String())
|
|
||||||
if !ok {
|
|
||||||
log.Printf("bad number %q", b.String())
|
|
||||||
return eof
|
|
||||||
}
|
|
||||||
return NUM
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the next rune for the lexer.
|
|
||||||
func (x *exprLex) next() rune {
|
|
||||||
if x.peek != eof {
|
|
||||||
r := x.peek
|
|
||||||
x.peek = eof
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if len(x.line) == 0 {
|
|
||||||
return eof
|
|
||||||
}
|
|
||||||
c, size := utf8.DecodeRune(x.line)
|
|
||||||
x.line = x.line[size:]
|
|
||||||
if c == utf8.RuneError && size == 1 {
|
|
||||||
log.Print("invalid utf8")
|
|
||||||
return x.next()
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// The parser calls this method on a parse error.
|
|
||||||
func (x *exprLex) Error(s string) {
|
|
||||||
log.Printf("parse error: %s", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
in := bufio.NewReader(os.Stdin)
|
|
||||||
for {
|
|
||||||
if _, err := os.Stdout.WriteString("> "); err != nil {
|
|
||||||
log.Fatalf("WriteString: %s", err)
|
|
||||||
}
|
|
||||||
line, err := in.ReadBytes('\n')
|
|
||||||
if err == io.EOF {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("ReadBytes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exprParse(&exprLex{line: line})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This file holds the go generate command to run yacc on the grammar in expr.y.
|
|
||||||
// To build expr:
|
|
||||||
// % go generate
|
|
||||||
// % go build
|
|
||||||
|
|
||||||
//go:generate goyacc -o expr.go -p "expr" expr.y
|
|
||||||
|
|
||||||
// Expr is a simple expression evaluator that serves as a working example of
|
|
||||||
// how to use Go's yacc implementation.
|
|
||||||
package main
|
|
3592
cmd/goyacc/yacc.go
3592
cmd/goyacc/yacc.go
File diff suppressed because it is too large
Load Diff
|
@ -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}
|
||||||
|
@ -28,7 +28,7 @@ func callees(q *Query) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
// Load/parse/type-check the program.
|
||||||
lprog, err := loadWithSoftErrors(&lconf)
|
lprog, err := lconf.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -27,7 +27,7 @@ func callers(q *Query) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
// Load/parse/type-check the program.
|
||||||
lprog, err := loadWithSoftErrors(&lconf)
|
lprog, err := lconf.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -35,7 +35,7 @@ func callstack(q *Query) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
// Load/parse/type-check the program.
|
||||||
lprog, err := loadWithSoftErrors(&lconf)
|
lprog, err := lconf.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,19 +86,12 @@ func definition(q *Query) error {
|
||||||
return fmt.Errorf("no identifier here")
|
return fmt.Errorf("no identifier here")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the declaration of this identifier.
|
obj := qpos.info.ObjectOf(id)
|
||||||
// If id is an anonymous field declaration,
|
|
||||||
// it is both a use of a type and a def of a field;
|
|
||||||
// prefer the use in that case.
|
|
||||||
obj := qpos.info.Uses[id]
|
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
obj = qpos.info.Defs[id]
|
// Happens for y in "switch y := x.(type)",
|
||||||
if obj == nil {
|
// and the package declaration,
|
||||||
// Happens for y in "switch y := x.(type)",
|
// but I think that's all.
|
||||||
// and the package declaration,
|
return fmt.Errorf("no object for identifier")
|
||||||
// but I think that's all.
|
|
||||||
return fmt.Errorf("no object for identifier")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !obj.Pos().IsValid() {
|
if !obj.Pos().IsValid() {
|
||||||
|
|
|
@ -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,57 +327,29 @@ 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 {
|
|
||||||
constVal = c.Val()
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -385,7 +357,7 @@ type describeValueResult struct {
|
||||||
func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
||||||
var prefix, suffix string
|
var prefix, suffix string
|
||||||
if r.constVal != nil {
|
if r.constVal != nil {
|
||||||
suffix = fmt.Sprintf(" of value %s", r.constVal)
|
suffix = fmt.Sprintf(" of constant value %s", constValString(r.constVal))
|
||||||
}
|
}
|
||||||
switch obj := r.obj.(type) {
|
switch obj := r.obj.(type) {
|
||||||
case *types.Func:
|
case *types.Func:
|
||||||
|
@ -423,7 +395,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 +406,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 +422,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 +523,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)
|
||||||
|
|
||||||
|
@ -706,41 +657,47 @@ func (r *describePackageResult) PrintPlain(printf printfFunc) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to adjust go1.5 numeric go/constant formatting.
|
||||||
|
// Can be removed once we give up compatibility with go1.5.
|
||||||
|
func constValString(v exact.Value) string {
|
||||||
|
if v.Kind() == exact.Float {
|
||||||
|
// In go1.5, go/constant floating-point values are printed
|
||||||
|
// as fractions. Make them appear as floating-point numbers.
|
||||||
|
f, _ := exact.Float64Val(v)
|
||||||
|
return fmt.Sprintf("%g", f)
|
||||||
|
}
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
func formatMember(obj types.Object, maxname int) string {
|
func formatMember(obj types.Object, maxname int) string {
|
||||||
qualifier := types.RelativeTo(obj.Pkg())
|
qualifier := types.RelativeTo(obj.Pkg())
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
||||||
switch obj := obj.(type) {
|
switch obj := obj.(type) {
|
||||||
case *types.Const:
|
case *types.Const:
|
||||||
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val())
|
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), constValString(obj.Val()))
|
||||||
|
|
||||||
case *types.Func:
|
case *types.Func:
|
||||||
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 +708,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 = constValString(mem.Val())
|
||||||
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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Simple test of Go guru/Emacs integration.
|
||||||
|
# Requires that GOROOT and GOPATH are set.
|
||||||
|
# Side effect: builds and installs guru in $GOROOT.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
[ -z "$GOROOT" ] && { echo "Error: GOROOT is unset." >&2; exit 1; }
|
||||||
|
[ -z "$GOPATH" ] && { echo "Error: GOPATH is unset." >&2; exit 1; }
|
||||||
|
|
||||||
|
log=/tmp/$(basename $0)-$$.log
|
||||||
|
thisdir=$(dirname $0)
|
||||||
|
|
||||||
|
function die() {
|
||||||
|
echo "Error: $@."
|
||||||
|
cat $log
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
trap "rm -f $log" EXIT
|
||||||
|
|
||||||
|
# Build and install guru.
|
||||||
|
go get golang.org/x/tools/cmd/guru || die "'go get' failed"
|
||||||
|
mv -f $GOPATH/bin/guru $GOROOT/bin/
|
||||||
|
$GOROOT/bin/guru >$log 2>&1 || true # (prints usage and exits 1)
|
||||||
|
grep -q "Run.*help" $log || die "$GOROOT/bin/guru not installed"
|
||||||
|
|
||||||
|
# Usage: run_emacs <elisp>
|
||||||
|
function run_emacs() {
|
||||||
|
emacs --batch --no-splash --no-window-system --no-init \
|
||||||
|
--load $GOPATH/src/github.com/dominikh/go-mode.el/go-mode.el \
|
||||||
|
--load $thisdir/guru.el \
|
||||||
|
--eval "$1" >$log 2>&1 || die "emacs command failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: expect_log <regex>
|
||||||
|
function expect_log() {
|
||||||
|
grep -q "$1" $log || die "didn't find expected lines in log; got:"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load main.go and describe the "fmt" import.
|
||||||
|
# Check that Println is mentioned.
|
||||||
|
run_emacs '
|
||||||
|
(progn
|
||||||
|
(princ (emacs-version)) ; requires Emacs v23
|
||||||
|
(find-file "'$thisdir'/main.go")
|
||||||
|
(insert "// modify but do not save the editor buffer\n")
|
||||||
|
(search-forward "\"fmt\"")
|
||||||
|
(backward-char)
|
||||||
|
(go-guru-describe)
|
||||||
|
(princ (with-current-buffer "*go-guru*"
|
||||||
|
(buffer-substring-no-properties (point-min) (point-max))))
|
||||||
|
(kill-emacs 0))
|
||||||
|
'
|
||||||
|
expect_log "fmt/print.go.*func Println"
|
||||||
|
|
||||||
|
# Jump to the definition of flag.Bool.
|
||||||
|
run_emacs '
|
||||||
|
(progn
|
||||||
|
(find-file "'$thisdir'/main.go")
|
||||||
|
(search-forward "flag.Bool")
|
||||||
|
(backward-char)
|
||||||
|
(go-guru-definition)
|
||||||
|
(message "file: %s" (buffer-file-name))
|
||||||
|
(message "line: %s" (buffer-substring (line-beginning-position)
|
||||||
|
(line-end-position)))
|
||||||
|
(kill-emacs 0))
|
||||||
|
'
|
||||||
|
expect_log "^file: .*flag.go"
|
||||||
|
expect_log "^line: func Bool"
|
||||||
|
|
||||||
|
echo "PASS"
|
|
@ -0,0 +1,551 @@
|
||||||
|
;;; go-guru.el --- Integration of the Go 'guru' analysis tool into Emacs.
|
||||||
|
|
||||||
|
;; 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.
|
||||||
|
|
||||||
|
;; Version: 0.1
|
||||||
|
;; Package-Requires: ((go-mode "1.3.1") (cl-lib "0.5"))
|
||||||
|
;; Keywords: tools
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
|
||||||
|
;; To enable the Go guru in Emacs, use this command to download,
|
||||||
|
;; build, and install the tool in $GOROOT/bin:
|
||||||
|
;;
|
||||||
|
;; $ go get golang.org/x/tools/cmd/guru
|
||||||
|
;;
|
||||||
|
;; Verify that the tool is on your $PATH:
|
||||||
|
;;
|
||||||
|
;; $ guru -help
|
||||||
|
;; Go source code guru.
|
||||||
|
;; Usage: guru [flags] <mode> <position>
|
||||||
|
;; ...
|
||||||
|
;;
|
||||||
|
;; Then copy this file to a directory on your `load-path',
|
||||||
|
;; and add this to your ~/.emacs:
|
||||||
|
;;
|
||||||
|
;; (require 'go-guru)
|
||||||
|
;;
|
||||||
|
;; Inside a buffer of Go source code, select an expression of
|
||||||
|
;; interest, and type `C-c C-o d' (for "describe") or run one of the
|
||||||
|
;; other go-guru-xxx commands. If you use `menu-bar-mode', these
|
||||||
|
;; commands are available from the Guru menu.
|
||||||
|
;;
|
||||||
|
;; To enable identifier highlighting mode in a Go source buffer, use:
|
||||||
|
;;
|
||||||
|
;; (go-guru-hl-identifier-mode)
|
||||||
|
;;
|
||||||
|
;; To enable it automatically in all Go source buffers,
|
||||||
|
;; add this to your ~/.emacs:
|
||||||
|
;;
|
||||||
|
;; (add-hook 'go-mode-hook #'go-guru-hl-identifier-mode)
|
||||||
|
;;
|
||||||
|
;; See http://golang.org/s/using-guru for more information about guru.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'compile)
|
||||||
|
(require 'easymenu)
|
||||||
|
(require 'go-mode)
|
||||||
|
(require 'json)
|
||||||
|
(require 'simple)
|
||||||
|
(require 'cl)
|
||||||
|
|
||||||
|
(defgroup go-guru nil
|
||||||
|
"Options specific to the Go guru."
|
||||||
|
:group 'go)
|
||||||
|
|
||||||
|
(defcustom go-guru-command "guru"
|
||||||
|
"The Go guru command."
|
||||||
|
:type 'string
|
||||||
|
:group 'go-guru)
|
||||||
|
|
||||||
|
(defcustom go-guru-scope ""
|
||||||
|
"The scope of the analysis. See `go-guru-set-scope'."
|
||||||
|
:type 'string
|
||||||
|
:group 'go-guru)
|
||||||
|
|
||||||
|
(defvar go-guru--scope-history
|
||||||
|
nil
|
||||||
|
"History of values supplied to `go-guru-set-scope'.")
|
||||||
|
|
||||||
|
(defcustom go-guru-build-tags ""
|
||||||
|
"Build tags passed to guru."
|
||||||
|
:type 'string
|
||||||
|
:group 'go-guru)
|
||||||
|
|
||||||
|
(defface go-guru-hl-identifier-face
|
||||||
|
'((t (:inherit highlight)))
|
||||||
|
"Face used for highlighting identifiers in `go-guru-hl-identifier'."
|
||||||
|
:group 'go-guru)
|
||||||
|
|
||||||
|
(defcustom go-guru-debug nil
|
||||||
|
"Print debug messages when running guru."
|
||||||
|
:type 'boolean
|
||||||
|
:group 'go-guru)
|
||||||
|
|
||||||
|
(defcustom go-guru-hl-identifier-idle-time 0.5
|
||||||
|
"How long to wait after user input before highlighting the current identifier."
|
||||||
|
:type 'float
|
||||||
|
:group 'go-guru)
|
||||||
|
|
||||||
|
(defvar go-guru--current-hl-identifier-idle-time
|
||||||
|
0
|
||||||
|
"The current delay for hl-identifier-mode.")
|
||||||
|
|
||||||
|
(defvar go-guru--hl-identifier-timer
|
||||||
|
nil
|
||||||
|
"The global timer used for highlighting identifiers.")
|
||||||
|
|
||||||
|
(defvar go-guru--last-enclosing
|
||||||
|
nil
|
||||||
|
"The remaining enclosing regions of the previous go-expand-region invocation.")
|
||||||
|
|
||||||
|
;; Extend go-mode-map.
|
||||||
|
(let ((m (define-prefix-command 'go-guru-map)))
|
||||||
|
(define-key m "d" #'go-guru-describe)
|
||||||
|
(define-key m "f" #'go-guru-freevars)
|
||||||
|
(define-key m "i" #'go-guru-implements)
|
||||||
|
(define-key m "c" #'go-guru-peers) ; c for channel
|
||||||
|
(define-key m "r" #'go-guru-referrers)
|
||||||
|
(define-key m "j" #'go-guru-definition) ; j for jump
|
||||||
|
(define-key m "p" #'go-guru-pointsto)
|
||||||
|
(define-key m "s" #'go-guru-callstack) ; s for stack
|
||||||
|
(define-key m "e" #'go-guru-whicherrs) ; e for error
|
||||||
|
(define-key m "<" #'go-guru-callers)
|
||||||
|
(define-key m ">" #'go-guru-callees)
|
||||||
|
(define-key m "x" #'go-guru-expand-region)) ;; x for expand
|
||||||
|
|
||||||
|
(define-key go-mode-map (kbd "C-c C-o") #'go-guru-map)
|
||||||
|
|
||||||
|
(easy-menu-define go-guru-mode-menu go-mode-map
|
||||||
|
"Menu for Go Guru."
|
||||||
|
'("Guru"
|
||||||
|
["Jump to Definition" go-guru-definition t]
|
||||||
|
["Show Referrers" go-guru-referrers t]
|
||||||
|
["Show Free Names" go-guru-freevars t]
|
||||||
|
["Describe Expression" go-guru-describe t]
|
||||||
|
["Show Implements" go-guru-implements t]
|
||||||
|
"---"
|
||||||
|
["Show Callers" go-guru-callers t]
|
||||||
|
["Show Callees" go-guru-callees t]
|
||||||
|
["Show Callstack" go-guru-callstack t]
|
||||||
|
"---"
|
||||||
|
["Show Points-To" go-guru-pointsto t]
|
||||||
|
["Show Which Errors" go-guru-whicherrs t]
|
||||||
|
["Show Channel Peers" go-guru-peers t]
|
||||||
|
"---"
|
||||||
|
["Set pointer analysis scope..." go-guru-set-scope t]))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-set-scope ()
|
||||||
|
"Set the scope for the Go guru, prompting the user to edit the previous scope.
|
||||||
|
|
||||||
|
The scope restricts analysis to the specified packages.
|
||||||
|
Its value is a comma-separated list of patterns of these forms:
|
||||||
|
golang.org/x/tools/cmd/guru # a single package
|
||||||
|
golang.org/x/tools/... # all packages beneath dir
|
||||||
|
... # the entire workspace.
|
||||||
|
|
||||||
|
A pattern preceded by '-' is negative, so the scope
|
||||||
|
encoding/...,-encoding/xml
|
||||||
|
matches all encoding packages except encoding/xml."
|
||||||
|
(interactive)
|
||||||
|
(let ((scope (read-from-minibuffer "Go guru scope: "
|
||||||
|
go-guru-scope
|
||||||
|
nil
|
||||||
|
nil
|
||||||
|
'go-guru--scope-history)))
|
||||||
|
(if (string-equal "" scope)
|
||||||
|
(error "You must specify a non-empty scope for the Go guru"))
|
||||||
|
(setq go-guru-scope scope)))
|
||||||
|
|
||||||
|
(defun go-guru--set-scope-if-empty ()
|
||||||
|
(if (string-equal "" go-guru-scope)
|
||||||
|
(go-guru-set-scope)))
|
||||||
|
|
||||||
|
(defun go-guru--json (mode)
|
||||||
|
"Execute the Go guru in the specified MODE, passing it the
|
||||||
|
selected region of the current buffer, requesting JSON output.
|
||||||
|
Parse and return the resulting JSON object."
|
||||||
|
;; A "what" query works even in a buffer without a file name.
|
||||||
|
(let* ((filename (file-truename (or buffer-file-name "synthetic.go")))
|
||||||
|
(cmd (go-guru--command mode filename '("-json")))
|
||||||
|
(buf (current-buffer))
|
||||||
|
;; Use temporary buffers to avoid conflict with go-guru--start.
|
||||||
|
(json-buffer (generate-new-buffer "*go-guru-json-output*"))
|
||||||
|
(input-buffer (generate-new-buffer "*go-guru-json-input*")))
|
||||||
|
(unwind-protect
|
||||||
|
;; Run guru, feeding it the input buffer (modified files).
|
||||||
|
(with-current-buffer input-buffer
|
||||||
|
(go-guru--insert-modified-files)
|
||||||
|
(unless (buffer-file-name buf)
|
||||||
|
(go-guru--insert-modified-file filename buf))
|
||||||
|
(let ((exitcode (apply #'call-process-region
|
||||||
|
(append (list (point-min)
|
||||||
|
(point-max)
|
||||||
|
(car cmd) ; guru
|
||||||
|
nil ; delete
|
||||||
|
json-buffer ; output
|
||||||
|
nil) ; display
|
||||||
|
(cdr cmd))))) ; args
|
||||||
|
(with-current-buffer json-buffer
|
||||||
|
(unless (zerop exitcode)
|
||||||
|
;; Failed: use buffer contents (sans final \n) as an error.
|
||||||
|
(error "%s" (buffer-substring (point-min) (1- (point-max)))))
|
||||||
|
;; Success: parse JSON.
|
||||||
|
(goto-char (point-min))
|
||||||
|
(json-read))))
|
||||||
|
;; Clean up temporary buffers.
|
||||||
|
(kill-buffer json-buffer)
|
||||||
|
(kill-buffer input-buffer))))
|
||||||
|
|
||||||
|
(define-compilation-mode go-guru-output-mode "Go guru"
|
||||||
|
"Go guru output mode is a variant of `compilation-mode' for the
|
||||||
|
output of the Go guru tool."
|
||||||
|
(set (make-local-variable 'compilation-error-screen-columns) nil)
|
||||||
|
(set (make-local-variable 'compilation-filter-hook) #'go-guru--compilation-filter-hook)
|
||||||
|
(set (make-local-variable 'compilation-start-hook) #'go-guru--compilation-start-hook))
|
||||||
|
|
||||||
|
(defun go-guru--compilation-filter-hook ()
|
||||||
|
"Post-process a blob of input to the go-guru-output buffer."
|
||||||
|
;; For readability, truncate each "file:line:col:" prefix to a fixed width.
|
||||||
|
;; If the prefix is longer than 20, show "…/last/19chars.go".
|
||||||
|
;; This usually includes the last segment of the package name.
|
||||||
|
;; Hide the line and column numbers.
|
||||||
|
(let ((start compilation-filter-start)
|
||||||
|
(end (point)))
|
||||||
|
(goto-char start)
|
||||||
|
(unless (bolp)
|
||||||
|
;; TODO(adonovan): not quite right: the filter may be called
|
||||||
|
;; with chunks of output containing incomplete lines. Moving to
|
||||||
|
;; beginning-of-line may cause duplicate post-processing.
|
||||||
|
(beginning-of-line))
|
||||||
|
(setq start (point))
|
||||||
|
(while (< start end)
|
||||||
|
(let ((p (search-forward ": " end t)))
|
||||||
|
(if (null p)
|
||||||
|
(setq start end) ; break out of loop
|
||||||
|
(setq p (1- p)) ; exclude final space
|
||||||
|
(let* ((posn (buffer-substring-no-properties start p))
|
||||||
|
(flen (search ":" posn)) ; length of filename
|
||||||
|
(filename (if (< flen 19)
|
||||||
|
(substring posn 0 flen)
|
||||||
|
(concat "…" (substring posn (- flen 19) flen)))))
|
||||||
|
(put-text-property start p 'display filename)
|
||||||
|
(forward-line 1)
|
||||||
|
(setq start (point))))))))
|
||||||
|
|
||||||
|
(defun go-guru--compilation-start-hook (proc)
|
||||||
|
"Erase default output header inserted by `compilation-mode'."
|
||||||
|
(with-current-buffer (process-buffer proc)
|
||||||
|
(let ((inhibit-read-only t))
|
||||||
|
(beginning-of-buffer)
|
||||||
|
(delete-region (point) (point-max)))))
|
||||||
|
|
||||||
|
(defun go-guru--start (mode)
|
||||||
|
"Start an asynchronous Go guru process for the specified query
|
||||||
|
MODE, passing it the selected region of the current buffer, and
|
||||||
|
feeding its standard input with the contents of all modified Go
|
||||||
|
buffers. Its output is handled by `go-guru-output-mode', a
|
||||||
|
variant of `compilation-mode'."
|
||||||
|
(or buffer-file-name
|
||||||
|
(error "Cannot use guru on a buffer without a file name"))
|
||||||
|
(let* ((filename (file-truename buffer-file-name))
|
||||||
|
(cmd (mapconcat #'shell-quote-argument (go-guru--command mode filename) " "))
|
||||||
|
(process-connection-type nil) ; use pipe (not pty) so EOF closes stdin
|
||||||
|
(procbuf (compilation-start cmd 'go-guru-output-mode)))
|
||||||
|
(with-current-buffer procbuf
|
||||||
|
(setq truncate-lines t)) ; the output is neater without line wrapping
|
||||||
|
(with-current-buffer (get-buffer-create "*go-guru-input*")
|
||||||
|
(erase-buffer)
|
||||||
|
(go-guru--insert-modified-files)
|
||||||
|
(process-send-region procbuf (point-min) (point-max))
|
||||||
|
(process-send-eof procbuf))
|
||||||
|
procbuf))
|
||||||
|
|
||||||
|
(defun go-guru--command (mode filename &optional flags)
|
||||||
|
"Return a command and argument list for a Go guru query of MODE, passing it
|
||||||
|
the selected region of the current buffer. FILENAME is the
|
||||||
|
effective name of the current buffer."
|
||||||
|
(let* ((posn (if (use-region-p)
|
||||||
|
(format "%s:#%d,#%d"
|
||||||
|
filename
|
||||||
|
(1- (go--position-bytes (region-beginning)))
|
||||||
|
(1- (go--position-bytes (region-end))))
|
||||||
|
(format "%s:#%d"
|
||||||
|
filename
|
||||||
|
(1- (go--position-bytes (point))))))
|
||||||
|
(cmd (append (list go-guru-command
|
||||||
|
"-modified"
|
||||||
|
"-scope" go-guru-scope
|
||||||
|
(format "-tags=%s" (mapconcat 'identity go-guru-build-tags ",")))
|
||||||
|
flags
|
||||||
|
(list mode
|
||||||
|
posn))))
|
||||||
|
;; Log the command to *Messages*, for debugging.
|
||||||
|
(when go-guru-debug
|
||||||
|
(message "go-guru--command: %s" cmd)
|
||||||
|
(message nil)) ; clear/shrink minibuffer
|
||||||
|
cmd))
|
||||||
|
|
||||||
|
(defun go-guru--insert-modified-files ()
|
||||||
|
"Insert the contents of each modified Go buffer into the
|
||||||
|
current buffer in the format specified by guru's -modified flag."
|
||||||
|
(mapc #'(lambda (b)
|
||||||
|
(and (buffer-modified-p b)
|
||||||
|
(buffer-file-name b)
|
||||||
|
(string= (file-name-extension (buffer-file-name b)) "go")
|
||||||
|
(go-guru--insert-modified-file (buffer-file-name b) b)))
|
||||||
|
(buffer-list)))
|
||||||
|
|
||||||
|
(defun go-guru--insert-modified-file (name buffer)
|
||||||
|
(insert (format "%s\n%d\n" name (go-guru--buffer-size-bytes buffer)))
|
||||||
|
(insert-buffer-substring buffer))
|
||||||
|
|
||||||
|
(defun go-guru--buffer-size-bytes (&optional buffer)
|
||||||
|
"Return the number of bytes in the current buffer.
|
||||||
|
If BUFFER, return the number of characters in that buffer instead."
|
||||||
|
(with-current-buffer (or buffer (current-buffer))
|
||||||
|
(string-bytes (buffer-substring (point-min)
|
||||||
|
(point-max)))))
|
||||||
|
|
||||||
|
(defun go-guru--goto-byte (offset)
|
||||||
|
"Go to the OFFSETth byte in the buffer."
|
||||||
|
(goto-char (byte-to-position offset)))
|
||||||
|
|
||||||
|
(defun go-guru--goto-byte-column (offset)
|
||||||
|
"Go to the OFFSETth byte in the current line."
|
||||||
|
(goto-char (byte-to-position (+ (position-bytes (point-at-bol)) (1- offset)))))
|
||||||
|
|
||||||
|
(defun go-guru--goto-pos (posn)
|
||||||
|
"Find the file containing the position POSN (of the form `file:line:col')
|
||||||
|
set the point to it, switching the current buffer."
|
||||||
|
(let ((file-line-pos (split-string posn ":")))
|
||||||
|
(find-file (car file-line-pos))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(forward-line (1- (string-to-number (cadr file-line-pos))))
|
||||||
|
(go-guru--goto-byte-column (string-to-number (caddr file-line-pos)))))
|
||||||
|
|
||||||
|
(defun go-guru--goto-pos-no-file (posn)
|
||||||
|
"Given `file:line:col', go to the line and column. The file
|
||||||
|
component will be ignored."
|
||||||
|
(let ((file-line-pos (split-string posn ":")))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(forward-line (1- (string-to-number (cadr file-line-pos))))
|
||||||
|
(go-guru--goto-byte-column (string-to-number (caddr file-line-pos)))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-callees ()
|
||||||
|
"Show possible callees of the function call at the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--set-scope-if-empty)
|
||||||
|
(go-guru--start "callees"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-callers ()
|
||||||
|
"Show the set of callers of the function containing the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--set-scope-if-empty)
|
||||||
|
(go-guru--start "callers"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-callstack ()
|
||||||
|
"Show an arbitrary path from a root of the call graph to the
|
||||||
|
function containing the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--set-scope-if-empty)
|
||||||
|
(go-guru--start "callstack"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-definition ()
|
||||||
|
"Jump to the definition of the selected identifier."
|
||||||
|
(interactive)
|
||||||
|
(or buffer-file-name
|
||||||
|
(error "Cannot use guru on a buffer without a file name"))
|
||||||
|
(let* ((res (go-guru--json "definition"))
|
||||||
|
(desc (cdr (assoc 'desc res))))
|
||||||
|
(push-mark)
|
||||||
|
(ring-insert find-tag-marker-ring (point-marker))
|
||||||
|
(go-guru--goto-pos (cdr (assoc 'objpos res)))
|
||||||
|
(message "%s" desc)))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-describe ()
|
||||||
|
"Describe the selected syntax, its kind, type and methods."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--start "describe"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-pointsto ()
|
||||||
|
"Show what the selected expression points to."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--set-scope-if-empty)
|
||||||
|
(go-guru--start "pointsto"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-implements ()
|
||||||
|
"Describe the 'implements' relation for types in the package
|
||||||
|
containing the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--start "implements"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-freevars ()
|
||||||
|
"Enumerate the free variables of the current selection."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--start "freevars"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-peers ()
|
||||||
|
"Enumerate the set of possible corresponding sends/receives for
|
||||||
|
this channel receive/send operation."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--set-scope-if-empty)
|
||||||
|
(go-guru--start "peers"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-referrers ()
|
||||||
|
"Enumerate all references to the object denoted by the selected
|
||||||
|
identifier."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--start "referrers"))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-whicherrs ()
|
||||||
|
"Show globals, constants and types to which the selected
|
||||||
|
expression (of type 'error') may refer."
|
||||||
|
(interactive)
|
||||||
|
(go-guru--set-scope-if-empty)
|
||||||
|
(go-guru--start "whicherrs"))
|
||||||
|
|
||||||
|
(defun go-guru-what ()
|
||||||
|
"Run a 'what' query and return the parsed JSON response as an
|
||||||
|
association list."
|
||||||
|
(go-guru--json "what"))
|
||||||
|
|
||||||
|
(defun go-guru--hl-symbols (posn face id)
|
||||||
|
"Highlight the symbols at the positions POSN by creating
|
||||||
|
overlays with face FACE. The attribute 'go-guru-overlay on the
|
||||||
|
overlays will be set to ID."
|
||||||
|
(save-excursion
|
||||||
|
(mapc (lambda (pos)
|
||||||
|
(go-guru--goto-pos-no-file pos)
|
||||||
|
(let ((x (make-overlay (point) (+ (point) (length (current-word))))))
|
||||||
|
(overlay-put x 'go-guru-overlay id)
|
||||||
|
(overlay-put x 'face face)))
|
||||||
|
posn)))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-unhighlight-identifiers ()
|
||||||
|
"Remove highlights from previously highlighted identifier."
|
||||||
|
(remove-overlays nil nil 'go-guru-overlay 'sameid))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun go-guru-hl-identifier ()
|
||||||
|
"Highlight all instances of the identifier under point. Removes
|
||||||
|
highlights from previously highlighted identifier."
|
||||||
|
(interactive)
|
||||||
|
(go-guru-unhighlight-identifiers)
|
||||||
|
(go-guru--hl-identifier))
|
||||||
|
|
||||||
|
(defun go-guru--hl-identifier ()
|
||||||
|
"Highlight all instances of the identifier under point."
|
||||||
|
(let ((posn (cdr (assoc 'sameids (go-guru-what)))))
|
||||||
|
(go-guru--hl-symbols posn 'go-guru-hl-identifier-face 'sameid)))
|
||||||
|
|
||||||
|
(defun go-guru--hl-identifiers-function ()
|
||||||
|
"Function run after an idle timeout, highlighting the
|
||||||
|
identifier at point, if necessary."
|
||||||
|
(when go-guru-hl-identifier-mode
|
||||||
|
(unless (go-guru--on-overlay-p 'sameid)
|
||||||
|
;; Ignore guru errors. Otherwise, we might end up with an error
|
||||||
|
;; every time the timer runs, e.g. because of a malformed
|
||||||
|
;; buffer.
|
||||||
|
(condition-case nil
|
||||||
|
(go-guru-hl-identifier)
|
||||||
|
(error nil)))
|
||||||
|
(unless (eq go-guru--current-hl-identifier-idle-time go-guru-hl-identifier-idle-time)
|
||||||
|
(go-guru--hl-set-timer))))
|
||||||
|
|
||||||
|
(defun go-guru--hl-set-timer ()
|
||||||
|
(if go-guru--hl-identifier-timer
|
||||||
|
(cancel-timer go-guru--hl-identifier-timer))
|
||||||
|
(setq go-guru--current-hl-identifier-idle-time go-guru-hl-identifier-idle-time)
|
||||||
|
(setq go-guru--hl-identifier-timer (run-with-idle-timer
|
||||||
|
go-guru-hl-identifier-idle-time
|
||||||
|
t
|
||||||
|
#'go-guru--hl-identifiers-function)))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(define-minor-mode go-guru-hl-identifier-mode
|
||||||
|
"Highlight instances of the identifier at point after a short
|
||||||
|
timeout."
|
||||||
|
:group 'go-guru
|
||||||
|
(if go-guru-hl-identifier-mode
|
||||||
|
(progn
|
||||||
|
(go-guru--hl-set-timer)
|
||||||
|
;; Unhighlight if point moves off identifier
|
||||||
|
(add-hook 'post-command-hook #'go-guru--hl-identifiers-post-command-hook nil t)
|
||||||
|
;; Unhighlight any time the buffer changes
|
||||||
|
(add-hook 'before-change-functions #'go-guru--hl-identifiers-before-change-function nil t))
|
||||||
|
(remove-hook 'post-command-hook #'go-guru--hl-identifiers-post-command-hook t)
|
||||||
|
(remove-hook 'before-change-functions #'go-guru--hl-identifiers-before-change-function t)
|
||||||
|
(go-guru-unhighlight-identifiers)))
|
||||||
|
|
||||||
|
(defun go-guru--on-overlay-p (id)
|
||||||
|
"Return whether point is on a guru overlay of type ID."
|
||||||
|
(find-if (lambda (el) (eq (overlay-get el 'go-guru-overlay) id)) (overlays-at (point))))
|
||||||
|
|
||||||
|
(defun go-guru--hl-identifiers-post-command-hook ()
|
||||||
|
(if (and go-guru-hl-identifier-mode
|
||||||
|
(not (go-guru--on-overlay-p 'sameid)))
|
||||||
|
(go-guru-unhighlight-identifiers)))
|
||||||
|
|
||||||
|
(defun go-guru--hl-identifiers-before-change-function (_beg _end)
|
||||||
|
(go-guru-unhighlight-identifiers))
|
||||||
|
|
||||||
|
;; TODO(dominikh): a future feature may be to cycle through all uses
|
||||||
|
;; of an identifier.
|
||||||
|
|
||||||
|
(defun go-guru--enclosing ()
|
||||||
|
"Return a list of enclosing regions."
|
||||||
|
(cdr (assoc 'enclosing (go-guru-what))))
|
||||||
|
|
||||||
|
(defun go-guru--enclosing-unique ()
|
||||||
|
"Return a list of enclosing regions, with duplicates removed.
|
||||||
|
Two regions are considered equal if they have the same start and
|
||||||
|
end point."
|
||||||
|
(let ((enclosing (go-guru--enclosing)))
|
||||||
|
(cl-remove-duplicates enclosing
|
||||||
|
:from-end t
|
||||||
|
:test (lambda (a b)
|
||||||
|
(and (= (cdr (assoc 'start a))
|
||||||
|
(cdr (assoc 'start b)))
|
||||||
|
(= (cdr (assoc 'end a))
|
||||||
|
(cdr (assoc 'end b))))))))
|
||||||
|
|
||||||
|
(defun go-guru-expand-region ()
|
||||||
|
"Expand region to the next enclosing syntactic unit."
|
||||||
|
(interactive)
|
||||||
|
(let* ((enclosing (if (eq last-command #'go-guru-expand-region)
|
||||||
|
go-guru--last-enclosing
|
||||||
|
(go-guru--enclosing-unique)))
|
||||||
|
(block (if (> (length enclosing) 0) (elt enclosing 0))))
|
||||||
|
(when block
|
||||||
|
(go-guru--goto-byte (1+ (cdr (assoc 'start block))))
|
||||||
|
(set-mark (byte-to-position (1+ (cdr (assoc 'end block)))))
|
||||||
|
(setq go-guru--last-enclosing (subseq enclosing 1))
|
||||||
|
(message "Region: %s" (cdr (assoc 'desc block)))
|
||||||
|
(setq deactivate-mark nil))))
|
||||||
|
|
||||||
|
|
||||||
|
(provide 'go-guru)
|
||||||
|
|
||||||
|
;; Local variables:
|
||||||
|
;; indent-tabs-mode: t
|
||||||
|
;; tab-width: 8
|
||||||
|
;; End
|
||||||
|
|
||||||
|
;;; go-guru.el ends here
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue