Compare commits
8 Commits
master
...
release-br
Author | SHA1 | Date |
---|---|---|
|
6220cba641 | |
|
ae17563914 | |
|
7771da7791 | |
|
9d32829149 | |
|
05ca1f09ae | |
|
46af848339 | |
|
26c35b4dcf | |
|
a84e830bb0 |
|
@ -4,6 +4,7 @@ Go is an open source project.
|
|||
|
||||
It is the work of hundreds of contributors. We appreciate your help!
|
||||
|
||||
|
||||
## Filing issues
|
||||
|
||||
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
|
||||
|
@ -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)
|
||||
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
|
||||
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 {
|
||||
Rel string `xml:"rel,attr,omitempty"`
|
||||
Rel string `xml:"rel,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 {
|
||||
|
|
42
blog/blog.go
42
blog/blog.go
|
@ -24,14 +24,7 @@ import (
|
|||
"golang.org/x/tools/present"
|
||||
)
|
||||
|
||||
var (
|
||||
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`,
|
||||
)
|
||||
)
|
||||
var validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)
|
||||
|
||||
// Config specifies Server configuration values.
|
||||
type Config struct {
|
||||
|
@ -48,7 +41,6 @@ type Config struct {
|
|||
FeedTitle string // The title of the Atom XML feed
|
||||
|
||||
PlayEnabled bool
|
||||
ServeLocalLinks bool // rewrite golang.org/{pkg,cmd} links to host-less, relative paths.
|
||||
}
|
||||
|
||||
// Doc represents an article adorned with presentation data.
|
||||
|
@ -81,17 +73,10 @@ type Server struct {
|
|||
func NewServer(cfg Config) (*Server, error) {
|
||||
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")
|
||||
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)
|
||||
return t.ParseFiles(root, path)
|
||||
return t.ParseFiles(root, filepath.Join(cfg.TemplatePath, name))
|
||||
}
|
||||
|
||||
s := &Server{cfg: cfg}
|
||||
|
@ -199,8 +184,8 @@ func (s *Server) loadDocs(root string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var html bytes.Buffer
|
||||
err = d.Render(&html, s.template.doc)
|
||||
html := new(bytes.Buffer)
|
||||
err = d.Render(html, s.template.doc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -425,18 +410,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
d.Doc = doc
|
||||
t = s.template.article
|
||||
}
|
||||
var err error
|
||||
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)
|
||||
}
|
||||
err := t.ExecuteTemplate(w, "root", d)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
@ -448,9 +422,3 @@ type docsByTime []*Doc
|
|||
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) 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// Bundle creates a single-source-file version of a source package
|
||||
// suitable for inclusion in a particular target package.
|
||||
//
|
||||
|
@ -88,14 +90,12 @@ import (
|
|||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
@ -105,8 +105,7 @@ var (
|
|||
outputFile = flag.String("o", "", "write output to `file` (default standard output)")
|
||||
dstPath = flag.String("dst", "", "set destination import `path` (default taken from current directory)")
|
||||
pkgName = flag.String("pkg", "", "set destination package `name` (default taken from current directory)")
|
||||
prefix = flag.String("prefix", "&_", "set bundled identifier prefix to `p` (default is \"&_\", where & stands for the original name)")
|
||||
underscore = flag.Bool("underscore", false, "rewrite golang.org/x/* to internal/x/* imports; temporary workaround for golang.org/issue/16333")
|
||||
prefix = flag.String("prefix", "", "set bundled identifier prefix to `p` (default source package name + \"_\")")
|
||||
|
||||
importMap = map[string]string{}
|
||||
)
|
||||
|
@ -200,11 +199,10 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Because there was a single Import call and Load succeeded,
|
||||
// InitialPackages is guaranteed to hold the sole requested package.
|
||||
info := lprog.InitialPackages()[0]
|
||||
if strings.Contains(prefix, "&") {
|
||||
prefix = strings.Replace(prefix, "&", info.Files[0].Name.Name, -1)
|
||||
info := lprog.Package(src)
|
||||
if prefix == "" {
|
||||
pkgName := info.Files[0].Name.Name
|
||||
prefix = pkgName + "_"
|
||||
}
|
||||
|
||||
objsToUpdate := make(map[types.Object]bool)
|
||||
|
@ -238,7 +236,7 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
|
|||
|
||||
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 != "" {
|
||||
fmt.Fprintf(&out, "//go:generate bundle %s\n", strings.Join(os.Args[1:], " "))
|
||||
} else {
|
||||
|
@ -259,64 +257,46 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
|
|||
|
||||
fmt.Fprintf(&out, "package %s\n\n", dstpkg)
|
||||
|
||||
// BUG(adonovan,shurcooL): bundle may generate incorrect code
|
||||
// due to shadowing between identifiers and imported package names.
|
||||
//
|
||||
// The generated code will either fail to compile or
|
||||
// (unlikely) compile successfully but have different behavior
|
||||
// than the original package. The risk of this happening is higher
|
||||
// when the original package has renamed imports (they're typically
|
||||
// renamed in order to resolve a shadow inside that particular .go file).
|
||||
|
||||
// TODO(adonovan,shurcooL):
|
||||
// - detect shadowing issues, and either return error or resolve them
|
||||
// Print a single declaration that imports all necessary packages.
|
||||
// TODO(adonovan):
|
||||
// - support renaming imports.
|
||||
// - preserve comments from the original import declarations.
|
||||
|
||||
// pkgStd and pkgExt are sets of printed import specs. This is done
|
||||
// to deduplicate instances of the same import name and path.
|
||||
var pkgStd = make(map[string]bool)
|
||||
var pkgExt = make(map[string]bool)
|
||||
for _, f := range info.Files {
|
||||
for _, imp := range f.Imports {
|
||||
path, err := strconv.Unquote(imp.Path.Value)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid import path string: %v", err) // Shouldn't happen here since conf.Load succeeded.
|
||||
if imp.Name != nil {
|
||||
return nil, fmt.Errorf("%s: renaming imports not supported",
|
||||
lprog.Fset.Position(imp.Pos()))
|
||||
}
|
||||
if path == dst {
|
||||
}
|
||||
}
|
||||
|
||||
var pkgStd, pkgExt []string
|
||||
for _, p := range info.Pkg.Imports() {
|
||||
if p.Path() == dst {
|
||||
continue
|
||||
}
|
||||
if newPath, ok := importMap[path]; ok {
|
||||
path = newPath
|
||||
x, ok := importMap[p.Path()]
|
||||
if !ok {
|
||||
x = p.Path()
|
||||
}
|
||||
|
||||
var name string
|
||||
if imp.Name != nil {
|
||||
name = imp.Name.Name
|
||||
}
|
||||
spec := fmt.Sprintf("%s %q", name, path)
|
||||
if isStandardImportPath(path) {
|
||||
pkgStd[spec] = true
|
||||
if isStandardImportPath(x) {
|
||||
pkgStd = append(pkgStd, x)
|
||||
} else {
|
||||
if *underscore {
|
||||
spec = strings.Replace(spec, "golang.org/x/", "internal/x/", 1)
|
||||
}
|
||||
pkgExt[spec] = true
|
||||
}
|
||||
pkgExt = append(pkgExt, x)
|
||||
}
|
||||
}
|
||||
|
||||
// Print a single declaration that imports all necessary packages.
|
||||
fmt.Fprintln(&out, "import (")
|
||||
for p := range pkgStd {
|
||||
fmt.Fprintf(&out, "\t%s\n", p)
|
||||
for _, p := range pkgStd {
|
||||
fmt.Fprintf(&out, "\t%q\n", p)
|
||||
}
|
||||
if len(pkgExt) > 0 {
|
||||
fmt.Fprintln(&out)
|
||||
for _, p := range pkgExt {
|
||||
fmt.Fprintf(&out, "\t%q\n", p)
|
||||
}
|
||||
for p := range pkgExt {
|
||||
fmt.Fprintf(&out, "\t%s\n", p)
|
||||
}
|
||||
fmt.Fprint(&out, ")\n\n")
|
||||
fmt.Fprintln(&out, ")\n")
|
||||
|
||||
// Modify and print each file.
|
||||
for _, f := range info.Files {
|
||||
|
@ -348,41 +328,25 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
|
|||
return true
|
||||
})
|
||||
|
||||
last := f.Package
|
||||
if len(f.Imports) > 0 {
|
||||
imp := f.Imports[len(f.Imports)-1]
|
||||
last = imp.End()
|
||||
if imp.Comment != nil {
|
||||
if e := imp.Comment.End(); e > last {
|
||||
last = e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pretty-print package-level declarations.
|
||||
// but no package or import declarations.
|
||||
//
|
||||
// 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
|
||||
for _, decl := range f.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
|
||||
continue
|
||||
}
|
||||
|
||||
beg, end := sourceRange(decl)
|
||||
|
||||
printComments(&out, f.Comments, last, beg)
|
||||
|
||||
buf.Reset()
|
||||
format.Node(&buf, lprog.Fset, &printer.CommentedNode{Node: decl, Comments: f.Comments})
|
||||
format.Node(&buf, lprog.Fset, decl)
|
||||
// Remove each "@@@." in the output.
|
||||
// TODO(adonovan): not hygienic.
|
||||
out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
|
||||
|
||||
last = printSameLineComment(&out, f.Comments, lprog.Fset, end)
|
||||
|
||||
out.WriteString("\n\n")
|
||||
}
|
||||
|
||||
printLastComments(&out, f.Comments, last)
|
||||
}
|
||||
|
||||
// Now format the entire thing.
|
||||
|
@ -394,69 +358,6 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// sourceRange returns the [beg, end) interval of source code
|
||||
// belonging to decl (incl. associated comments).
|
||||
func sourceRange(decl ast.Decl) (beg, end token.Pos) {
|
||||
beg = decl.Pos()
|
||||
end = decl.End()
|
||||
|
||||
var doc, com *ast.CommentGroup
|
||||
|
||||
switch d := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
doc = d.Doc
|
||||
if len(d.Specs) > 0 {
|
||||
switch spec := d.Specs[len(d.Specs)-1].(type) {
|
||||
case *ast.ValueSpec:
|
||||
com = spec.Comment
|
||||
case *ast.TypeSpec:
|
||||
com = spec.Comment
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
doc = d.Doc
|
||||
}
|
||||
|
||||
if doc != nil {
|
||||
beg = doc.Pos()
|
||||
}
|
||||
if com != nil && com.End() > end {
|
||||
end = com.End()
|
||||
}
|
||||
|
||||
return beg, end
|
||||
}
|
||||
|
||||
func printComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos, end token.Pos) {
|
||||
for _, cg := range comments {
|
||||
if pos <= cg.Pos() && cg.Pos() < end {
|
||||
for _, c := range cg.List {
|
||||
fmt.Fprintln(out, c.Text)
|
||||
}
|
||||
fmt.Fprintln(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const infinity = 1 << 30
|
||||
|
||||
func printLastComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos token.Pos) {
|
||||
printComments(out, comments, pos, infinity)
|
||||
}
|
||||
|
||||
func printSameLineComment(out *bytes.Buffer, comments []*ast.CommentGroup, fset *token.FileSet, pos token.Pos) token.Pos {
|
||||
tf := fset.File(pos)
|
||||
for _, cg := range comments {
|
||||
if pos <= cg.Pos() && tf.Line(cg.Pos()) == tf.Line(pos) {
|
||||
for _, c := range cg.List {
|
||||
fmt.Fprintln(out, c.Text)
|
||||
}
|
||||
return cg.End()
|
||||
}
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
type flagFunc func(string)
|
||||
|
||||
func (f flagFunc) Set(s string) error {
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// 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 (
|
||||
|
@ -30,7 +28,6 @@ func TestBundle(t *testing.T) {
|
|||
"initial": {
|
||||
"a.go": load("testdata/src/initial/a.go"),
|
||||
"b.go": load("testdata/src/initial/b.go"),
|
||||
"c.go": load("testdata/src/initial/c.go"),
|
||||
},
|
||||
"domain.name/importdecl": {
|
||||
"p.go": load("testdata/src/domain.name/importdecl/p.go"),
|
||||
|
|
|
@ -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
|
||||
|
||||
// The package doc comment
|
||||
|
@ -8,10 +8,6 @@ package dest
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
. "fmt"
|
||||
_ "fmt"
|
||||
renamedfmt "fmt"
|
||||
renamedfmt2 "fmt"
|
||||
|
||||
"domain.name/importdecl"
|
||||
)
|
||||
|
@ -23,40 +19,15 @@ func init() { prefixfoo() }
|
|||
type prefixS struct {
|
||||
prefixt
|
||||
u int
|
||||
} /* multi-line
|
||||
comment
|
||||
*/
|
||||
|
||||
// non-associated comment
|
||||
|
||||
/*
|
||||
non-associated comment2
|
||||
*/
|
||||
|
||||
// Function bar.
|
||||
func prefixbar(s *prefixS) {
|
||||
fmt.Println(s.prefixt, s.u) // comment inside function
|
||||
}
|
||||
|
||||
// file-end comment
|
||||
// Function bar.
|
||||
func prefixbar(s *prefixS) { fmt.Println(s.prefixt, s.u) }
|
||||
|
||||
type prefixt int // type1
|
||||
type prefixt int
|
||||
|
||||
// const1
|
||||
const prefixc = 1 // const2
|
||||
const prefixc = 1
|
||||
|
||||
func prefixfoo() {
|
||||
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
|
||||
|
||||
import "fmt" // this comment should not be visible
|
||||
import "fmt"
|
||||
|
||||
// init functions are not renamed
|
||||
func init() { foo() }
|
||||
|
@ -9,19 +9,7 @@ func init() { foo() }
|
|||
type S struct {
|
||||
t
|
||||
u int
|
||||
} /* multi-line
|
||||
comment
|
||||
*/
|
||||
|
||||
// non-associated comment
|
||||
|
||||
/*
|
||||
non-associated comment2
|
||||
*/
|
||||
|
||||
// Function bar.
|
||||
func bar(s *S) {
|
||||
fmt.Println(s.t, s.u) // comment inside function
|
||||
}
|
||||
|
||||
// file-end comment
|
||||
// Function bar.
|
||||
func bar(s *S) { fmt.Println(s.t, s.u) }
|
||||
|
|
|
@ -7,17 +7,10 @@ import (
|
|||
"domain.name/importdecl"
|
||||
)
|
||||
|
||||
type t int // type1
|
||||
type t int
|
||||
|
||||
// const1
|
||||
const c = 1 // const2
|
||||
const c = 1
|
||||
|
||||
func foo() {
|
||||
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/rta"
|
||||
"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/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
|
@ -67,7 +67,7 @@ const Usage = `callgraph: display the the call graph of a Go program.
|
|||
|
||||
Usage:
|
||||
|
||||
callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] package...
|
||||
callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] <args>...
|
||||
|
||||
Flags:
|
||||
|
||||
|
@ -114,10 +114,12 @@ Flags:
|
|||
|
||||
Caller and Callee are *ssa.Function values, which print as
|
||||
"(*sync/atomic.Mutex).Lock", but other attributes may be
|
||||
derived from them, e.g. Caller.Pkg.Pkg.Path yields the
|
||||
derived from them, e.g. Caller.Pkg.Object.Path yields the
|
||||
import path of the enclosing package. Consult the go/ssa
|
||||
API documentation for details.
|
||||
|
||||
` + loader.FromArgsUsage + `
|
||||
|
||||
Examples:
|
||||
|
||||
Show the call graph of the trivial web server application:
|
||||
|
@ -126,7 +128,7 @@ Examples:
|
|||
|
||||
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
|
||||
|
||||
Show functions that make dynamic calls into the 'fmt' test package,
|
||||
|
@ -156,7 +158,7 @@ func init() {
|
|||
|
||||
func main() {
|
||||
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)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -164,30 +166,28 @@ func main() {
|
|||
|
||||
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 {
|
||||
fmt.Fprintln(os.Stderr, Usage)
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.LoadAllSyntax,
|
||||
Tests: tests,
|
||||
Dir: dir,
|
||||
}
|
||||
if gopath != "" {
|
||||
cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing
|
||||
}
|
||||
initial, err := packages.Load(cfg, args...)
|
||||
// Use the initial packages from the command line.
|
||||
args, err := conf.FromArgs(args, tests)
|
||||
if err != nil {
|
||||
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.
|
||||
prog, pkgs := ssautil.AllPackages(initial, 0)
|
||||
prog := ssautil.CreateProgram(iprog, 0)
|
||||
prog.Build()
|
||||
|
||||
// -- 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 {
|
||||
return err
|
||||
}
|
||||
config := &pointer.Config{
|
||||
Mains: mains,
|
||||
Mains: []*ssa.Package{main},
|
||||
BuildCallGraph: true,
|
||||
Log: ptalog,
|
||||
}
|
||||
|
@ -237,13 +237,13 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
|
|||
cg = ptares.CallGraph
|
||||
|
||||
case "rta":
|
||||
mains, err := mainPackages(pkgs)
|
||||
main, err := mainPackage(prog, tests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var roots []*ssa.Function
|
||||
for _, main := range mains {
|
||||
roots = append(roots, main.Func("init"), main.Func("main"))
|
||||
roots := []*ssa.Function{
|
||||
main.Func("init"),
|
||||
main.Func("main"),
|
||||
}
|
||||
rtares := rta.Analyze(roots, true)
|
||||
cg = rtares.CallGraph
|
||||
|
@ -303,19 +303,35 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
|
|||
return nil
|
||||
}
|
||||
|
||||
// mainPackages returns the main packages to analyze.
|
||||
// Each resulting package is named "main" and has a main function.
|
||||
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
|
||||
var mains []*ssa.Package
|
||||
for _, p := range pkgs {
|
||||
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
|
||||
mains = append(mains, p)
|
||||
// mainPackage returns the main package to analyze.
|
||||
// The resulting package has a main() function.
|
||||
func mainPackage(prog *ssa.Program, tests bool) (*ssa.Package, error) {
|
||||
pkgs := prog.AllPackages()
|
||||
|
||||
// TODO(adonovan): allow independent control over tests, mains and libraries.
|
||||
// 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
|
||||
}
|
||||
}
|
||||
if len(mains) == 0 {
|
||||
return nil, fmt.Errorf("no main packages")
|
||||
return nil, fmt.Errorf("no tests")
|
||||
}
|
||||
return mains, nil
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no main package")
|
||||
}
|
||||
|
||||
type Edge struct {
|
||||
|
|
|
@ -5,44 +5,31 @@
|
|||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
// +build go1.11
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"go/build"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// This test currently requires GOPATH mode.
|
||||
// Explicitly disabling module mode should suffix, but
|
||||
// we'll also turn off GOPROXY just for good measure.
|
||||
if err := os.Setenv("GO111MODULE", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.Setenv("GOPROXY", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallgraph(t *testing.T) {
|
||||
gopath, err := filepath.Abs("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctxt := build.Default // copy
|
||||
ctxt.GOPATH = "testdata"
|
||||
|
||||
const format = "{{.Caller}} --> {{.Callee}}"
|
||||
|
||||
for _, test := range []struct {
|
||||
algo string
|
||||
algo, format string
|
||||
tests bool
|
||||
want []string
|
||||
}{
|
||||
{"rta", false, []string{
|
||||
{"rta", format, false, []string{
|
||||
// rta imprecisely shows cross product of {main,main2} x {C,D}
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.D).f`,
|
||||
|
@ -50,7 +37,7 @@ func TestCallgraph(t *testing.T) {
|
|||
`pkg.main2 --> (pkg.C).f`,
|
||||
`pkg.main2 --> (pkg.D).f`,
|
||||
}},
|
||||
{"pta", false, []string{
|
||||
{"pta", format, false, []string{
|
||||
// pta distinguishes main->C, main2->D. Also has a root node.
|
||||
`<root> --> pkg.init`,
|
||||
`<root> --> pkg.main`,
|
||||
|
@ -58,42 +45,37 @@ func TestCallgraph(t *testing.T) {
|
|||
`pkg.main --> pkg.main2`,
|
||||
`pkg.main2 --> (pkg.D).f`,
|
||||
}},
|
||||
// tests: both the package's main and the test's main are called.
|
||||
// The callgraph includes all the guts of the "testing" package.
|
||||
{"rta", true, []string{
|
||||
`pkg.test.main --> testing.MainStart`,
|
||||
`testing.runExample --> pkg.Example`,
|
||||
// tests: main is not called.
|
||||
{"rta", format, true, []string{
|
||||
`pkg.Example --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
`test$main.init --> pkg.init`,
|
||||
}},
|
||||
{"pta", true, []string{
|
||||
`<root> --> pkg.test.main`,
|
||||
`<root> --> pkg.main`,
|
||||
`pkg.test.main --> testing.MainStart`,
|
||||
`testing.runExample --> pkg.Example`,
|
||||
{"pta", format, true, []string{
|
||||
`<root> --> pkg.Example`,
|
||||
`<root> --> test$main.init`,
|
||||
`pkg.Example --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
`test$main.init --> pkg.init`,
|
||||
}},
|
||||
} {
|
||||
const format = "{{.Caller}} --> {{.Callee}}"
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
||||
edges := make(map[string]bool)
|
||||
for _, line := range strings.Split(fmt.Sprint(stdout), "\n") {
|
||||
edges[line] = true
|
||||
}
|
||||
for _, edge := range test.want {
|
||||
if !edges[edge] {
|
||||
t.Errorf("callgraph(%q, %t): missing edge: %s",
|
||||
test.algo, test.tests, edge)
|
||||
}
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Log("got:\n", stdout)
|
||||
got := sortedLines(fmt.Sprint(stdout))
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("callgraph(%q, %q, %t):\ngot:\n%s\nwant:\n%s",
|
||||
test.algo, test.format, test.tests,
|
||||
strings.Join(got, "\n"),
|
||||
strings.Join(test.want, "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// An Example function must have an "Output:" comment for the go build
|
||||
// system to generate a call to it from the test main package.
|
||||
// Don't import "testing", it adds a lot of callgraph edges.
|
||||
|
||||
func Example() {
|
||||
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 {
|
||||
out, err = os.Create(outfile)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = htmlTemplate.Execute(out, d)
|
||||
if err == nil {
|
||||
err = out.Close()
|
||||
|
|
|
@ -1,83 +1,18 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
The digraph command performs queries over unlabelled directed graphs
|
||||
represented in text form. It is intended to integrate nicely with
|
||||
typical UNIX command pipelines.
|
||||
|
||||
Usage:
|
||||
|
||||
your-application | digraph [command]
|
||||
|
||||
The support commands are:
|
||||
|
||||
nodes
|
||||
the set of all nodes
|
||||
degree
|
||||
the in-degree and out-degree of each node
|
||||
preds <node> ...
|
||||
the set of immediate predecessors of the specified nodes
|
||||
succs <node> ...
|
||||
the set of immediate successors of the specified nodes
|
||||
forward <node> ...
|
||||
the set of nodes transitively reachable from the specified nodes
|
||||
reverse <node> ...
|
||||
the set of nodes that transitively reach the specified nodes
|
||||
somepath <node> <node>
|
||||
the list of nodes on some arbitrary path from the first node to the second
|
||||
allpaths <node> <node>
|
||||
the set of nodes on all paths from the first node to the second
|
||||
sccs
|
||||
all strongly connected components (one per line)
|
||||
scc <node>
|
||||
the set of nodes nodes strongly connected to the specified one
|
||||
|
||||
Input format:
|
||||
|
||||
Each line contains zero or more words. Words are separated by unquoted
|
||||
whitespace; words may contain Go-style double-quoted portions, allowing spaces
|
||||
and other characters to be expressed.
|
||||
|
||||
Each word declares a node, and if there are more than one, an edge from the
|
||||
first to each subsequent one. The graph is provided on the standard input.
|
||||
|
||||
For instance, the following (acyclic) graph specifies a partial order among the
|
||||
subtasks of getting dressed:
|
||||
|
||||
$ cat clothes.txt
|
||||
socks shoes
|
||||
"boxer shorts" pants
|
||||
pants belt shoes
|
||||
shirt tie sweater
|
||||
sweater jacket
|
||||
hat
|
||||
|
||||
The line "shirt tie sweater" indicates the two edges shirt -> tie and
|
||||
shirt -> sweater, not shirt -> tie -> sweater.
|
||||
|
||||
Example usage:
|
||||
|
||||
Using digraph with existing Go tools:
|
||||
|
||||
$ go mod graph | digraph nodes # Operate on the Go module graph.
|
||||
$ go list -m all | digraph nodes # Operate on the Go package graph.
|
||||
|
||||
Show the transitive closure of imports of the digraph tool itself:
|
||||
$ go list -f '{{.ImportPath}} {{join .Imports " "}}' ... | digraph forward golang.org/x/tools/cmd/digraph
|
||||
|
||||
Show which clothes (see above) must be donned before a jacket:
|
||||
$ digraph reverse jacket
|
||||
|
||||
*/
|
||||
package main // import "golang.org/x/tools/cmd/digraph"
|
||||
|
||||
// The digraph command performs queries over unlabelled directed graphs
|
||||
// represented in text form. It is intended to integrate nicely with
|
||||
// typical UNIX command pipelines.
|
||||
//
|
||||
// Since directed graphs (import graphs, reference graphs, call graphs,
|
||||
// etc) often arise during software tool development and debugging, this
|
||||
// command is included in the go.tools repository.
|
||||
//
|
||||
// TODO(adonovan):
|
||||
// - 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.
|
||||
// - allow queries to nest, like Blaze query language.
|
||||
//
|
||||
package main // import "golang.org/x/tools/cmd/digraph"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
@ -93,41 +28,73 @@ import (
|
|||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, `Usage: your-application | digraph [command]
|
||||
const Usage = `digraph: queries over directed graphs in text form.
|
||||
|
||||
Graph 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 field 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.
|
||||
|
||||
Supported queries:
|
||||
|
||||
The support commands are:
|
||||
nodes
|
||||
the set of all nodes
|
||||
degree
|
||||
the in-degree and out-degree of each node
|
||||
preds <node> ...
|
||||
the in-degree and out-degree of each node.
|
||||
preds <label> ...
|
||||
the set of immediate predecessors of the specified nodes
|
||||
succs <node> ...
|
||||
succs <label> ...
|
||||
the set of immediate successors of the specified nodes
|
||||
forward <node> ...
|
||||
forward <label> ...
|
||||
the set of nodes transitively reachable from the specified nodes
|
||||
reverse <node> ...
|
||||
reverse <label> ...
|
||||
the set of nodes that transitively reach the specified nodes
|
||||
somepath <node> <node>
|
||||
somepath <label> <label>
|
||||
the list of nodes on some arbitrary path from the first node to the second
|
||||
allpaths <node> <node>
|
||||
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 <node>
|
||||
scc <label>
|
||||
the set of nodes nodes strongly connected to the specified one
|
||||
`)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
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() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
usage()
|
||||
fmt.Println(Usage)
|
||||
return
|
||||
}
|
||||
|
||||
if err := digraph(args[0], args[1:]); err != nil {
|
||||
|
@ -262,47 +229,6 @@ func (g graph) sccs() []nodeset {
|
|||
return sccs
|
||||
}
|
||||
|
||||
func (g graph) allpaths(from, to string) error {
|
||||
// Mark all nodes to "to".
|
||||
seen := make(nodeset) // value of seen[x] indicates whether x is on some path to "to"
|
||||
var visit func(node string) bool
|
||||
visit = func(node string) bool {
|
||||
reachesTo, ok := seen[node]
|
||||
if !ok {
|
||||
reachesTo = node == to
|
||||
seen[node] = reachesTo
|
||||
for e := range g[node] {
|
||||
if visit(e) {
|
||||
reachesTo = true
|
||||
}
|
||||
}
|
||||
if reachesTo && node != to {
|
||||
seen[node] = true
|
||||
}
|
||||
}
|
||||
return reachesTo
|
||||
}
|
||||
visit(from)
|
||||
|
||||
// For each marked node, collect its marked successors.
|
||||
var edges []string
|
||||
for n := range seen {
|
||||
for succ := range g[n] {
|
||||
if seen[succ] {
|
||||
edges = append(edges, n+" "+succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort (so that this method is deterministic) and print edges.
|
||||
sort.Strings(edges)
|
||||
for _, e := range edges {
|
||||
fmt.Fprintln(stdout, e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse(rd io.Reader) (graph, error) {
|
||||
g := make(graph)
|
||||
|
||||
|
@ -325,7 +251,6 @@ func parse(rd io.Reader) (graph, error) {
|
|||
return g, nil
|
||||
}
|
||||
|
||||
// Overridable for testing purposes.
|
||||
var stdin io.Reader = os.Stdin
|
||||
var stdout io.Writer = os.Stdout
|
||||
|
||||
|
@ -440,7 +365,33 @@ func digraph(cmd string, args []string) error {
|
|||
if g[to] == nil {
|
||||
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":
|
||||
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
|
||||
|
||||
import (
|
||||
|
@ -29,34 +26,35 @@ d c
|
|||
`
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
input string
|
||||
cmd string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
|
||||
{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
|
||||
{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
|
||||
{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
|
||||
{"scss", g2, "sccs", nil, "a\nb\nc d\n"},
|
||||
{"scc", g2, "scc", []string{"d"}, "c\nd\n"},
|
||||
{"succs", g2, "succs", []string{"a"}, "b\nc\n"},
|
||||
{"preds", g2, "preds", []string{"c"}, "a\nd\n"},
|
||||
{"preds multiple args", g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\n"},
|
||||
{g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
|
||||
{g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
|
||||
{g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
|
||||
{g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
|
||||
|
||||
{g2, "allpaths", []string{"a", "d"}, "a\nb\nc\nd\n"},
|
||||
|
||||
{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"},
|
||||
} {
|
||||
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)
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
got := stdout.(fmt.Stringer).String()
|
||||
if got != test.want {
|
||||
t.Errorf("digraph(%s, %s) = got %q, want %q", test.cmd, test.args, got, test.want)
|
||||
t.Errorf("digraph(%s, %s) = %q, want %q", test.cmd, test.args, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(adonovan):
|
||||
|
@ -64,110 +62,6 @@ d c
|
|||
// - test errors
|
||||
}
|
||||
|
||||
func TestAllpaths(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
in string
|
||||
to string // from is always "A"
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Basic",
|
||||
in: "A B\nB C",
|
||||
to: "B",
|
||||
want: "A B\n",
|
||||
},
|
||||
{
|
||||
name: "Long",
|
||||
in: "A B\nB C\n",
|
||||
to: "C",
|
||||
want: "A B\nB C\n",
|
||||
},
|
||||
{
|
||||
name: "Cycle Basic",
|
||||
in: "A B\nB A",
|
||||
to: "B",
|
||||
want: "A B\nB A\n",
|
||||
},
|
||||
{
|
||||
name: "Cycle Path Out",
|
||||
// A <-> B -> C -> D
|
||||
in: "A B\nB A\nB C\nC D",
|
||||
to: "C",
|
||||
want: "A B\nB A\nB C\n",
|
||||
},
|
||||
{
|
||||
name: "Cycle Path Out Further Out",
|
||||
// A -> B <-> C -> D -> E
|
||||
in: "A B\nB C\nC D\nC B\nD E",
|
||||
to: "D",
|
||||
want: "A B\nB C\nC B\nC D\n",
|
||||
},
|
||||
{
|
||||
name: "Two Paths Basic",
|
||||
// /-> C --\
|
||||
// A -> B -- -> E -> F
|
||||
// \-> D --/
|
||||
in: "A B\nB C\nC E\nB D\nD E\nE F",
|
||||
to: "E",
|
||||
want: "A B\nB C\nB D\nC E\nD E\n",
|
||||
},
|
||||
{
|
||||
name: "Two Paths With One Immediately From Start",
|
||||
// /-> B -+ -> D
|
||||
// A -- |
|
||||
// \-> C <+
|
||||
in: "A B\nA C\nB C\nB D",
|
||||
to: "C",
|
||||
want: "A B\nA C\nB C\n",
|
||||
},
|
||||
{
|
||||
name: "Two Paths Further Up",
|
||||
// /-> B --\
|
||||
// A -- -> D -> E -> F
|
||||
// \-> C --/
|
||||
in: "A B\nA C\nB D\nC D\nD E\nE F",
|
||||
to: "E",
|
||||
want: "A B\nA C\nB D\nC D\nD E\n",
|
||||
},
|
||||
{
|
||||
// We should include A - C - D even though it's further up the
|
||||
// second path than D (which would already be in the graph by
|
||||
// the time we get around to integrating the second path).
|
||||
name: "Two Splits",
|
||||
// /-> B --\ /-> E --\
|
||||
// A -- -> D -- -> G -> H
|
||||
// \-> C --/ \-> F --/
|
||||
in: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\nG H",
|
||||
to: "G",
|
||||
want: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\n",
|
||||
},
|
||||
{
|
||||
// D - E should not be duplicated.
|
||||
name: "Two Paths - Two Splits With Gap",
|
||||
// /-> B --\ /-> F --\
|
||||
// A -- -> D -> E -- -> H -> I
|
||||
// \-> C --/ \-> G --/
|
||||
in: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\nH I",
|
||||
to: "H",
|
||||
want: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\n",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
stdin = strings.NewReader(test.in)
|
||||
stdout = new(bytes.Buffer)
|
||||
if err := digraph("allpaths", []string{"A", test.to}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := stdout.(fmt.Stringer).String()
|
||||
if got != test.want {
|
||||
t.Errorf("digraph(allpaths, A, %s) = got %q, want %q", test.to, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
line string
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -139,7 +139,7 @@ func doMain() error {
|
|||
hadErrors = true
|
||||
}
|
||||
} 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
|
||||
}
|
||||
|
||||
// cwd contains the current working directory of the tool.
|
||||
//
|
||||
// It is initialized directly so that its value will be set for any other
|
||||
// package variables or init functions that depend on it, such as the gopath
|
||||
// variable in main_test.go.
|
||||
var cwd string = func() string {
|
||||
cwd, err := os.Getwd()
|
||||
var cwd string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
cwd, err = os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("os.Getwd: %v", err)
|
||||
}
|
||||
return cwd
|
||||
}()
|
||||
}
|
||||
|
||||
// shortPath returns an absolute or relative name for path, whatever is shorter.
|
||||
// Plundered from $GOROOT/src/cmd/go/build.go.
|
||||
|
|
|
@ -10,7 +10,6 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
@ -33,25 +32,11 @@ import (
|
|||
// titanic.biz/bar -- domain is sinking; package has jumped ship to new.com/bar
|
||||
// titanic.biz/foo -- domain is sinking but package has no import comment yet
|
||||
|
||||
var gopath = filepath.Join(cwd, "testdata")
|
||||
|
||||
func init() {
|
||||
if err := os.Setenv("GOPATH", gopath); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// This test currently requires GOPATH mode.
|
||||
// Explicitly disabling module mode should suffix, but
|
||||
// we'll also turn off GOPROXY just for good measure.
|
||||
if err := os.Setenv("GO111MODULE", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.Setenv("GOPROXY", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixImports(t *testing.T) {
|
||||
gopath := filepath.Join(cwd, "testdata")
|
||||
if err := os.Setenv("GOPATH", gopath); err != nil {
|
||||
t.Fatalf("os.Setenv: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
stderr = os.Stderr
|
||||
*badDomains = "code.google.com"
|
||||
|
@ -215,10 +200,7 @@ import (
|
|||
}
|
||||
|
||||
// Compare stderr output.
|
||||
if got := stderr.(*bytes.Buffer).String(); got != test.wantStderr {
|
||||
if strings.Contains(got, "vendor/golang_org/x/text/unicode/norm") {
|
||||
t.Skip("skipping known-broken test; see golang.org/issue/17417")
|
||||
}
|
||||
if stderr.(*bytes.Buffer).String() != test.wantStderr {
|
||||
t.Errorf("#%d. stderr: got <<%s>>, want <<%s>>",
|
||||
i, stderr, test.wantStderr)
|
||||
}
|
||||
|
@ -239,6 +221,11 @@ import (
|
|||
|
||||
// TestDryRun tests that the -n flag suppresses calls to writeFile.
|
||||
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
|
||||
defer func() { *dryrun = false }() // restore
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// This file implements access to gc-generated export data.
|
||||
|
||||
package main
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// This file implements access to gccgo-generated export data.
|
||||
|
||||
package main
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package main
|
||||
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
exact "go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
|
@ -141,13 +143,7 @@ func (p *printer) printPackage(pkg *types.Package, filter func(types.Object) boo
|
|||
p.printDecl("type", len(typez), func() {
|
||||
for _, obj := range typez {
|
||||
p.printf("%s ", obj.Name())
|
||||
typ := obj.Type()
|
||||
if isAlias(obj) {
|
||||
p.print("= ")
|
||||
p.writeType(p.pkg, typ)
|
||||
} else {
|
||||
p.writeType(p.pkg, typ.Underlying())
|
||||
}
|
||||
p.writeType(p.pkg, obj.Type().Underlying())
|
||||
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.
|
||||
// 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
|
||||
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 {
|
||||
b[i], b[j] = b[j], b[i]
|
||||
}
|
||||
|
@ -229,14 +225,14 @@ var (
|
|||
|
||||
// floatString returns the string representation for a
|
||||
// numeric value v in normalized floating-point format.
|
||||
func floatString(v constant.Value) string {
|
||||
if constant.Sign(v) == 0 {
|
||||
func floatString(v exact.Value) string {
|
||||
if exact.Sign(v) == 0 {
|
||||
return "0.0"
|
||||
}
|
||||
// x != 0
|
||||
|
||||
// convert |v| into a big.Rat x
|
||||
x := new(big.Rat).SetFrac(absInt(constant.Num(v)), absInt(constant.Denom(v)))
|
||||
x := new(big.Rat).SetFrac(absInt(exact.Num(v)), absInt(exact.Denom(v)))
|
||||
|
||||
// normalize x and determine exponent e
|
||||
// (This is not very efficient, but also not speed-critical.)
|
||||
|
@ -272,7 +268,7 @@ func floatString(v constant.Value) string {
|
|||
if e != 0 {
|
||||
s += fmt.Sprintf("e%+d", e)
|
||||
}
|
||||
if constant.Sign(v) < 0 {
|
||||
if exact.Sign(v) < 0 {
|
||||
s = "-" + s
|
||||
}
|
||||
|
||||
|
@ -286,29 +282,29 @@ func floatString(v constant.Value) string {
|
|||
// valString returns the string representation for the value v.
|
||||
// Setting floatFmt forces an integer value to be formatted in
|
||||
// normalized floating-point format.
|
||||
// TODO(gri) Move this code into package constant.
|
||||
func valString(v constant.Value, floatFmt bool) string {
|
||||
// TODO(gri) Move this code into package exact.
|
||||
func valString(v exact.Value, floatFmt bool) string {
|
||||
switch v.Kind() {
|
||||
case constant.Int:
|
||||
case exact.Int:
|
||||
if floatFmt {
|
||||
return floatString(v)
|
||||
}
|
||||
case constant.Float:
|
||||
case exact.Float:
|
||||
return floatString(v)
|
||||
case constant.Complex:
|
||||
re := constant.Real(v)
|
||||
im := constant.Imag(v)
|
||||
case exact.Complex:
|
||||
re := exact.Real(v)
|
||||
im := exact.Imag(v)
|
||||
var s string
|
||||
if constant.Sign(re) != 0 {
|
||||
if exact.Sign(re) != 0 {
|
||||
s = floatString(re)
|
||||
if constant.Sign(im) >= 0 {
|
||||
if exact.Sign(im) >= 0 {
|
||||
s += " + "
|
||||
} else {
|
||||
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 v.String()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// This file implements access to export data from source.
|
||||
|
||||
package main
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// This file implements writing of types. The functionality is lifted
|
||||
// directly from go/types, but now contains various modifications for
|
||||
// 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 (
|
||||
blogRepo = "golang.org/x/blog"
|
||||
blogURL = "https://blog.golang.org/"
|
||||
blogURL = "http://blog.golang.org/"
|
||||
blogPath = "/blog/"
|
||||
)
|
||||
|
||||
|
@ -34,19 +34,16 @@ var (
|
|||
func init() {
|
||||
// Initialize blog only when first accessed.
|
||||
http.HandleFunc(blogPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
blogInitOnce.Do(func() {
|
||||
blogInit(r.Host)
|
||||
})
|
||||
blogInitOnce.Do(blogInit)
|
||||
blogServer.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func blogInit(host string) {
|
||||
// Binary distributions included the blog content in "/blog".
|
||||
// We stopped including this in Go 1.11.
|
||||
func blogInit() {
|
||||
// Binary distributions will include the blog content in "/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 {
|
||||
root = pkg.Dir
|
||||
}
|
||||
|
@ -66,7 +63,6 @@ func blogInit(host string) {
|
|||
TemplatePath: filepath.Join(root, "template"),
|
||||
HomeArticles: 5,
|
||||
PlayEnabled: playEnabled,
|
||||
ServeLocalLinks: strings.HasPrefix(host, "localhost"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// 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 "net/http"
|
||||
|
|
|
@ -6,19 +6,48 @@
|
|||
|
||||
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.
|
||||
|
||||
godoc -http=:6060
|
||||
|
||||
Usage:
|
||||
|
||||
godoc [flag]
|
||||
godoc [flag] package [name ...]
|
||||
|
||||
The flags are:
|
||||
|
||||
-v
|
||||
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
|
||||
show timestamps with directory listings
|
||||
-index
|
||||
|
@ -32,12 +61,7 @@ The flags are:
|
|||
to the indexer (the indexer will never finish), a value of 1.0
|
||||
means that index creation is running at full throttle (other
|
||||
goroutines may get no time while the index is built)
|
||||
-index_interval=0
|
||||
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
|
||||
-links=true:
|
||||
link identifiers to their declarations
|
||||
-write_index=false
|
||||
write index to a file; the file name must be specified with
|
||||
|
@ -48,17 +72,21 @@ The flags are:
|
|||
-notes="BUG"
|
||||
regular expression matching note markers to show
|
||||
(e.g., "BUG|TODO", ".*")
|
||||
-html
|
||||
print HTML in command-line mode
|
||||
-goroot=$GOROOT
|
||||
Go root directory
|
||||
-http=addr
|
||||
HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
|
||||
-server=addr
|
||||
webserver address for command line searches
|
||||
-analysis=type,pointer
|
||||
comma-separated list of analyses to perform
|
||||
"type": display identifier resolution, type info, method sets,
|
||||
'implements', and static callees
|
||||
"pointer": display channel peers, callers and dynamic callees
|
||||
"pointer" display channel peers, callers and dynamic callees
|
||||
(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=""
|
||||
directory containing alternate template files; if set,
|
||||
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
|
||||
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 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
|
||||
shown, and only an identifier index but no full text search index is created.
|
||||
|
||||
By default, godoc uses the system's GOOS/GOARCH. You can provide the URL parameters
|
||||
"GOOS" and "GOARCH" to set the output on the web page for the target system.
|
||||
By default, godoc uses the system's GOOS/GOARCH; in command-line mode you can
|
||||
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
|
||||
"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
|
||||
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
|
||||
|
||||
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation
|
||||
for all (not just the exported) declarations of package big.
|
||||
For instance, http://golang.org/pkg/math/big/?m=all,text shows the documentation
|
||||
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.
|
||||
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.
|
||||
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:
|
||||
|
||||
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
|
||||
|
||||
Godoc documentation is converted to HTML or to text using the go/doc package;
|
||||
see https://golang.org/pkg/go/doc/#ToHTML for the exact rules.
|
||||
see http://golang.org/pkg/go/doc/#ToHTML for the exact rules.
|
||||
Godoc also shows example code that is runnable by the testing package;
|
||||
see https://golang.org/pkg/testing/#hdr-Examples for the conventions.
|
||||
see http://golang.org/pkg/testing/#hdr-Examples for the conventions.
|
||||
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"
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
|
@ -23,6 +22,50 @@ import (
|
|||
"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.
|
||||
// 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" {
|
||||
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-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -58,6 +97,33 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
|
|||
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 {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
|
@ -74,32 +140,19 @@ func waitForServerReady(t *testing.T, addr string) {
|
|||
waitForServer(t,
|
||||
fmt.Sprintf("http://%v/", addr),
|
||||
"The Go Programming Language",
|
||||
15*time.Second,
|
||||
false)
|
||||
5*time.Second)
|
||||
}
|
||||
|
||||
func waitForSearchReady(t *testing.T, addr string) {
|
||||
waitForServer(t,
|
||||
fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr),
|
||||
"The list of tokens.",
|
||||
2*time.Minute,
|
||||
false)
|
||||
}
|
||||
|
||||
func waitUntilScanComplete(t *testing.T, addr string) {
|
||||
waitForServer(t,
|
||||
fmt.Sprintf("http://%v/pkg", addr),
|
||||
"Scan is not yet complete",
|
||||
2*time.Minute,
|
||||
true,
|
||||
)
|
||||
// setting reverse as true, which means this waits
|
||||
// until the string is not returned in the response anymore
|
||||
2*time.Minute)
|
||||
}
|
||||
|
||||
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
|
||||
deadline := time.Now().Add(timeout)
|
||||
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)
|
||||
res.Body.Close()
|
||||
if err == nil && res.StatusCode == http.StatusOK {
|
||||
if bytes.Contains(rbody, []byte(match)) && !reverse {
|
||||
if err == nil && res.StatusCode == http.StatusOK &&
|
||||
bytes.Contains(rbody, []byte(match)) {
|
||||
return
|
||||
}
|
||||
if !bytes.Contains(rbody, []byte(match)) && reverse {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Fatalf("Server failed to respond in %v", timeout)
|
||||
}
|
||||
|
||||
// hasTag checks whether a given release tag is contained in the current version
|
||||
// of the go binary.
|
||||
func hasTag(t string) bool {
|
||||
for _, v := range build.Default.ReleaseTags {
|
||||
if t == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func killAndWait(cmd *exec.Cmd) {
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
}
|
||||
|
||||
func TestURL(t *testing.T) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
t.Skip("skipping on plan9; fails to start up quickly enough")
|
||||
}
|
||||
bin, cleanup := buildGodoc(t)
|
||||
defer cleanup()
|
||||
|
||||
testcase := func(url string, contents string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
|
||||
|
||||
args := []string{fmt.Sprintf("-url=%s", url)}
|
||||
cmd := exec.Command(bin, args...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
cmd.Args[0] = "godoc"
|
||||
|
||||
// Set GOPATH variable to non-existing path
|
||||
// and GOPROXY=off to disable module fetches.
|
||||
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
|
||||
// (We don't want the indexer looking at the local workspace during tests.)
|
||||
cmd.Env = append(os.Environ(),
|
||||
"GOPATH=does_not_exist",
|
||||
"GOPROXY=off",
|
||||
"GO111MODULE=off")
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to run godoc -url=%q: %s\nstderr:\n%s", url, err, stderr)
|
||||
}
|
||||
|
||||
if !strings.Contains(stdout.String(), contents) {
|
||||
t.Errorf("did not find substring %q in output of godoc -url=%q:\n%s", contents, url, stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("index", testcase("/", "Go is an open source programming language"))
|
||||
t.Run("fmt", testcase("/pkg/fmt", "Package fmt implements formatted I/O"))
|
||||
}
|
||||
|
||||
// Basic integration test for godoc HTTP interface.
|
||||
func TestWeb(t *testing.T) {
|
||||
testWeb(t, false)
|
||||
|
@ -193,9 +191,6 @@ func TestWebIndex(t *testing.T) {
|
|||
|
||||
// Basic integration test for godoc HTTP interface.
|
||||
func testWeb(t *testing.T, withIndex bool) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
t.Skip("skipping on plan9; fails to start up quickly enough")
|
||||
}
|
||||
bin, cleanup := buildGodoc(t)
|
||||
defer cleanup()
|
||||
addr := serverAddress(t)
|
||||
|
@ -207,16 +202,7 @@ func testWeb(t *testing.T, withIndex bool) {
|
|||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Args[0] = "godoc"
|
||||
|
||||
// Set GOPATH variable to non-existing path
|
||||
// and GOPROXY=off to disable module fetches.
|
||||
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
|
||||
// (We don't want the indexer looking at the local workspace during tests.)
|
||||
cmd.Env = append(os.Environ(),
|
||||
"GOPATH=does_not_exist",
|
||||
"GOPROXY=off",
|
||||
"GO111MODULE=off")
|
||||
|
||||
cmd.Env = godocEnv()
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("failed to start godoc: %s", err)
|
||||
}
|
||||
|
@ -226,112 +212,73 @@ func testWeb(t *testing.T, withIndex bool) {
|
|||
waitForSearchReady(t, addr)
|
||||
} else {
|
||||
waitForServerReady(t, addr)
|
||||
waitUntilScanComplete(t, addr)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
contains []string // substring
|
||||
match []string // regexp
|
||||
notContains []string
|
||||
match []string
|
||||
dontmatch []string
|
||||
needIndex bool
|
||||
releaseTag string // optional release tag that must be in go/build.ReleaseTags
|
||||
}{
|
||||
{
|
||||
path: "/",
|
||||
contains: []string{"Go is an open source programming language"},
|
||||
match: []string{"Go is an open source programming language"},
|
||||
},
|
||||
{
|
||||
path: "/pkg/fmt/",
|
||||
contains: []string{"Package fmt implements formatted I/O"},
|
||||
match: []string{"Package fmt implements formatted I/O"},
|
||||
},
|
||||
{
|
||||
path: "/src/fmt/",
|
||||
contains: []string{"scan_test.go"},
|
||||
match: []string{"scan_test.go"},
|
||||
},
|
||||
{
|
||||
path: "/src/fmt/print.go",
|
||||
contains: []string{"// Println formats using"},
|
||||
match: []string{"// Println formats using"},
|
||||
},
|
||||
{
|
||||
path: "/pkg",
|
||||
contains: []string{
|
||||
match: []string{
|
||||
"Standard library",
|
||||
"Package fmt implements formatted I/O",
|
||||
},
|
||||
notContains: []string{
|
||||
dontmatch: []string{
|
||||
"internal/syscall",
|
||||
"cmd/gc",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/?m=all",
|
||||
contains: []string{
|
||||
match: []string{
|
||||
"Standard library",
|
||||
"Package fmt implements formatted I/O",
|
||||
"internal/syscall/?m=all",
|
||||
"internal/syscall",
|
||||
},
|
||||
notContains: []string{
|
||||
dontmatch: []string{
|
||||
"cmd/gc",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/search?q=ListenAndServe",
|
||||
contains: []string{
|
||||
path: "/search?q=notwithstanding",
|
||||
match: []string{
|
||||
"/src",
|
||||
},
|
||||
notContains: []string{
|
||||
dontmatch: []string{
|
||||
"/pkg/bootstrap",
|
||||
},
|
||||
needIndex: true,
|
||||
},
|
||||
{
|
||||
path: "/pkg/strings/",
|
||||
contains: []string{
|
||||
match: []string{
|
||||
`href="/src/strings/strings.go"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/cmd/compile/internal/amd64/",
|
||||
contains: []string{
|
||||
`href="/src/cmd/compile/internal/amd64/ssa.go"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/math/bits/",
|
||||
contains: []string{
|
||||
`Added in Go 1.9`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/net/",
|
||||
contains: []string{
|
||||
`// IPv6 scoped addressing zone; added in Go 1.1`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/net/http/httptrace/",
|
||||
match: []string{
|
||||
`Got1xxResponse.*// Go 1\.11`,
|
||||
`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 {
|
||||
|
@ -345,34 +292,18 @@ func testWeb(t *testing.T, withIndex bool) {
|
|||
continue
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
strBody := string(body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
|
||||
}
|
||||
isErr := false
|
||||
for _, substr := range test.contains {
|
||||
if test.releaseTag != "" && !hasTag(test.releaseTag) {
|
||||
continue
|
||||
}
|
||||
for _, substr := range test.match {
|
||||
if !bytes.Contains(body, []byte(substr)) {
|
||||
t.Errorf("GET %s: wanted substring %q in body", url, substr)
|
||||
isErr = true
|
||||
}
|
||||
}
|
||||
for _, re := range test.match {
|
||||
if test.releaseTag != "" && !hasTag(test.releaseTag) {
|
||||
continue
|
||||
}
|
||||
if ok, err := regexp.MatchString(re, strBody); !ok || err != nil {
|
||||
if err != nil {
|
||||
t.Fatalf("Bad regexp %q: %v", re, err)
|
||||
}
|
||||
t.Errorf("GET %s: wanted to match %s in body", url, re)
|
||||
isErr = true
|
||||
}
|
||||
}
|
||||
for _, substr := range test.notContains {
|
||||
for _, substr := range test.dontmatch {
|
||||
if bytes.Contains(body, []byte(substr)) {
|
||||
t.Errorf("GET %s: didn't want substring %q in body", url, substr)
|
||||
isErr = true
|
||||
|
@ -424,11 +355,14 @@ func main() { print(lib.V) }
|
|||
defer cleanup()
|
||||
addr := serverAddress(t)
|
||||
cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-analysis=type")
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", filepath.Join(tmpdir, "goroot")))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath")))
|
||||
cmd.Env = append(cmd.Env, "GO111MODULE=off")
|
||||
cmd.Env = append(cmd.Env, "GOPROXY=off")
|
||||
for _, e := range os.Environ() {
|
||||
if strings.HasPrefix(e, "GOROOT=") || strings.HasPrefix(e, "GOPATH=") {
|
||||
continue
|
||||
}
|
||||
cmd.Env = append(cmd.Env, e)
|
||||
}
|
||||
cmd.Stdout = os.Stderr
|
||||
stderr, err := cmd.StderrPipe()
|
||||
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"
|
||||
|
||||
"golang.org/x/tools/godoc"
|
||||
"golang.org/x/tools/godoc/golangorgenv"
|
||||
"golang.org/x/tools/godoc/redirect"
|
||||
"golang.org/x/tools/godoc/vfs"
|
||||
)
|
||||
|
@ -31,20 +30,21 @@ var (
|
|||
fs = vfs.NameSpace{}
|
||||
)
|
||||
|
||||
var enforceHosts = false // set true in production on app engine
|
||||
|
||||
// hostEnforcerHandler redirects requests to "http://foo.golang.org/bar"
|
||||
// to "https://golang.org/bar".
|
||||
// It permits requests to the host "godoc-test.golang.org" for testing and
|
||||
// golang.google.cn for Chinese users.
|
||||
// It permits requests to the host "godoc-test.golang.org" for testing.
|
||||
type hostEnforcerHandler struct {
|
||||
h http.Handler
|
||||
}
|
||||
|
||||
func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !golangorgenv.EnforceHosts() {
|
||||
if !enforceHosts {
|
||||
h.h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if !h.isHTTPS(r) || !h.validHost(r.Host) {
|
||||
if r.TLS == nil || !h.validHost(r.Host) {
|
||||
r.URL.Scheme = "https"
|
||||
if h.validHost(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)
|
||||
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)
|
||||
}
|
||||
|
||||
func (h hostEnforcerHandler) isHTTPS(r *http.Request) bool {
|
||||
return r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
|
||||
}
|
||||
|
||||
func (h hostEnforcerHandler) validHost(host string) bool {
|
||||
switch strings.ToLower(host) {
|
||||
case "golang.org", "golang.google.cn":
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(host, "-dot-golang-org.appspot.com") {
|
||||
// staging/test
|
||||
case "golang.org", "godoc-test.golang.org":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -112,7 +104,11 @@ func readTemplate(name string) *template.Template {
|
|||
return t
|
||||
}
|
||||
|
||||
func readTemplates(p *godoc.Presentation) {
|
||||
func readTemplates(p *godoc.Presentation, html bool) {
|
||||
p.PackageText = readTemplate("package.txt")
|
||||
p.SearchText = readTemplate("search.txt")
|
||||
|
||||
if html || p.HTMLMode {
|
||||
codewalkHTML = readTemplate("codewalk.html")
|
||||
codewalkdirHTML = readTemplate("codewalkdir.html")
|
||||
p.CallGraphHTML = readTemplate("callgraph.html")
|
||||
|
@ -123,13 +119,13 @@ func readTemplates(p *godoc.Presentation) {
|
|||
p.ImplementsHTML = readTemplate("implements.html")
|
||||
p.MethodSetHTML = readTemplate("methodset.html")
|
||||
p.PackageHTML = readTemplate("package.html")
|
||||
p.PackageRootHTML = readTemplate("packageroot.html")
|
||||
p.SearchHTML = readTemplate("search.html")
|
||||
p.SearchDocHTML = readTemplate("searchdoc.html")
|
||||
p.SearchCodeHTML = readTemplate("searchcode.html")
|
||||
p.SearchTxtHTML = readTemplate("searchtxt.html")
|
||||
p.SearchDescXML = readTemplate("opensearch.xml")
|
||||
}
|
||||
}
|
||||
|
||||
type fmtResponse struct {
|
||||
Body string
|
||||
|
|
|
@ -14,18 +14,28 @@
|
|||
// (idea is if you say import "compress/zlib", you go to
|
||||
// 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
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
_ "expvar" // to serve /debug/vars
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
_ "net/http/pprof" // to serve /debug/pprof/*
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -43,7 +53,10 @@ import (
|
|||
"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 (
|
||||
// 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`)
|
||||
|
||||
// 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
|
||||
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")
|
||||
|
||||
// command-line searches
|
||||
query = flag.Bool("q", false, "arguments are considered search queries")
|
||||
|
||||
verbose = flag.Bool("v", false, "verbose mode")
|
||||
|
||||
// file system roots
|
||||
// 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
|
||||
tabWidth = flag.Int("tabwidth", 4, "tab width")
|
||||
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
|
||||
templateDir = flag.String("templates", "", "load templates/JS/CSS from disk in this directory")
|
||||
showPlayground = flag.Bool("play", false, "enable playground")
|
||||
templateDir = flag.String("templates", "", "directory containing alternate template files")
|
||||
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")
|
||||
|
||||
// search index
|
||||
|
@ -84,19 +105,10 @@ var (
|
|||
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() {
|
||||
fmt.Fprintf(os.Stderr, "usage: godoc -http="+defaultAddr+"\n")
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"usage: godoc package [name ...]\n"+
|
||||
" godoc -http="+defaultAddr+"\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
@ -123,58 +135,40 @@ func handleURLFlag() {
|
|||
|
||||
// Invoke default HTTP handler to serve request
|
||||
// to our buffering httpWriter.
|
||||
w := &httpResponseRecorder{code: 200, header: make(http.Header), body: new(bytes.Buffer)}
|
||||
w := httptest.NewRecorder()
|
||||
http.DefaultServeMux.ServeHTTP(w, req)
|
||||
|
||||
// Return data, error, or follow redirect.
|
||||
switch w.code {
|
||||
switch w.Code {
|
||||
case 200: // ok
|
||||
os.Stdout.Write(w.body.Bytes())
|
||||
os.Stdout.Write(w.Body.Bytes())
|
||||
return
|
||||
case 301, 302, 303, 307: // redirect
|
||||
redirect := w.header.Get("Location")
|
||||
redirect := w.HeaderMap.Get("Location")
|
||||
if redirect == "" {
|
||||
log.Fatalf("HTTP %d without Location header", w.code)
|
||||
log.Fatalf("HTTP %d without Location header", w.Code)
|
||||
}
|
||||
urlstr = redirect
|
||||
default:
|
||||
log.Fatalf("HTTP error %d", w.code)
|
||||
log.Fatalf("HTTP error %d", w.Code)
|
||||
}
|
||||
}
|
||||
log.Fatalf("too many redirects")
|
||||
}
|
||||
|
||||
func initCorpus(corpus *godoc.Corpus) {
|
||||
err := corpus.Init()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if certInit != nil {
|
||||
certInit()
|
||||
}
|
||||
|
||||
playEnabled = *showPlayground
|
||||
|
||||
// Check usage.
|
||||
if flag.NArg() > 0 {
|
||||
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.")
|
||||
// Check usage: either server and no args, command line and args, or index creation mode
|
||||
if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex {
|
||||
usage()
|
||||
}
|
||||
|
||||
// Set the resolved goroot.
|
||||
vfs.GOROOT = *goroot
|
||||
|
||||
fsGate := make(chan bool, 20)
|
||||
var fsGate chan bool
|
||||
fsGate = make(chan bool, 20)
|
||||
|
||||
// Determine file system to use.
|
||||
if *zipfile == "" {
|
||||
|
@ -201,6 +195,8 @@ func main() {
|
|||
fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
|
||||
}
|
||||
|
||||
httpMode := *httpAddr != ""
|
||||
|
||||
var typeAnalysis, pointerAnalysis bool
|
||||
if *analysisFlag != "" {
|
||||
for _, a := range strings.Split(*analysisFlag, ",") {
|
||||
|
@ -218,7 +214,7 @@ func main() {
|
|||
corpus := godoc.NewCorpus(fs)
|
||||
corpus.Verbose = *verbose
|
||||
corpus.MaxResults = *maxResults
|
||||
corpus.IndexEnabled = *indexEnabled
|
||||
corpus.IndexEnabled = *indexEnabled && httpMode
|
||||
if *maxResults == 0 {
|
||||
corpus.IndexFullText = false
|
||||
}
|
||||
|
@ -226,27 +222,29 @@ func main() {
|
|||
corpus.IndexDirectory = indexDirectoryDefault
|
||||
corpus.IndexThrottle = *indexThrottle
|
||||
corpus.IndexInterval = *indexInterval
|
||||
if *writeIndex || *urlFlag != "" {
|
||||
if *writeIndex {
|
||||
corpus.IndexThrottle = 1.0
|
||||
corpus.IndexEnabled = true
|
||||
initCorpus(corpus)
|
||||
} else {
|
||||
go initCorpus(corpus)
|
||||
}
|
||||
if *writeIndex || httpMode || *urlFlag != "" {
|
||||
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.TabWidth = *tabWidth
|
||||
pres.ShowTimestamps = *showTimestamps
|
||||
pres.ShowPlayground = *showPlayground
|
||||
pres.ShowExamples = *showExamples
|
||||
pres.DeclLinks = *declLinks
|
||||
pres.SrcMode = *srcMode
|
||||
pres.HTMLMode = *html
|
||||
if *notesRx != "" {
|
||||
pres.NotesRx = regexp.MustCompile(*notesRx)
|
||||
}
|
||||
|
||||
readTemplates(pres)
|
||||
readTemplates(pres, httpMode || *urlFlag != "")
|
||||
registerHandlers(pres)
|
||||
|
||||
if *writeIndex {
|
||||
|
@ -281,12 +279,15 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
if httpMode {
|
||||
// HTTP server mode.
|
||||
var handler http.Handler = http.DefaultServeMux
|
||||
if *verbose {
|
||||
log.Printf("Go Documentation Server")
|
||||
log.Printf("version = %s", runtime.Version())
|
||||
log.Printf("address = %s", *httpAddr)
|
||||
log.Printf("goroot = %s", *goroot)
|
||||
log.Printf("tabwidth = %d", *tabWidth)
|
||||
switch {
|
||||
case !*indexEnabled:
|
||||
log.Print("search index disabled")
|
||||
|
@ -309,30 +310,20 @@ func main() {
|
|||
go analysis.Run(pointerAnalysis, &corpus.Analysis)
|
||||
}
|
||||
|
||||
if runHTTPS != nil {
|
||||
go func() {
|
||||
if err := runHTTPS(handler); err != nil {
|
||||
log.Fatalf("ListenAndServe TLS: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Start http server.
|
||||
if *verbose {
|
||||
log.Println("starting HTTP server")
|
||||
}
|
||||
if wrapHTTPMux != nil {
|
||||
handler = wrapHTTPMux(handler)
|
||||
}
|
||||
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
|
||||
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
if *query {
|
||||
handleRemoteSearch()
|
||||
return
|
||||
}
|
||||
|
||||
if err := godoc.CommandLine(os.Stdout, fs, pres, flag.Args()); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// 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 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
|
||||
|
||||
In addition to fixing imports, goimports also formats
|
||||
your code in the same style as gofmt so it can be used
|
||||
as a replacement for your editor's gofmt-on-save hook.
|
||||
It's a drop-in replacement for your editor's gofmt-on-save hook.
|
||||
It has the same command-line interface as gofmt and formats
|
||||
your code in the same way.
|
||||
|
||||
For emacs, make sure you have the latest go-mode.el:
|
||||
https://github.com/dominikh/go-mode.el
|
||||
Then in your .emacs file:
|
||||
(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)
|
||||
|
||||
For vim, set "gofmt_command" to "goimports":
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/scanner"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -20,9 +19,10 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/imports"
|
||||
"golang.org/x/tools/imports"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -31,31 +31,25 @@ var (
|
|||
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
|
||||
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.")
|
||||
|
||||
verbose bool // verbose logging
|
||||
verbose = flag.Bool("v", false, "verbose logging")
|
||||
|
||||
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
|
||||
memProfile = flag.String("memprofile", "", "memory profile output")
|
||||
memProfileRate = flag.Int("memrate", 0, "if > 0, sets runtime.MemProfileRate")
|
||||
traceProfile = flag.String("trace", "", "trace profile output")
|
||||
|
||||
options = &imports.Options{
|
||||
TabWidth: 8,
|
||||
TabIndent: true,
|
||||
Comments: 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
|
||||
)
|
||||
|
||||
func init() {
|
||||
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.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.")
|
||||
flag.StringVar(&imports.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages")
|
||||
}
|
||||
|
||||
func report(err error) {
|
||||
|
@ -152,24 +146,17 @@ func processFile(filename string, in io.Reader, out io.Writer, argType argumentT
|
|||
fmt.Fprintln(out, filename)
|
||||
}
|
||||
if *write {
|
||||
if argType == fromStdin {
|
||||
// filename is "<standard input>"
|
||||
return errors.New("can't use -w on stdin")
|
||||
}
|
||||
err = ioutil.WriteFile(filename, res, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if *doDiff {
|
||||
if argType == fromStdin {
|
||||
filename = "stdin.go" // because <standard input>.orig looks silly
|
||||
}
|
||||
data, err := diff(src, res, filename)
|
||||
data, err := diff(src, res)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -208,8 +195,6 @@ func main() {
|
|||
// 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.
|
||||
var parseFlags = func() []string {
|
||||
flag.BoolVar(&verbose, "v", false, "verbose logging")
|
||||
|
||||
flag.Parse()
|
||||
return flag.Args()
|
||||
}
|
||||
|
@ -240,10 +225,12 @@ func gofmtMain() {
|
|||
defer flush()
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
// doTrace is a conditionally compiled wrapper around runtime/trace. It is
|
||||
// used to allow goimports to compile under gccgo, which does not support
|
||||
// runtime/trace. See https://golang.org/issue/15544.
|
||||
defer doTrace()()
|
||||
if *traceProfile != "" {
|
||||
bw, flush := bufferedFileWriter(*traceProfile)
|
||||
trace.Start(bw)
|
||||
defer flush()
|
||||
defer trace.Stop()
|
||||
}
|
||||
if *memProfileRate > 0 {
|
||||
runtime.MemProfileRate = *memProfileRate
|
||||
bw, flush := bufferedFileWriter(*memProfile)
|
||||
|
@ -256,9 +243,9 @@ func gofmtMain() {
|
|||
}()
|
||||
}
|
||||
|
||||
if verbose {
|
||||
if *verbose {
|
||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
||||
options.Env.Debug = true
|
||||
imports.Debug = true
|
||||
}
|
||||
if options.TabWidth < 0 {
|
||||
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) {
|
||||
file, err := ioutil.TempFile(dir, prefix)
|
||||
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)
|
||||
func diff(b1, b2 []byte) (data []byte, err error) {
|
||||
f1, err := ioutil.TempFile("", "gofmt")
|
||||
if err != nil {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f2)
|
||||
defer os.Remove(f2.Name())
|
||||
defer f2.Close()
|
||||
|
||||
cmd := "diff"
|
||||
if runtime.GOOS == "plan9" {
|
||||
cmd = "/bin/ape/diff"
|
||||
}
|
||||
f1.Write(b1)
|
||||
f2.Write(b2)
|
||||
|
||||
data, err = exec.Command(cmd, "-u", f1, f2).CombinedOutput()
|
||||
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
||||
if len(data) > 0 {
|
||||
// diff exits with a non-zero status when the files don't match.
|
||||
// Ignore that failure as long as we get output.
|
||||
return replaceTempFilename(data, filename)
|
||||
err = nil
|
||||
}
|
||||
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.
|
||||
func isFile(name string) bool {
|
||||
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 (
|
||||
fromFlag = flag.String("from", "", "Import path of package to be moved")
|
||||
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")
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// gotype.go is a copy of the original source maintained
|
||||
// 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.
|
||||
// +build go1.5
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
import (
|
||||
|
@ -98,19 +19,18 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// main operation modes
|
||||
testFiles = flag.Bool("t", false, "include in-package test files in 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")
|
||||
allFiles = flag.Bool("a", false, "use all (incl. _test.go) files when processing a directory")
|
||||
allErrors = flag.Bool("e", false, "report all errors (not just the first 10)")
|
||||
verbose = flag.Bool("v", false, "verbose mode")
|
||||
compiler = flag.String("c", defaultCompiler, "compiler used for installed packages (gc, gccgo, or source)")
|
||||
gccgo = flag.Bool("gccgo", false, "use gccgoimporter instead of gcimporter")
|
||||
|
||||
// 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)")
|
||||
printTrace = flag.Bool("trace", false, "print parse trace (forces -seq)")
|
||||
parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
|
||||
|
@ -119,55 +39,36 @@ var (
|
|||
var (
|
||||
fset = token.NewFileSet()
|
||||
errorCount = 0
|
||||
sequential = false
|
||||
parserMode parser.Mode
|
||||
sizes types.Sizes
|
||||
)
|
||||
|
||||
func initParserMode() {
|
||||
if *allErrors {
|
||||
parserMode |= parser.AllErrors
|
||||
}
|
||||
if *printAST {
|
||||
sequential = true
|
||||
}
|
||||
if *printTrace {
|
||||
parserMode |= parser.Trace
|
||||
sequential = true
|
||||
}
|
||||
if *parseComments && (*printAST || *printTrace) {
|
||||
parserMode |= parser.ParseComments
|
||||
}
|
||||
}
|
||||
|
||||
const usageString = `usage: gotype [flags] [path ...]
|
||||
|
||||
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.
|
||||
`
|
||||
func initSizes() {
|
||||
wordSize := 8
|
||||
maxAlign := 8
|
||||
switch build.Default.GOARCH {
|
||||
case "386", "arm":
|
||||
wordSize = 4
|
||||
maxAlign = 4
|
||||
// add more cases as needed
|
||||
}
|
||||
sizes = &types.StdSizes{WordSize: int64(wordSize), MaxAlign: int64(maxAlign)}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, usageString)
|
||||
fmt.Fprintln(os.Stderr, "usage: gotype [flags] [path ...]")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
@ -201,49 +102,60 @@ func parseStdin() (*ast.File, error) {
|
|||
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))
|
||||
errors := make([]error, len(filenames))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
if *sequential {
|
||||
for i, filename := range filenames {
|
||||
wg.Add(1)
|
||||
go func(i int, filepath string) {
|
||||
defer wg.Done()
|
||||
files[i], errors[i] = parse(filepath, nil)
|
||||
}(i, filepath.Join(dir, filename))
|
||||
if sequential {
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// if there are errors, return the first one for deterministic results
|
||||
for _, err := range errors {
|
||||
var err error
|
||||
files[i], err = parse(filename, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, err // leave unfinished goroutines hanging
|
||||
}
|
||||
}
|
||||
} else {
|
||||
type parseResult struct {
|
||||
file *ast.File
|
||||
err error
|
||||
}
|
||||
|
||||
out := make(chan parseResult)
|
||||
for _, filename := range filenames {
|
||||
go func(filename string) {
|
||||
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
|
||||
}
|
||||
|
||||
func parseDir(dir string) ([]*ast.File, error) {
|
||||
func parseDir(dirname string) ([]*ast.File, error) {
|
||||
ctxt := build.Default
|
||||
pkginfo, err := ctxt.ImportDir(dir, 0)
|
||||
pkginfo, err := ctxt.ImportDir(dirname, 0)
|
||||
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if *xtestFiles {
|
||||
return parseFiles(dir, pkginfo.XTestGoFiles)
|
||||
}
|
||||
|
||||
filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
|
||||
if *testFiles {
|
||||
if *allFiles {
|
||||
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) {
|
||||
|
@ -269,13 +181,15 @@ func getPkgFiles(args []string) ([]*ast.File, error) {
|
|||
}
|
||||
|
||||
// list of files
|
||||
return parseFiles("", args)
|
||||
return parseFiles(args)
|
||||
}
|
||||
|
||||
func checkPkgFiles(files []*ast.File) {
|
||||
compiler := "gc"
|
||||
if *gccgo {
|
||||
compiler = "gccgo"
|
||||
}
|
||||
type bailout struct{}
|
||||
|
||||
// if checkPkgFiles is called multiple times, set up conf only once
|
||||
conf := types.Config{
|
||||
FakeImportC: true,
|
||||
Error: func(err error) {
|
||||
|
@ -284,8 +198,8 @@ func checkPkgFiles(files []*ast.File) {
|
|||
}
|
||||
report(err)
|
||||
},
|
||||
Importer: importer.For(*compiler, nil),
|
||||
Sizes: SizesFor(build.Default.Compiler, build.Default.GOARCH),
|
||||
Importer: importer.For(compiler, nil),
|
||||
Sizes: sizes,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
|
@ -320,7 +234,11 @@ func printStats(d time.Duration) {
|
|||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
if *printAST || *printTrace {
|
||||
*sequential = true
|
||||
}
|
||||
initParserMode()
|
||||
initSizes()
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func callees(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
@ -28,7 +28,7 @@ func callees(q *Query) error {
|
|||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := loadWithSoftErrors(&lconf)
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -240,7 +240,7 @@ func (r *calleesTypesResult) JSON(fset *token.FileSet) []byte {
|
|||
Desc: "static function call",
|
||||
}
|
||||
j.Callees = []*serial.Callee{
|
||||
{
|
||||
&serial.Callee{
|
||||
Name: r.callee.FullName(),
|
||||
Pos: fset.Position(r.callee.Pos()).String(),
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
"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.
|
||||
//
|
||||
func callers(q *Query) error {
|
||||
|
@ -27,7 +27,7 @@ func callers(q *Query) error {
|
|||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := loadWithSoftErrors(&lconf)
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
"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.
|
||||
//
|
||||
// The information may be misleading in a context-insensitive
|
||||
|
@ -35,7 +35,7 @@ func callstack(q *Query) error {
|
|||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := loadWithSoftErrors(&lconf)
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -86,20 +86,13 @@ func definition(q *Query) error {
|
|||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
// Look up the declaration of this identifier.
|
||||
// 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 {
|
||||
obj = qpos.info.Defs[id]
|
||||
obj := qpos.info.ObjectOf(id)
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)",
|
||||
// and the package declaration,
|
||||
// but I think that's all.
|
||||
return fmt.Errorf("no object for identifier")
|
||||
}
|
||||
}
|
||||
|
||||
if !obj.Pos().IsValid() {
|
||||
return fmt.Errorf("%s is built in", obj.Name())
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
exact "go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
|
@ -162,9 +162,6 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
|
|||
path = append([]ast.Node{n.Name}, path...)
|
||||
continue
|
||||
|
||||
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
||||
return path, actionUnknown // uninteresting
|
||||
|
||||
case ast.Stmt:
|
||||
return path, actionStmt
|
||||
|
||||
|
@ -176,6 +173,9 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
|
|||
*ast.ChanType:
|
||||
return path, actionType
|
||||
|
||||
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
||||
return path, actionUnknown // uninteresting
|
||||
|
||||
case *ast.Ellipsis:
|
||||
// Continue to enclosing node.
|
||||
// e.g. [...]T in ArrayType
|
||||
|
@ -327,56 +327,28 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error
|
|||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
if typ == nil {
|
||||
typ = types.Typ[types.Invalid]
|
||||
t := qpos.info.TypeOf(expr)
|
||||
if t == nil {
|
||||
t = types.Typ[types.Invalid]
|
||||
}
|
||||
constVal := qpos.info.Types[expr].Value
|
||||
if c, ok := obj.(*types.Const); ok {
|
||||
constVal = c.Val()
|
||||
}
|
||||
|
||||
return &describeValueResult{
|
||||
qpos: qpos,
|
||||
expr: expr,
|
||||
typ: typ,
|
||||
names: appendNames(nil, typ),
|
||||
typ: t,
|
||||
constVal: constVal,
|
||||
obj: obj,
|
||||
methods: accessibleMethods(typ, qpos.info.Pkg),
|
||||
fields: accessibleFields(typ, qpos.info.Pkg),
|
||||
methods: accessibleMethods(t, qpos.info.Pkg),
|
||||
fields: accessibleFields(t, qpos.info.Pkg),
|
||||
}, 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 {
|
||||
qpos *queryPos
|
||||
expr ast.Expr // query node
|
||||
typ types.Type // type of expression
|
||||
names []*types.Named // named types within typ
|
||||
constVal constant.Value // value of expression, if constant
|
||||
constVal exact.Value // value of expression, if constant
|
||||
obj types.Object // var/func/const object, if expr was Ident
|
||||
methods []*types.Selection
|
||||
fields []describeField
|
||||
|
@ -385,7 +357,7 @@ type describeValueResult struct {
|
|||
func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
||||
var prefix, suffix string
|
||||
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) {
|
||||
case *types.Func:
|
||||
|
@ -423,7 +395,6 @@ func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
|||
|
||||
printMethods(printf, r.expr, r.methods)
|
||||
printFields(printf, r.expr, r.fields)
|
||||
printNamedTypes(printf, r.expr, r.names)
|
||||
}
|
||||
|
||||
func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
||||
|
@ -435,21 +406,12 @@ func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
|||
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{
|
||||
Desc: astutil.NodeDescription(r.expr),
|
||||
Pos: fset.Position(r.expr.Pos()).String(),
|
||||
Detail: "value",
|
||||
Value: &serial.DescribeValue{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
TypesPos: typesPos,
|
||||
Value: value,
|
||||
ObjPos: objpos,
|
||||
},
|
||||
|
@ -460,46 +422,48 @@ func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
|||
|
||||
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
||||
var description string
|
||||
var typ types.Type
|
||||
var t types.Type
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
obj := qpos.info.ObjectOf(n).(*types.TypeName)
|
||||
typ = obj.Type()
|
||||
if isAlias(obj) {
|
||||
description = "alias of "
|
||||
} else if obj.Pos() == n.Pos() {
|
||||
description = "definition of " // (Named type)
|
||||
} else if _, ok := typ.(*types.Basic); ok {
|
||||
t = qpos.info.TypeOf(n)
|
||||
switch t := t.(type) {
|
||||
case *types.Basic:
|
||||
description = "reference to built-in "
|
||||
|
||||
case *types.Named:
|
||||
isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above
|
||||
if isDef {
|
||||
description = "definition of "
|
||||
} else {
|
||||
description = "reference to " // (Named type)
|
||||
description = "reference to "
|
||||
}
|
||||
}
|
||||
|
||||
case ast.Expr:
|
||||
typ = qpos.info.TypeOf(n)
|
||||
t = qpos.info.TypeOf(n)
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
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).
|
||||
switch typ.(type) {
|
||||
switch t.(type) {
|
||||
case *types.Named, *types.Struct:
|
||||
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
|
||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
||||
szs.Sizeof(typ), szs.Alignof(typ))
|
||||
szs.Sizeof(t), szs.Alignof(t))
|
||||
}
|
||||
|
||||
return &describeTypeResult{
|
||||
qpos: qpos,
|
||||
node: path[0],
|
||||
description: description,
|
||||
typ: typ,
|
||||
methods: accessibleMethods(typ, qpos.info.Pkg),
|
||||
fields: accessibleFields(typ, qpos.info.Pkg),
|
||||
typ: t,
|
||||
methods: accessibleMethods(t, qpos.info.Pkg),
|
||||
fields: accessibleFields(t, qpos.info.Pkg),
|
||||
}, 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) {
|
||||
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 {
|
||||
qualifier := types.RelativeTo(obj.Pkg())
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
||||
switch obj := obj.(type) {
|
||||
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:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
|
||||
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.
|
||||
switch typ := typ.(type) {
|
||||
var abbrev string
|
||||
switch t := obj.Type().Underlying().(type) {
|
||||
case *types.Interface:
|
||||
if typ.NumMethods() > 1 {
|
||||
typestr = "interface{...}"
|
||||
if t.NumMethods() > 1 {
|
||||
abbrev = "interface{...}"
|
||||
}
|
||||
case *types.Struct:
|
||||
if typ.NumFields() > 1 {
|
||||
typestr = "struct{...}"
|
||||
if t.NumFields() > 1 {
|
||||
abbrev = "struct{...}"
|
||||
}
|
||||
}
|
||||
if typestr == "" {
|
||||
typestr = types.TypeString(typ, qualifier)
|
||||
if abbrev == "" {
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type().Underlying(), qualifier))
|
||||
} else {
|
||||
fmt.Fprintf(&buf, " %s", abbrev)
|
||||
}
|
||||
buf.WriteString(typestr)
|
||||
|
||||
case *types.Var:
|
||||
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 {
|
||||
var members []*serial.DescribeMember
|
||||
for _, mem := range r.members {
|
||||
obj := mem.obj
|
||||
typ := obj.Type()
|
||||
typ := mem.obj.Type()
|
||||
var val string
|
||||
var alias string
|
||||
switch obj := obj.(type) {
|
||||
switch mem := mem.obj.(type) {
|
||||
case *types.Const:
|
||||
val = obj.Val().String()
|
||||
val = constValString(mem.Val())
|
||||
case *types.TypeName:
|
||||
if isAlias(obj) {
|
||||
alias = "= " // kludgy
|
||||
} else {
|
||||
typ = typ.Underlying()
|
||||
}
|
||||
}
|
||||
members = append(members, &serial.DescribeMember{
|
||||
Name: obj.Name(),
|
||||
Type: alias + typ.String(),
|
||||
Name: mem.obj.Name(),
|
||||
Type: typ.String(),
|
||||
Value: val,
|
||||
Pos: fset.Position(obj.Pos()).String(),
|
||||
Kind: tokenOf(obj),
|
||||
Pos: fset.Position(mem.obj.Pos()).String(),
|
||||
Kind: tokenOf(mem.obj),
|
||||
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