Compare commits

..

15 Commits

Author SHA1 Message Date
Rebecca Stambler e7e52e73e2 [release-branch.go1.10] godoc/static: update copyright year in static.go
This change was created with `go generate
golang.org/x/tools/godoc/static` and updates the year in the copyright
notice in the file.

Change-Id: I5916b7a6d1f1ceae84d58c392767ca97b314ebc3
Reviewed-on: https://go-review.googlesource.com/c/156077
Reviewed-by: Heschi Kreinick <heschi@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
(cherry picked from commit 7c850e7ac1)
Reviewed-on: https://go-review.googlesource.com/c/156340
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-01-04 18:27:19 +00:00
Brad Fitzpatrick 156d532d4f [release-branch.go1.10] godoc/dl: update Mac & Windows minimum versions
Updates golang/go#27213

Change-Id: I25813e9aafcdb39d4f93e27b98d8672c770234a6
Reviewed-on: https://go-review.googlesource.com/131402
Reviewed-by: Andrew Bonventre <andybons@golang.org>
(cherry picked from commit 1057b4d08146b510c47b87a2221e9720fe1a9fb9)
Reviewed-on: https://go-review.googlesource.com/131403
2018-08-26 16:47:53 +00:00
Robert Griesemer 8cc6a32d9b [release-branch.go1.10] godoc: fix test failure
Adjust code to satisfy vet.

Change-Id: I532b4d988eb29b413520de60b8520d9e100b42c9
Reviewed-on: https://go-review.googlesource.com/118557
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
(cherry picked from commit 02fcd6aaf1)
Reviewed-on: https://go-review.googlesource.com/126835
Run-TryBot: Andrew Bonventre <andybons@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
2018-07-31 19:39:58 +00:00
Andrew Bonventre 0d7f9d6ece [release-branch.go1.10] godoc: don't display tar instructions for malformed download query
Ensure that the filename passed via ?download= matches the given
regexp before showing the command-line instructions to download it.

Change-Id: I87d734ebeb1f5e8e1b4311a8be6b3ae18bef8b10
Reviewed-on: https://go-review.googlesource.com/126798
Reviewed-by: Andrew Bonventre <andybons@golang.org>
2018-07-30 22:30:05 +00:00
Brad Fitzpatrick be728107ea [release-branch.go1.10] godoc/dl: also serve go-import meta tags at golang.org/dl for cmd/go
Updates golang/go#23223

Change-Id: Iacecbb5e095fd3d6acb3f8e1fb238db63d1e0b6d
Reviewed-on: https://go-review.googlesource.com/125195
Reviewed-by: Andrew Bonventre <andybons@golang.org>
(cherry picked from commit 99195f4d4f)
Reviewed-on: https://go-review.googlesource.com/125176
2018-07-20 16:20:53 +00:00
Andrew Bonventre d1fff0794f [release-branch.go1.10] godoc/dl: update download link from /x/build/version to /dl
Change-Id: Id2baaa87ab23e27ce5018271c8bb4e73750fb437
Reviewed-on: https://go-review.googlesource.com/125137
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
(cherry picked from commit b1e472a6fcaf6cd39293984c1d101cb1f4b9e7cd)
Reviewed-on: https://go-review.googlesource.com/125155
2018-07-20 03:36:27 +00:00
Brad Fitzpatrick 42933c415e [release-branch.go1.10] godoc/dl: add meta tags for go get golang.org/dl/go1.N
Updates golang/go#23223

Change-Id: I628cea181d3a0e6bb25fdd98e098581aa222e049
Reviewed-on: https://go-review.googlesource.com/123679
Reviewed-by: Andrew Bonventre <andybons@golang.org>
(cherry picked from commit 827f47db85)
Reviewed-on: https://go-review.googlesource.com/125175
2018-07-20 03:36:06 +00:00
Filippo Valsorda e9928cbe4a [release-branch.go1.10] godoc/dl: add valsorda to users allowed to upload
Change-Id: Iac4a80b030be9c275323a27d2dfba4f83942cbf9
Reviewed-on: https://go-review.googlesource.com/117195
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
(cherry picked from commit 4e38c85762)
Reviewed-on: https://go-review.googlesource.com/117215
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2018-06-07 19:00:54 +00:00
Andrew Bonventre d3e4ceb59d [release-branch.go1.10] godoc: don't exclude version field from File struct
When uploading a new release, it deserializes the payload into
a File struct for processing. It needs the Version field to
determine which release it's in.

Change-Id: I171f416dbb1b1b6c354948ea9a31b9c8694f892e
Reviewed-on: https://go-review.googlesource.com/110627
Run-TryBot: Andrew Bonventre <andybons@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
(cherry picked from commit 7d1093680e1fd99c46025161a45e7f5230c79d60)
Reviewed-on: https://go-review.googlesource.com/110635
Reviewed-by: Andrew Bonventre <andybons@golang.org>
2018-05-01 16:38:31 +00:00
Andrew Bonventre 97530abbb5 [release-branch.go1.10] cmd/godoc: add x/lint and x/vgo redirects
Update golang/go#25048

Change-Id: I6e21f167091338600c2a0a37411b0a565d27ce36
Reviewed-on: https://go-review.googlesource.com/96196
Run-TryBot: Andrew Bonventre <andybons@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
(cherry picked from commit 733d489786)
Reviewed-on: https://go-review.googlesource.com/109095
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2018-04-24 16:22:42 +00:00
Zachary Gershman bd7f39a7ac [release-branch.go1.10] godoc/dl: provide JSON feed of releases
This addresses the build errors that caused a
revert of the original PR golang/tools#21

Fixes golang/go#23746

GitHub-Last-Rev: 6606c0b63afb4f1043b2aa2dc640edda4cf9afe8
GitHub-Pull-Request: golang/tools#25
Change-Id: Ic5a7e3054d182dc2041d2966ca68960c3abd7620
Reviewed-on: https://go-review.googlesource.com/96178
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
(cherry picked from commit 86e0f6745d)
Reviewed-on: https://go-review.googlesource.com/102795
Run-TryBot: Andrew Bonventre <andybons@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
2018-03-28 02:06:43 +00:00
Yury Smolsky 27eedfbdea [release-branch.go1.10] cmd/godoc: fix TestWebIndex test
The godoc in the test was indexing sources in the default GOPATH.
If the default GOPATH pointed to local workspace, test would timeout.
The fix is to supply GOPATH set to non-existing path.

Fixes golang/go#24504

Change-Id: Iedf044cdec78d5c5642105650ad8ec17aa10a5ba
Reviewed-on: https://go-review.googlesource.com/102295
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
(cherry picked from commit 77106db15f)
Reviewed-on: https://go-review.googlesource.com/102796
Run-TryBot: Andrew Bonventre <andybons@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
2018-03-28 02:06:15 +00:00
Agniva De Sarker 1396c68d3b [release-branch.go1.10] godoc: init corpus in a separate goroutine in http mode
Currently, in http mode the server blocks until the corpus
has been initialized. This can cause considerable delay
if the user workspace is significantly large and the files
are not present in the buffer cache.

This CL spawns off the initialization in a separate goroutine
if httpMode is set and turns on a flag when it's done.
The http handler checks the flag and returns an error response
if it has not been set.

The check is only performed for the path prefixes handled by the
handlerServer struct. Other paths do not call the GetPageInfo() function
and hence can return immediately. This preserves maximum responsiveness
of the server.

Also adds an additional print statement in verbose mode

Note: This is a re-do of a previous CL golang.org/cl/88695 which was
incorrect committed without running tests. This CL fixes that test.

Fixes golang/go#13278

Change-Id: I80c801f32af007312090d3783a2ea2c6f92cad66
Reviewed-on: https://go-review.googlesource.com/93215
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
(cherry picked from commit 006ac430a0)
Reviewed-on: https://go-review.googlesource.com/102799
Run-TryBot: Andrew Bonventre <andybons@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
2018-03-27 23:32:53 +00:00
Andrew Bonventre c1ca329f65 [release-branch.go1.10] Revert "godoc: init corpus in a separate goroutine in http mode"
This reverts commit f86b507a7e.

Reason for revert: broke tests in tools repo

Change-Id: Id7e5d8e050896b6f5fedaee705be8a5f9adf4bf3
Reviewed-on: https://go-review.googlesource.com/93115
Run-TryBot: Andrew Bonventre <andybons@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
(cherry picked from commit 70252dea49)
Reviewed-on: https://go-review.googlesource.com/102797
2018-03-27 23:26:22 +00:00
Robert Griesemer 4c772e4117 [release-branch.go1.10] cmd/guru: fix .golden file (fix build)
This regression is due to https://go-review.googlesource.com/c/go/+/100235
which made the go/scanner behavior match the compiler's scanner by
not modifying filenames in line directives in any form. Specifically,
relative filenames now remain relative. This makes it easier to reason
about the behavior of the scanner but may require clients to adjust
those filenames.

If removing the longer path in the .golden file is not satisfactory,
the (scanner) client may have to massage positions returned via line
directives; or better, the line directives are created with extended
path information.

R=​adonovan

Change-Id: I836571a6dafef5e6d81e4c7add203c994e107055
Reviewed-on: https://go-review.googlesource.com/101015
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
(cherry picked from commit c4b4e4b0fa)
Reviewed-on: https://go-review.googlesource.com/102798
Run-TryBot: Andrew Bonventre <andybons@golang.org>
2018-03-27 23:26:09 +00:00
861 changed files with 86378 additions and 98547 deletions

View File

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

View File

@ -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.
@ -199,8 +191,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 +417,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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -105,8 +105,8 @@ 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 + \"_\")")
underscore = flag.Bool("underscore", false, "rewrite golang.org to golang_org in imports; temporary workaround for golang.org/issue/16333")
importMap = map[string]string{}
)
@ -203,8 +203,9 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
// 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)
if prefix == "" {
pkgName := info.Files[0].Name.Name
prefix = pkgName + "_"
}
objsToUpdate := make(map[types.Object]bool)
@ -298,7 +299,7 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
pkgStd[spec] = true
} else {
if *underscore {
spec = strings.Replace(spec, "golang.org/x/", "internal/x/", 1)
spec = strings.Replace(spec, "golang.org/", "golang_org/", 1)
}
pkgExt[spec] = true
}

View File

@ -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:
@ -118,6 +118,8 @@ Flags:
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:
@ -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.
_, 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,7 +221,7 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
}
}
mains, err := mainPackages(pkgs)
mains, err := mainPackages(prog, tests)
if err != nil {
return err
}
@ -237,7 +237,7 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
cg = ptares.CallGraph
case "rta":
mains, err := mainPackages(pkgs)
mains, err := mainPackages(prog, tests)
if err != nil {
return err
}
@ -305,13 +305,25 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
// mainPackages returns the main packages to analyze.
// Each resulting package is named "main" and has a main function.
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
func mainPackages(prog *ssa.Program, tests bool) ([]*ssa.Package, error) {
pkgs := prog.AllPackages() // TODO(adonovan): use only initial packages
// If tests, create a "testmain" package for each test.
var mains []*ssa.Package
for _, p := range pkgs {
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
mains = append(mains, p)
if tests {
for _, pkg := range pkgs {
if main := prog.CreateTestMainPackage(pkg); main != nil {
mains = append(mains, main)
}
}
if mains == nil {
return nil, fmt.Errorf("no tests")
}
return mains, nil
}
// Otherwise, use the main packages.
mains = append(mains, ssautil.MainPackages(pkgs)...)
if len(mains) == 0 {
return nil, fmt.Errorf("no main packages")
}

View File

@ -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$testmain.init --> pkg.init`,
`pkg.Example --> (pkg.C).f`,
`pkg.main --> (pkg.C).f`,
}},
{"pta", true, []string{
`<root> --> pkg.test.main`,
`<root> --> pkg.main`,
`pkg.test.main --> testing.MainStart`,
`testing.runExample --> pkg.Example`,
{"pta", format, true, []string{
`<root> --> pkg$testmain.init`,
`<root> --> pkg.Example`,
`pkg$testmain.init --> pkg.init`,
`pkg.Example --> (pkg.C).f`,
`pkg.main --> (pkg.C).f`,
}},
} {
const format = "{{.Caller}} --> {{.Callee}}"
stdout = new(bytes.Buffer)
if err := doCallgraph("testdata/src", gopath, test.algo, format, test.tests, []string{"pkg"}); err != nil {
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
}

View File

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

View File

@ -22,21 +22,12 @@
// -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.
//
@ -46,15 +37,12 @@
// -obj
// Report object file statistics.
//
// -pkg 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.
@ -79,9 +67,9 @@ package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"go/build"
"io/ioutil"
"log"
"os"
@ -96,7 +84,6 @@ import (
var (
goroot string
compiler string
linker string
runRE *regexp.Regexp
is6g bool
)
@ -107,8 +94,6 @@ var (
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`")
@ -118,31 +103,24 @@ var (
flagShort = flag.Bool("short", false, "skip long-running benchmarks")
)
type test struct {
var tests = []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}},
dir string
long bool
}{
{"BenchmarkTemplate", "html/template", false},
{"BenchmarkUnicode", "unicode", false},
{"BenchmarkGoTypes", "go/types", false},
{"BenchmarkCompiler", "cmd/compile/internal/gc", false},
{"BenchmarkSSA", "cmd/compile/internal/ssa", false},
{"BenchmarkFlate", "compress/flate", false},
{"BenchmarkGoParser", "go/parser", false},
{"BenchmarkReflect", "reflect", false},
{"BenchmarkTar", "archive/tar", false},
{"BenchmarkXML", "encoding/xml", false},
{"BenchmarkStdCmd", "", true},
{"BenchmarkHelloSize", "", false},
{"BenchmarkCmdGoSize", "", true},
}
func usage() {
@ -166,27 +144,19 @@ func main() {
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" {
out, err := exec.Command(*flagGoCmd, "tool", "-n", "compile").CombinedOutput()
if err != nil {
out, err = exec.Command(*flagGoCmd, "tool", "-n", "6g").CombinedOutput()
is6g = true
if err != nil {
out, err = exec.Command(*flagGoCmd, "tool", "-n", "compile").CombinedOutput()
log.Fatalf("go tool -n compiler: %v\n%s", err, out)
}
}
linker = *flagLinker
if linker == "" && !is6g { // TODO: Support 6l
_, linker = toolPath("link")
}
if is6g {
*flagMemprofilerate = -1
*flagAlloc = false
*flagCpuprofile = ""
*flagMemprofile = ""
compiler = strings.TrimSpace(string(out))
}
if *flagRun != "" {
@ -197,117 +167,67 @@ func main() {
runRE = r
}
if *flagPackage != "" {
tests = []test{
{"BenchmarkPkg", compile{*flagPackage}},
{"BenchmarkPkgLink", link{*flagPackage}},
}
runRE = nil
}
for i := 0; i < *flagCount; i++ {
if *flagPackage != "" {
runBuild("BenchmarkPkg", *flagPackage, i)
continue
}
for _, tt := range tests {
if tt.r.long() && *flagShort {
if tt.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)
}
runBuild(tt.name, tt.dir, i)
}
}
}
}
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 {
func runCmd(name string, cmd *exec.Cmd) {
start := time.Now()
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%v\n%s", err, out)
log.Printf("%v: %v\n%s", name, err, out)
return
}
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 {
func runStdCmd() {
args := []string{"build", "-a"}
if *flagCompilerFlags != "" {
args = append(args, "-gcflags", *flagCompilerFlags)
}
args = append(args, r.pkgs...)
args = append(args, "std", "cmd")
cmd := exec.Command(*flagGoCmd, args...)
cmd.Dir = filepath.Join(goroot, "src")
return runCmd(name, cmd)
runCmd("BenchmarkStdCmd", 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)
// path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
func runSize(name, path string) {
cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", path)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
log.Print(err)
return
}
defer os.Remove("_compilebenchout_")
info, err := os.Stat("_compilebenchout_")
if err != nil {
return err
log.Print(err)
return
}
out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
if err != nil {
return fmt.Errorf("size: %v\n%s", err, out)
log.Printf("size: %v\n%s", err, out)
return
}
lines := strings.Split(string(out), "\n")
if len(lines) < 2 {
return fmt.Errorf("not enough output from size: %s", out)
log.Printf("not enough output from size: %s", out)
return
}
f := strings.Fields(lines[1])
if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
@ -315,31 +235,110 @@ func (r size) run(name string, count int) error {
} 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)
func runBuild(name, dir string, count int) {
switch name {
case "BenchmarkStdCmd":
runStdCmd()
return
case "BenchmarkCmdGoSize":
runSize("BenchmarkCmdGoSize", "cmd/go")
return
case "BenchmarkHelloSize":
runSize("BenchmarkHelloSize", filepath.Join(goroot, "test/helloworld.go"))
return
}
// Find dir and source file list.
pkg, err := goList(c.dir)
pkg, err := build.Import(dir, ".", 0)
if err != nil {
return err
log.Print(err)
return
}
args := []string{"-o", "_compilebench_.o"}
if is6g {
*flagMemprofilerate = -1
*flagAlloc = false
*flagCpuprofile = ""
*flagMemprofile = ""
}
if *flagMemprofilerate >= 0 {
args = append(args, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
}
args = append(args, strings.Fields(*flagCompilerFlags)...)
if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
if *flagAlloc || *flagMemprofile != "" {
args = append(args, "-memprofile", "_compilebench_.memprof")
}
if *flagCpuprofile != "" {
args = append(args, "-cpuprofile", "_compilebench_.cpuprof")
}
}
args = append(args, pkg.GoFiles...)
if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil {
return err
cmd := exec.Command(compiler, args...)
cmd.Dir = pkg.Dir
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
start := time.Now()
err = cmd.Run()
if err != nil {
log.Printf("%v: %v", name, err)
return
}
end := time.Now()
var allocs, allocbytes int64
if *flagAlloc || *flagMemprofile != "" {
out, err := ioutil.ReadFile(pkg.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
}
switch f[1] {
case "TotalAlloc":
allocbytes = val
case "Mallocs":
allocs = val
}
}
if *flagMemprofile != "" {
if err := ioutil.WriteFile(*flagMemprofile, out, 0666); err != nil {
log.Print(err)
}
}
os.Remove(pkg.Dir + "/_compilebench_.memprof")
}
if *flagCpuprofile != "" {
out, err := ioutil.ReadFile(pkg.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(pkg.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 *flagAlloc {
fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs)
}
opath := pkg.Dir + "/_compilebench_.o"
@ -358,147 +357,4 @@ func (c compile) run(name string, count int) error {
fmt.Println()
os.Remove(opath)
return nil
}
type link struct{ dir string }
func (link) long() bool { return false }
func (r link) run(name string, count int) error {
if linker == "" {
// No linker. Skip the test.
return nil
}
// Build dependencies.
out, err := exec.Command(*flagGoCmd, "build", "-i", "-o", "/dev/null", r.dir).CombinedOutput()
if err != nil {
return fmt.Errorf("go build -i %s: %v\n%s", r.dir, err, out)
}
// Build the main package.
pkg, err := goList(r.dir)
if err != nil {
return err
}
args := []string{"-o", "_compilebench_.o"}
args = append(args, pkg.GoFiles...)
cmd := exec.Command(compiler, args...)
cmd.Dir = pkg.Dir
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return fmt.Errorf("compiling: %v", err)
}
defer os.Remove(pkg.Dir + "/_compilebench_.o")
// Link the main package.
args = []string{"-o", "_compilebench_.exe"}
args = append(args, strings.Fields(*flagLinkerFlags)...)
args = append(args, "_compilebench_.o")
if err := runBuildCmd(name, count, pkg.Dir, linker, args); err != nil {
return err
}
fmt.Println()
defer os.Remove(pkg.Dir + "/_compilebench_.exe")
return err
}
// runBuildCmd runs "tool args..." in dir, measures standard build
// tool metrics, and prints a benchmark line. The caller may print
// additional metrics and then must print a newline.
//
// This assumes tool accepts standard build tool flags like
// -memprofilerate, -memprofile, and -cpuprofile.
func runBuildCmd(name string, count int, dir, tool string, args []string) error {
var preArgs []string
if *flagMemprofilerate >= 0 {
preArgs = append(preArgs, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
}
if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
if *flagAlloc || *flagMemprofile != "" {
preArgs = append(preArgs, "-memprofile", "_compilebench_.memprof")
}
if *flagCpuprofile != "" {
preArgs = append(preArgs, "-cpuprofile", "_compilebench_.cpuprof")
}
}
cmd := exec.Command(tool, append(preArgs, args...)...)
cmd.Dir = dir
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
start := time.Now()
err := cmd.Run()
if err != nil {
return err
}
end := time.Now()
haveAllocs := false
var allocs, allocbytes int64
if *flagAlloc || *flagMemprofile != "" {
out, err := ioutil.ReadFile(dir + "/_compilebench_.memprof")
if err != nil {
log.Print("cannot find memory profile after compilation")
}
for _, line := range strings.Split(string(out), "\n") {
f := strings.Fields(line)
if len(f) < 4 || f[0] != "#" || f[2] != "=" {
continue
}
val, err := strconv.ParseInt(f[3], 0, 64)
if err != nil {
continue
}
haveAllocs = true
switch f[1] {
case "TotalAlloc":
allocbytes = val
case "Mallocs":
allocs = val
}
}
if !haveAllocs {
log.Println("missing stats in memprof (golang.org/issue/18641)")
}
if *flagMemprofile != "" {
outpath := *flagMemprofile
if *flagCount != 1 {
outpath = fmt.Sprintf("%s_%d", outpath, count)
}
if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
log.Print(err)
}
}
os.Remove(dir + "/_compilebench_.memprof")
}
if *flagCpuprofile != "" {
out, err := ioutil.ReadFile(dir + "/_compilebench_.cpuprof")
if err != nil {
log.Print(err)
}
outpath := *flagCpuprofile
if *flagCount != 1 {
outpath = fmt.Sprintf("%s_%d", outpath, count)
}
if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
log.Print(err)
}
os.Remove(dir + "/_compilebench_.cpuprof")
}
wallns := end.Sub(start).Nanoseconds()
userns := cmd.ProcessState.UserTime().Nanoseconds()
fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns)
if haveAllocs {
fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs)
}
return nil
}

View File

@ -1,83 +1,18 @@
// Copyright 2019 The Go Authors. All rights reserved.
// 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 {

View File

@ -1,6 +1,3 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
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

View 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.

View File

@ -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"
@ -239,6 +224,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)

View File

@ -2,7 +2,7 @@
// 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
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package main

View File

@ -7,7 +7,7 @@ package main
import (
"bytes"
"fmt"
"go/constant"
exact "go/constant"
"go/token"
"go/types"
"io"
@ -213,9 +213,9 @@ func (p *printer) printDecl(keyword string, n int, printGroup func()) {
// absInt returns the absolute value of v as a *big.Int.
// 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 +229,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 +272,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 +286,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()

View File

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

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

@ -0,0 +1,82 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build appengine
package main
// This file replaces main.go when running godoc under app-engine.
// See README.godoc-app for details.
import (
"archive/zip"
"log"
"net/http"
"path"
"regexp"
"golang.org/x/tools/godoc"
"golang.org/x/tools/godoc/dl"
"golang.org/x/tools/godoc/proxy"
"golang.org/x/tools/godoc/short"
"golang.org/x/tools/godoc/static"
"golang.org/x/tools/godoc/vfs"
"golang.org/x/tools/godoc/vfs/mapfs"
"golang.org/x/tools/godoc/vfs/zipfs"
"google.golang.org/appengine"
)
func init() {
enforceHosts = !appengine.IsDevAppServer()
playEnabled = true
log.Println("initializing godoc ...")
log.Printf(".zip file = %s", zipFilename)
log.Printf(".zip GOROOT = %s", zipGoroot)
log.Printf("index files = %s", indexFilenames)
goroot := path.Join("/", zipGoroot) // fsHttp paths are relative to '/'
// read .zip file and set up file systems
const zipfile = zipFilename
rc, err := zip.OpenReader(zipfile)
if err != nil {
log.Fatalf("%s: %s\n", zipfile, err)
}
// rc is never closed (app running forever)
fs.Bind("/", zipfs.New(rc, zipFilename), goroot, vfs.BindReplace)
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
corpus := godoc.NewCorpus(fs)
corpus.Verbose = false
corpus.MaxResults = 10000 // matches flag default in main.go
corpus.IndexEnabled = true
corpus.IndexFiles = indexFilenames
if err := corpus.Init(); err != nil {
log.Fatal(err)
}
corpus.IndexDirectory = indexDirectoryDefault
go corpus.RunIndexer()
pres = godoc.NewPresentation(corpus)
pres.TabWidth = 8
pres.ShowPlayground = true
pres.ShowExamples = true
pres.DeclLinks = true
pres.NotesRx = regexp.MustCompile("BUG")
readTemplates(pres, true)
mux := registerHandlers(pres)
dl.RegisterHandlers(mux)
short.RegisterHandlers(mux)
// Register /compile and /share handlers against the default serve mux
// so that other app modules can make plain HTTP requests to those
// hosts. (For reasons, HTTPS communication between modules is broken.)
proxy.RegisterHandlers(http.DefaultServeMux)
log.Println("godoc initialization complete")
}

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

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

View File

@ -21,7 +21,7 @@ import (
const (
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)

View File

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

View File

@ -6,19 +6,50 @@
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 +63,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 +74,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
(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 +103,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 +111,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
@ -109,11 +143,11 @@ 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"

View File

@ -0,0 +1,9 @@
// 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_test
func init() { isGo19 = true }

View File

@ -8,7 +8,6 @@ import (
"bufio"
"bytes"
"fmt"
"go/build"
"io"
"io/ioutil"
"net"
@ -32,10 +31,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 +53,91 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
return bin, func() { os.RemoveAll(tmp) }
}
var isGo19 bool // godoc19_test.go sets it to true.
// Basic regression test for godoc command-line tool.
func TestCLI(t *testing.T) {
bin, cleanup := buildGodoc(t)
defer cleanup()
// condStr returns s if cond is true, otherwise empty string.
condStr := func(cond bool, s string) string {
if !cond {
return ""
}
return s
}
tests := []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{
`cannot find package` +
// TODO: Remove this when support for Go 1.8 is dropped.
condStr(!isGo19,
// For Go 1.8 and older, because it doesn't have CL 33158 change applied to go/build.
// 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\.`,
},
},
}
for _, test := range tests {
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 {
@ -122,62 +202,11 @@ func waitForServer(t *testing.T, url, match string, timeout time.Duration, rever
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)
@ -194,7 +223,7 @@ 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")
t.Skip("skipping on plan9; files to start up quickly enough")
}
bin, cleanup := buildGodoc(t)
defer cleanup()
@ -208,14 +237,10 @@ func testWeb(t *testing.T, withIndex bool) {
cmd.Stderr = os.Stderr
cmd.Args[0] = "godoc"
// Set GOPATH variable to non-existing path
// and GOPROXY=off to disable module fetches.
// Set GOPATH variable to non-existing path.
// 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 = append(os.Environ(), "GOPATH=does_not_exist")
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start godoc: %s", err)
@ -231,108 +256,70 @@ func testWeb(t *testing.T, withIndex bool) {
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",
},
notContains: []string{
dontmatch: []string{
"cmd/gc",
},
},
{
path: "/search?q=ListenAndServe",
contains: []string{
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{
match: []string{
`href="/src/cmd/compile/internal/amd64/ssa.go"`,
},
},
{
path: "/pkg/math/bits/",
contains: []string{
`Added in Go 1.9`,
},
},
{
path: "/pkg/net/",
contains: []string{
`// IPv6 scoped addressing zone; added in Go 1.1`,
},
},
{
path: "/pkg/net/http/httptrace/",
match: []string{
`Got1xxResponse.*// Go 1\.11`,
},
releaseTag: "go1.11",
},
// Verify we don't add version info to a struct field added the same time
// as the struct itself:
{
path: "/pkg/net/http/httptrace/",
match: []string{
`(?m)GotFirstResponseByte func\(\)\s*$`,
},
},
// Remove trailing periods before adding semicolons:
{
path: "/pkg/database/sql/",
contains: []string{
"The number of connections currently in use; added in Go 1.11",
"The number of idle connections; added in Go 1.11",
},
releaseTag: "go1.11",
},
}
for _, test := range tests {
if test.needIndex && !withIndex {
@ -345,34 +332,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
@ -427,8 +398,6 @@ func main() { print(lib.V) }
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", filepath.Join(tmpdir, "goroot")))
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath")))
cmd.Env = append(cmd.Env, "GO111MODULE=off")
cmd.Env = append(cmd.Env, "GOPROXY=off")
cmd.Stdout = os.Stderr
stderr, err := cmd.StderrPipe()
if err != nil {

View File

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

View File

@ -21,7 +21,6 @@ import (
"text/template"
"golang.org/x/tools/godoc"
"golang.org/x/tools/godoc/golangorgenv"
"golang.org/x/tools/godoc/redirect"
"golang.org/x/tools/godoc/vfs"
)
@ -31,6 +30,8 @@ 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
@ -40,11 +41,11 @@ type hostEnforcerHandler struct {
}
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 +55,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", "golang.google.cn":
return true
}
return false
@ -112,7 +105,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,12 +120,12 @@ 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 {

View File

@ -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,7 @@ import (
"golang.org/x/tools/godoc/vfs/zipfs"
)
const defaultAddr = "localhost:6060" // default webserver address
const defaultAddr = ":6060" // default webserver address
var (
// file system to serve
@ -56,21 +66,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")
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 +102,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,22 +132,22 @@ 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")
@ -155,26 +164,22 @@ 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: server and no args.
if (*httpAddr != "" || *urlFlag != "") && (flag.NArg() > 0) {
fmt.Fprintln(os.Stderr, "can't use -http with args.")
usage()
}
// Set the resolved goroot.
vfs.GOROOT = *goroot
// Check usage: command line args or index creation mode.
if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex {
fmt.Fprintln(os.Stderr, "missing args.")
usage()
}
fsGate := make(chan bool, 20)
var fsGate chan bool
fsGate = make(chan bool, 20)
// Determine file system to use.
if *zipfile == "" {
@ -201,6 +206,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 +225,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 +233,31 @@ 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 {
}
if *writeIndex || httpMode || *urlFlag != "" {
if httpMode {
go initCorpus(corpus)
} else {
initCorpus(corpus)
}
}
// 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 +292,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,9 +323,9 @@ func main() {
go analysis.Run(pointerAnalysis, &corpus.Analysis)
}
if runHTTPS != nil {
if serveAutoCertHook != nil {
go func() {
if err := runHTTPS(handler); err != nil {
if err := serveAutoCertHook(handler); err != nil {
log.Fatalf("ListenAndServe TLS: %v", err)
}
}()
@ -321,18 +335,23 @@ func main() {
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
}
if *query {
handleRemoteSearch()
return
}
if err := godoc.CommandLine(os.Stdout, fs, pres, flag.Args()); err != nil {
log.Print(err)
}
}
// Hooks that are set non-nil in autocert.go if the "autocert" build tag
// is used.
var (
certInit func()
runHTTPS func(http.Handler) error
wrapHTTPMux func(http.Handler) http.Handler
)
// serveAutoCertHook if non-nil specifies a function to listen on port 443.
// See autocert.go.
var serveAutoCertHook func(http.Handler) error

View File

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

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

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

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

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

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

@ -0,0 +1,92 @@
// 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"},
"lint": {"https://go.googlesource.com/lint", "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"},
"vgo": {"https://go.googlesource.com/vgo", "git"},
}
func init() {
http.HandleFunc(xPrefix, xHandler)
}
func xHandler(w http.ResponseWriter, r *http.Request) {
head, tail := strings.TrimPrefix(r.URL.Path, xPrefix), ""
if i := strings.Index(head, "/"); i != -1 {
head, tail = head[:i], head[i:]
}
if head == "" {
http.Redirect(w, r, "https://godoc.org/-/subrepo", http.StatusTemporaryRedirect)
return
}
repo, ok := xMap[head]
if !ok {
http.NotFound(w, r)
return
}
data := struct {
Prefix, Head, Tail string
Repo xRepo
}{xPrefix, head, tail, repo}
if err := xTemplate.Execute(w, data); err != nil {
log.Println("xHandler:", err)
}
}
var xTemplate = template.Must(template.New("x").Parse(`<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="golang.org{{.Prefix}}{{.Head}} {{.Repo.VCS}} {{.Repo.URL}}">
<meta name="go-source" content="golang.org{{.Prefix}}{{.Head}} https://github.com/golang/{{.Head}}/ https://github.com/golang/{{.Head}}/tree/master{/dir} https://github.com/golang/{{.Head}}/blob/master{/dir}/{file}#L{line}">
<meta http-equiv="refresh" content="0; url=https://godoc.org/golang.org{{.Prefix}}{{.Head}}{{.Tail}}">
</head>
<body>
Nothing to see here; <a href="https://godoc.org/golang.org{{.Prefix}}{{.Head}}{{.Tail}}">move along</a>.
</body>
</html>
`))

View File

@ -13,6 +13,8 @@ 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-autoloads)
(add-hook 'before-save-hook 'gofmt-before-save)
For vim, set "gofmt_command" to "goimports":

View File

@ -10,7 +10,6 @@ import (
"errors"
"flag"
"fmt"
"go/build"
"go/scanner"
"io"
"io/ioutil"
@ -22,7 +21,7 @@ import (
"runtime/pprof"
"strings"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/imports"
)
var (
@ -31,7 +30,6 @@ 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
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
@ -43,19 +41,13 @@ var (
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) {
@ -258,7 +250,7 @@ func gofmtMain() {
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)

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The gopls command is an LSP server for Go.
// The Language Server Protocol allows any text editor
// to be extended with IDE-like features;
// see https://langserver.org/ for details.
package main // import "golang.org/x/tools/cmd/gopls"
import (
"context"
"os"
"golang.org/x/tools/internal/lsp/cmd"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/tool"
)
func main() {
debug.Version += "-cmd.gopls"
tool.Main(context.Background(), cmd.New("gopls-legacy", "", nil), os.Args[1:])
}

View File

@ -48,8 +48,6 @@ func TestGeneratedFiles(t *testing.T) {
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{
@ -312,9 +310,6 @@ func g() { fmt.Println(test.Foo(3)) }
// 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 {

View File

@ -387,8 +387,6 @@ func setup() {
yaccpar = strings.Replace(yaccpartext, "$$", prefix, -1)
openup()
fmt.Fprintf(ftable, "// Code generated by goyacc %s. DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
defin(0, "$end")
extval = PRIVATE // tokens start in unicode 'private use'
defin(0, "error")
@ -1272,7 +1270,7 @@ l1:
func cpyact(curprod []int, max int) {
if !lflag {
fmt.Fprintf(fcode, "\n//line %v:%v", infile, lineno)
fmt.Fprintf(fcode, "\n\t\t//line %v:%v", infile, lineno)
}
fmt.Fprint(fcode, "\n\t\t")
@ -1414,7 +1412,8 @@ loop:
if nnc == '/' {
fcode.WriteRune('*')
fcode.WriteRune('/')
continue loop
c = getrune(finput)
break swt
}
ungetrune(finput, nnc)
}

View File

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

View File

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

View File

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

View File

@ -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
@ -340,7 +340,6 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error
qpos: qpos,
expr: expr,
typ: typ,
names: appendNames(nil, typ),
constVal: constVal,
obj: obj,
methods: accessibleMethods(typ, qpos.info.Pkg),
@ -348,35 +347,11 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error
}, 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
@ -423,7 +398,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 +409,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,
},
@ -559,19 +524,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)

View File

@ -75,7 +75,7 @@ type Query struct {
PTALog io.Writer // (optional) pointer-analysis log file
Reflection bool // model reflection soundly (currently slow).
// result-printing function, safe for concurrent use
// result-printing function
Output func(*token.FileSet, QueryResult)
}

View File

@ -6,7 +6,7 @@ package main_test
// This file defines a test framework for guru queries.
//
// The files beneath testdata/src contain Go programs containing
// The files beneath testdata/src/main contain Go programs containing
// query annotations of the form:
//
// @verb id "select"
@ -35,7 +35,6 @@ import (
"go/token"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
@ -50,18 +49,6 @@ import (
guru "golang.org/x/tools/cmd/guru"
)
func init() {
// This test currently requires GOPATH mode.
// Explicitly disabling module mode should suffix, but
// we'll also turn off GOPROXY just for good measure.
if err := os.Setenv("GO111MODULE", "off"); err != nil {
log.Fatal(err)
}
if err := os.Setenv("GOPROXY", "off"); err != nil {
log.Fatal(err)
}
}
var updateFlag = flag.Bool("update", false, "Update the golden files.")
type query struct {
@ -223,11 +210,6 @@ func doQuery(out io.Writer, q *query, json bool) {
}
func TestGuru(t *testing.T) {
if testing.Short() {
// These tests are super slow.
// TODO: make a lighter version of the tests for short mode?
t.Skipf("skipping in short mode")
}
switch runtime.GOOS {
case "android":
t.Skipf("skipping test on %q (no testdata dir)", runtime.GOOS)
@ -236,9 +218,10 @@ func TestGuru(t *testing.T) {
}
for _, filename := range []string{
"testdata/src/alias/alias.go",
"testdata/src/alias/alias.go", // iff guru.HasAlias (go1.9)
"testdata/src/calls/main.go",
"testdata/src/describe/main.go",
"testdata/src/describe/main19.go", // iff go1.9
"testdata/src/freevars/main.go",
"testdata/src/implements/main.go",
"testdata/src/implements-methods/main.go",
@ -255,6 +238,7 @@ func TestGuru(t *testing.T) {
"testdata/src/calls-json/main.go",
"testdata/src/peers-json/main.go",
"testdata/src/definition-json/main.go",
"testdata/src/definition-json/main19.go",
"testdata/src/describe-json/main.go",
"testdata/src/implements-json/main.go",
"testdata/src/implements-methods-json/main.go",
@ -262,22 +246,28 @@ func TestGuru(t *testing.T) {
"testdata/src/referrers-json/main.go",
"testdata/src/what-json/main.go",
} {
filename := filename
name := strings.Split(filename, "/")[2]
t.Run(name, func(t *testing.T) {
t.Parallel()
if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
// Disable this test on plan9 since it expects a particular
// wording for a "no such file or directory" error.
t.Skip()
continue
}
if filename == "testdata/src/alias/alias.go" && !guru.HasAlias {
continue
}
if strings.HasSuffix(filename, "19.go") && !contains(build.Default.ReleaseTags, "go1.9") {
// TODO(adonovan): recombine the 'describe' and 'definition'
// tests once we drop support for go1.8.
continue
}
json := strings.Contains(filename, "-json/")
queries := parseQueries(t, filename)
golden := filename + "lden"
got := filename + "t"
gotfh, err := os.Create(got)
if err != nil {
t.Fatalf("Create(%s) failed: %s", got, err)
t.Errorf("Create(%s) failed: %s", got, err)
continue
}
defer os.Remove(got)
defer gotfh.Close()
@ -310,10 +300,18 @@ func TestGuru(t *testing.T) {
}
}
}
})
}
}
func contains(haystack []string, needle string) bool {
for _, x := range haystack {
if needle == x {
return true
}
}
return false
}
func TestIssue14684(t *testing.T) {
var buildContext = build.Default
buildContext.GOPATH = "testdata"

View File

@ -19,7 +19,7 @@ import (
"golang.org/x/tools/refactor/importgraph"
)
// The implements function displays the "implements" relation as it pertains to the
// Implements displays the "implements" relation as it pertains to the
// selected type.
// If the selection is a method, 'implements' displays
// the corresponding methods of the types that would have been reported

View File

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

View File

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

View File

@ -196,7 +196,6 @@ type DescribeValue struct {
Type string `json:"type"` // type of the expression
Value string `json:"value,omitempty"` // value of the expression, if constant
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
TypesPos []Definition `json:"typespos,omitempty"` // location of the named types, that type consist of
}
type DescribeMethod struct {

View File

@ -9,7 +9,6 @@ package definition
import (
"lib"
lib2 "lib"
"nosuchpkg"
)
func main() {
@ -28,7 +27,6 @@ func main() {
var _ lib.Const // @definition qualified-const "Const"
var _ lib2.Type // @definition qualified-type-renaming "Type"
var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch"
var _ nosuchpkg.T // @definition qualified-nopkg "nosuchpkg"
var u U
print(u.field) // @definition select-field "field"

View File

@ -11,17 +11,17 @@ Error: no object for identifier
}
-------- @definition lexical-func --------
{
"objpos": "$GOPATH/src/definition-json/main.go:38:6",
"objpos": "$GOPATH/src/definition-json/main.go:36:6",
"desc": "func f"
}
-------- @definition lexical-var --------
{
"objpos": "$GOPATH/src/definition-json/main.go:19:6",
"objpos": "$GOPATH/src/definition-json/main.go:18:6",
"desc": "var x"
}
-------- @definition lexical-shadowing --------
{
"objpos": "$GOPATH/src/definition-json/main.go:22:5",
"objpos": "$GOPATH/src/definition-json/main.go:21:5",
"desc": "var x"
}
-------- @definition qualified-type --------
@ -52,19 +52,14 @@ Error: no object for identifier
-------- @definition qualified-nomember --------
Error: couldn't find declaration of Nonesuch in "lib"
-------- @definition qualified-nopkg --------
{
"objpos": "testdata/src/definition-json/main.go:12:2",
"desc": "package nosuchpkg"
}
-------- @definition select-field --------
{
"objpos": "testdata/src/definition-json/main.go:40:16",
"objpos": "testdata/src/definition-json/main.go:38:16",
"desc": "field field int"
}
-------- @definition select-method --------
{
"objpos": "testdata/src/definition-json/main.go:42:10",
"objpos": "testdata/src/definition-json/main.go:40:10",
"desc": "func (T).method()"
}
-------- @definition embedded-other-file --------
@ -90,6 +85,6 @@ Error: int is built in
}
-------- @definition embedded-same-file --------
{
"objpos": "$GOPATH/src/definition-json/main.go:40:6",
"objpos": "$GOPATH/src/definition-json/main.go:38:6",
"desc": "type T"
}

View File

@ -0,0 +1,5 @@
package definition
import "nosuchpkg"
var _ nosuchpkg.T // @definition qualified-nopkg "nosuchpkg"

View File

@ -0,0 +1,5 @@
-------- @definition qualified-nopkg --------
{
"objpos": "testdata/src/definition-json/main19.go:3:8",
"desc": "package nosuchpkg"
}

View File

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

View File

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

View File

@ -8,14 +8,9 @@ package describe // @describe pkgdecl "describe"
import (
"lib"
"nosuchpkg" // @describe badimport1 "nosuchpkg"
nosuchpkg2 "nosuchpkg" // @describe badimport2 "nosuchpkg2"
_ "unsafe" // @describe unsafe "unsafe"
)
var _ nosuchpkg.T
var _ nosuchpkg2.T
type cake float64 // @describe type-ref-builtin "float64"
const c = iota // @describe const-ref-iota "iota"
@ -36,7 +31,6 @@ func main() { // @describe func-def-main "main"
var i I // @describe type-I "I"
_ = d.f // @describe func-ref-d.f "d.f"
_ = i.f // @describe func-ref-i.f "i.f"
var slice []D // @describe slice-of-D "slice"
var dptr *D // @describe ptr-with-nonptr-methods "dptr"
_ = dptr
@ -91,11 +85,6 @@ func main() { // @describe func-def-main "main"
var _ lib.Outer // @describe lib-outer "Outer"
var mmm map[C]D // @describe var-map-of-C-D "mmm"
d := newD().ThirdField // @describe field-access "ThirdField"
astCopy := ast
unknown() // @describe call-unknown "\\("
}
@ -107,10 +96,7 @@ type C int
type D struct {
Field int
AnotherField string
ThirdField C
}
func (c *C) f() {}
func (d D) f() {}
func newD() D { return D{} }

View File

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

View File

@ -0,0 +1,13 @@
package describe
// The behavior of "describe" on a non-existent import changed
// when go/types started returning fake packages, so this test
// is executed only under go1.9.
import (
"nosuchpkg" // @describe badimport1 "nosuchpkg"
nosuchpkg2 "nosuchpkg" // @describe badimport2 "nosuchpkg2"
)
var _ nosuchpkg.T
var _ nosuchpkg2.T

View File

@ -0,0 +1,6 @@
-------- @describe badimport1 --------
import of package "nosuchpkg"
-------- @describe badimport2 --------
reference to package "nosuchpkg"

View File

@ -6,35 +6,35 @@
"package": "definition-json",
"refs": [
{
"pos": "testdata/src/definition-json/main.go:19:8",
"pos": "testdata/src/definition-json/main.go:18:8",
"text": "\tvar x lib.T // @definition lexical-pkgname \"lib\""
},
{
"pos": "testdata/src/definition-json/main.go:25:8",
"pos": "testdata/src/definition-json/main.go:24:8",
"text": "\tvar _ lib.Type // @definition qualified-type \"Type\""
},
{
"pos": "testdata/src/definition-json/main.go:26:8",
"pos": "testdata/src/definition-json/main.go:25:8",
"text": "\tvar _ lib.Func // @definition qualified-func \"Func\""
},
{
"pos": "testdata/src/definition-json/main.go:27:8",
"pos": "testdata/src/definition-json/main.go:26:8",
"text": "\tvar _ lib.Var // @definition qualified-var \"Var\""
},
{
"pos": "testdata/src/definition-json/main.go:28:8",
"pos": "testdata/src/definition-json/main.go:27:8",
"text": "\tvar _ lib.Const // @definition qualified-const \"Const\""
},
{
"pos": "testdata/src/definition-json/main.go:29:8",
"pos": "testdata/src/definition-json/main.go:28:8",
"text": "\tvar _ lib2.Type // @definition qualified-type-renaming \"Type\""
},
{
"pos": "testdata/src/definition-json/main.go:30:8",
"pos": "testdata/src/definition-json/main.go:29:8",
"text": "\tvar _ lib.Nonesuch // @definition qualified-nomember \"Nonesuch\""
},
{
"pos": "testdata/src/definition-json/main.go:63:2",
"pos": "testdata/src/definition-json/main.go:61:2",
"text": "\tlib.Type // @definition embedded-other-pkg \"Type\""
}
]
@ -43,7 +43,7 @@
"package": "describe",
"refs": [
{
"pos": "testdata/src/describe/main.go:92:8",
"pos": "testdata/src/describe/main.go:86:8",
"text": "\tvar _ lib.Outer // @describe lib-outer \"Outer\""
}
]

View File

@ -5,6 +5,4 @@ import "lib"
func _() {
// This reference should be found by the ref-method query.
_ = (lib.Type).Method // ref from internal test package
_ = notexported
}

View File

@ -25,8 +25,6 @@ func main() {
s2.f = 1
}
var notexported int // @referrers unexported-from-test "notexported"
// Test //line directives:
type U int // @referrers ref-type-U "U"

View File

@ -33,7 +33,7 @@ type _ lib.T
var _ lib.Var // @what pkg "lib"
-------- @referrers ref-method --------
references to func (lib.Type).Method(x *int) *int
references to func (Type).Method(x *int) *int
_ = (lib.Type).Method // ref from external test package
_ = (lib.Type).Method // ref from internal test package
_ = v.Method
@ -54,11 +54,7 @@ references to field f int
_ = s{}.f // @referrers ref-field "f"
s2.f = 1
-------- @referrers unexported-from-test --------
references to var notexported int
_ = notexported
-------- @referrers ref-type-U --------
references to type U int
open testdata/src/referrers/nosuchfile.y: no such file or directory (+ 1 more refs in this file)
open nosuchfile.y: no such file or directory (+ 1 more refs in this file)

View File

@ -10,7 +10,6 @@ import (
"go/build"
"go/token"
"os"
"path"
"path/filepath"
"sort"
"strings"
@ -203,7 +202,7 @@ func guessImportPath(filename string, buildContext *build.Context) (srcdir, impo
if d >= 0 && d < minD {
minD = d
srcdir = gopathDir
importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
importPath = strings.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:], string(os.PathSeparator))
}
}
if srcdir == "" {

View File

@ -0,0 +1 @@
BasedOnStyle: Google

1
cmd/heapview/client/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/node_modules/

View File

@ -0,0 +1,45 @@
# Go Heap Viewer Client
This directory contains the client Typescript code for the Go
heap viewer.
## Typescript Tooling
Below are instructions for downloading tooling and files to
help make the development process more convenient. These tools
are not required for contributing or running the heap viewer-
they are just meant as development aids.
## Node and NPM
We use npm to manage the dependencies for these tools. There are
a couple of ways of installing npm on your system, but we recommend
using nvm.
Run the following command to install nvm:
[shell]$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.3/install.sh | bash
or see the instructions on [the nvm github page](github.com/creationix/nvm)
for alternative methods. This will put the nvm tool in your home directory
and edit your path to add nvm, node and other tools you install using them.
Once nvm is installed, use
[shell]$ nvm install node
then
[shell]$ nvm use node
to install node.js.
Once node is installed, you can install typescript using
[shell]$ npm install -g typescript
Finally, import type definitions into this project by running
[shell]$ npm install
in this directory. They will be imported into the node_packages directory
and be automatically available to the Typescript compiler.

195
cmd/heapview/client/main.ts Normal file
View File

@ -0,0 +1,195 @@
// 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.
/**
* An enum of types of actions that might be requested
* by the app.
*/
enum Action {
TOGGLE_SIDEBAR, // Toggle the sidebar.
NAVIGATE_ABOUT, // Go to the about page.
}
const TITLE = 'Go Heap Viewer';
/**
* A type of event that signals to the AppElement controller
* that something shoud be done. For the most part, the structure
* of the app will be that elements' state will mostly be controlled
* by parent elements. Elements will issue actions that the AppElement
* will handle, and the app will be re-rendered down the DOM
* hierarchy.
*/
class ActionEvent extends Event {
static readonly EVENT_TYPE = 'action-event'
constructor(public readonly action: Action) { super(ActionEvent.EVENT_TYPE); }
}
/**
* A hamburger menu element. Triggers a TOGGLE_SIDE action to toggle the
* sidebar.
*/
export class HamburgerElement extends HTMLElement {
static readonly NAME = 'heap-hamburger';
createdCallback() {
this.appendChild(document.createTextNode('☰'));
this.onclick =
() => { this.dispatchEvent(new ActionEvent(Action.TOGGLE_SIDEBAR)) };
}
}
document.registerElement(HamburgerElement.NAME, HamburgerElement);
/**
* A heading for the page with a hamburger menu and a title.
*/
export class HeadingElement extends HTMLElement {
static readonly NAME = 'heap-heading';
createdCallback() {
this.style.display = 'block';
this.style.backgroundColor = '#2196F3';
this.style.webkitUserSelect = 'none';
this.style.cursor = 'default';
this.style.color = '#FFFFFF';
this.style.padding = '10px';
const div = document.createElement('div');
div.style.margin = '0px';
div.style.fontSize = '2em';
div.appendChild(document.createElement(HamburgerElement.NAME));
div.appendChild(document.createTextNode(' ' + TITLE));
this.appendChild(div);
}
}
document.registerElement(HeadingElement.NAME, HeadingElement);
/**
* A sidebar that has navigation for the app.
*/
export class SidebarElement extends HTMLElement {
static readonly NAME = 'heap-sidebar';
createdCallback() {
this.style.display = 'none';
this.style.backgroundColor = '#9E9E9E';
this.style.width = '15em';
const aboutButton = document.createElement('button');
aboutButton.innerText = 'about';
aboutButton.onclick =
() => { this.dispatchEvent(new ActionEvent(Action.NAVIGATE_ABOUT)) };
this.appendChild(aboutButton);
}
toggle() {
this.style.display = this.style.display === 'none' ? 'block' : 'none';
}
}
document.registerElement(SidebarElement.NAME, SidebarElement);
/**
* A Container for the main content in the app.
* TODO(matloob): Implement main content.
*/
export class MainContentElement extends HTMLElement {
static readonly NAME = 'heap-container';
attachedCallback() {
this.style.backgroundColor = '#E0E0E0';
this.style.height = '100%';
this.style.flex = '1';
}
}
document.registerElement(MainContentElement.NAME, MainContentElement);
/**
* A container and controller for the whole app.
* Contains the heading, side drawer and main panel.
*/
class AppElement extends HTMLElement {
static readonly NAME = 'heap-app';
private sidebar: SidebarElement;
private mainContent: MainContentElement;
attachedCallback() {
document.title = TITLE;
this.addEventListener(
ActionEvent.EVENT_TYPE, e => this.handleAction(e as ActionEvent),
/* capture */ true);
this.render();
}
render() {
this.style.display = 'block';
this.style.height = '100vh';
this.style.width = '100vw';
this.appendChild(document.createElement(HeadingElement.NAME));
const bodyDiv = document.createElement('div');
bodyDiv.style.height = '100%';
bodyDiv.style.display = 'flex';
this.sidebar =
document.createElement(SidebarElement.NAME) as SidebarElement;
bodyDiv.appendChild(this.sidebar);
this.mainContent =
document.createElement(MainContentElement.NAME) as MainContentElement;
bodyDiv.appendChild(this.mainContent);
this.appendChild(bodyDiv);
this.renderRoute();
}
renderRoute() {
this.mainContent.innerHTML = ''
switch (window.location.pathname) {
case '/about':
this.mainContent.appendChild(
document.createElement(AboutPageElement.NAME));
break;
}
}
handleAction(event: ActionEvent) {
switch (event.action) {
case Action.TOGGLE_SIDEBAR:
this.sidebar.toggle();
break;
case Action.NAVIGATE_ABOUT:
window.history.pushState({}, '', '/about');
this.renderRoute();
break;
}
}
}
document.registerElement(AppElement.NAME, AppElement);
/**
* An about page.
*/
class AboutPageElement extends HTMLElement {
static readonly NAME = 'heap-about';
createdCallback() { this.textContent = TITLE; }
}
document.registerElement(AboutPageElement.NAME, AboutPageElement);
/**
* Resets body's margin and padding, and sets font.
*/
function clearStyle(document: Document) {
const styleElement = document.createElement('style') as HTMLStyleElement;
document.head.appendChild(styleElement);
const styleSheet = styleElement.sheet as CSSStyleSheet;
styleSheet.insertRule(
'* {font-family: Roboto,Helvetica; box-sizing: border-box}', 0);
styleSheet.insertRule('body {margin: 0px; padding:0px}', 0);
}
export function main() {
clearStyle(document);
document.body.appendChild(document.createElement(AppElement.NAME));
}

View File

@ -0,0 +1,29 @@
// 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.
import {HamburgerElement, HeadingElement, SidebarElement, main} from './main';
describe('main', () => {
it('sets the document\'s title', () => {
main();
expect(document.title).toBe('Go Heap Viewer');
});
it('has a heading', () => {
main();
expect(document.querySelector(HeadingElement.NAME)).toBeDefined();
});
it('has a sidebar', () => {
main();
const hamburger = document.querySelector(HamburgerElement.NAME);
const sidebar =
document.querySelector(SidebarElement.NAME) as SidebarElement;
expect(sidebar.style.display).toBe('none');
// Click on the hamburger. Sidebar should then be visible.
hamburger.dispatchEvent(new Event('click'));
expect(sidebar.style.display).toBe('block');
})
});

View File

@ -0,0 +1,35 @@
{
"//": [
"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.",
"This file exists to help import typescript typings",
"for web features used in this project. Neither the",
"typings nor node or npm are required to do development",
"on the code in this project.",
"If you do have npm installed, use the `npm i` command",
"in this directory to install the typings."
],
"private": true,
"name": "@golangtools/heapview",
"version": "0.0.0",
"devDependencies": {
"@types/webcomponents.js": "latest",
"@types/whatwg-fetch": "latest",
"@types/jasmine": "latest",
"jasmine-core": "latest",
"karma": "latest",
"karma-jasmine": "latest",
"karma-chrome-launcher": "latest",
"clang-format": "latest"
},
"scripts": {
"test": "karma start testing/karma.conf.js",
"format": "find . | grep '\\(test_main\\.js\\|\\.ts\\)$' | xargs clang-format -i",
"lint": "tslint --project ."
}
}

View File

@ -0,0 +1,22 @@
// 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.
module.exports = config => {
config.set({
frameworks: ['jasmine'],
basePath: '../../../..',
files: [
'third_party/webcomponents/customelements.js',
'third_party/typescript/typescript.js',
'third_party/moduleloader/moduleloader.js',
'cmd/heapview/client/testing/test_main.js',
{pattern: 'cmd/heapview/client/**/*.ts', included: false},
],
browsers: ['Chrome'],
plugins: [
'karma-jasmine',
'karma-chrome-launcher'
],
})
}

View File

@ -0,0 +1,32 @@
// 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.
// Configure module loader.
System.transpiler = 'typescript'
System.typescriptOptions = {
target: ts.ScriptTarget.ES2015
};
System.locate = (load) => load.name + '.ts';
// Determine set of test files.
var tests = [];
for (var file in window.__karma__.files) {
if (window.__karma__.files.hasOwnProperty(file)) {
if (/_test\.ts$/.test(file)) {
tests.push(file.slice(0, -3));
}
}
}
// Steal loaded callback so we can block until we're
// done loading all test modules.
var loadedCallback = window.__karma__.loaded.bind(window.__karma__);
window.__karma__.loaded = () => {};
// Load all test modules, and then call loadedCallback.
var promises = [];
for (var i = 0; i < tests.length; i++) {
promises.push(System.import(tests[i]));
}
Promise.all(promises).then(loadedCallback);

View File

@ -0,0 +1,16 @@
// 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.
// This file contains configuration for the Typescript
// compiler if you're running it locally for typechecking
// and other tooling. The Typescript compiler is
// not necessary to do development on this project.
{
"compilerOptions": {
"noEmit": true,
"strictNullChecks": true,
"target": "es2015"
}
}

View File

@ -0,0 +1,40 @@
// 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.
// This tslint file is based on a configuration used at
// Google.
{
"rules": {
"class-name": true,
"forin": true,
"interface-name": [true, "never-prefix"],
"jsdoc-format": true,
"label-position": true,
"label-undefined": true,
"new-parens": true,
"no-angle-bracket-type-assertion": true,
"no-construct": true,
"no-debugger": true,
"no-namespace": [true, "allow-declarations"],
"no-reference": true,
"no-require-imports": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"semicolon": [true, "always"],
"switch-default": true,
"triple-equals": [true, "allow-null-check"],
"use-isnan": true,
"variable-name": [
true,
"check-format",
"ban-keywords",
"allow-leading-underscore",
"allow-trailing-underscore",
"allow-pascal-case"
]
}
}

View File

@ -0,0 +1,145 @@
// 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 darwin linux
package core
import (
"errors"
"fmt"
"io"
"os"
"syscall"
)
var errMmapClosed = errors.New("mmap: closed")
// mmapFile wraps a memory-mapped file.
type mmapFile struct {
data []byte
pos uint64
writable bool
}
// mmapOpen opens the named file for reading.
// If writable is true, the file is also open for writing.
func mmapOpen(filename string, writable bool) (*mmapFile, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
st, err := f.Stat()
if err != nil {
return nil, err
}
size := st.Size()
if size == 0 {
return &mmapFile{data: []byte{}}, nil
}
if size < 0 {
return nil, fmt.Errorf("mmap: file %q has negative size: %d", filename, size)
}
if size != int64(int(size)) {
return nil, fmt.Errorf("mmap: file %q is too large", filename)
}
prot := syscall.PROT_READ
if writable {
prot |= syscall.PROT_WRITE
}
data, err := syscall.Mmap(int(f.Fd()), 0, int(size), prot, syscall.MAP_SHARED)
if err != nil {
return nil, err
}
return &mmapFile{data: data, writable: writable}, nil
}
// Size returns the size of the mapped file.
func (f *mmapFile) Size() uint64 {
return uint64(len(f.data))
}
// Pos returns the current file pointer.
func (f *mmapFile) Pos() uint64 {
return f.pos
}
// SeekTo sets the current file pointer relative to the start of the file.
func (f *mmapFile) SeekTo(offset uint64) {
f.pos = offset
}
// Read implements io.Reader.
func (f *mmapFile) Read(p []byte) (int, error) {
if f.data == nil {
return 0, errMmapClosed
}
if f.pos >= f.Size() {
return 0, io.EOF
}
n := copy(p, f.data[f.pos:])
f.pos += uint64(n)
if n < len(p) {
return n, io.EOF
}
return n, nil
}
// ReadByte implements io.ByteReader.
func (f *mmapFile) ReadByte() (byte, error) {
if f.data == nil {
return 0, errMmapClosed
}
if f.pos >= f.Size() {
return 0, io.EOF
}
b := f.data[f.pos]
f.pos++
return b, nil
}
// ReadSlice returns a slice of size n that points directly at the
// underlying mapped file. There is no copying. Fails if it cannot
// read at least n bytes.
func (f *mmapFile) ReadSlice(n uint64) ([]byte, error) {
if f.data == nil {
return nil, errMmapClosed
}
if f.pos+n >= f.Size() {
return nil, io.EOF
}
first := f.pos
f.pos += n
return f.data[first:f.pos:f.pos], nil
}
// ReadSliceAt is like ReadSlice, but reads from a specific offset.
// The file pointer is not used or advanced.
func (f *mmapFile) ReadSliceAt(offset, n uint64) ([]byte, error) {
if f.data == nil {
return nil, errMmapClosed
}
if f.Size() < offset {
return nil, fmt.Errorf("mmap: out-of-bounds ReadSliceAt offset %d, size is %d", offset, f.Size())
}
if offset+n >= f.Size() {
return nil, io.EOF
}
end := offset + n
return f.data[offset:end:end], nil
}
// Close closes the file.
func (f *mmapFile) Close() error {
if f.data == nil {
return nil
}
err := syscall.Munmap(f.data)
f.data = nil
f.pos = 0
return err
}

View File

@ -0,0 +1,14 @@
// 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 !darwin,!linux
package core
// TODO(matloob): perhaps use the more portable golang.org/x/exp/mmap
// instead of the mmap code in mmapfile.go.
type mmapFile struct{}
func (m *mmapFile) Close() error { return nil }

View File

@ -0,0 +1,308 @@
// 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.
// Package core provides functions for reading core dumps
// and examining their contained heaps.
package core
import (
"bytes"
"encoding/binary"
"fmt"
"runtime"
"sort"
)
// RawDump provides raw access to the heap records in a core file.
// The raw records in this file are described by other structs named Raw{*}.
// All []byte slices are direct references to the underlying mmap'd file.
// These references will become invalid as soon as the RawDump is closed.
type RawDump struct {
Params *RawParams
MemStats *runtime.MemStats
HeapObjects []RawSegment // heap objects sorted by Addr, low-to-high
GlobalSegments []RawSegment // data, bss, and noptrbss segments
OSThreads []*RawOSThread
Goroutines []*RawGoroutine
StackFrames []*RawStackFrame
OtherRoots []*RawOtherRoot
Finalizers []*RawFinalizer
Defers []*RawDefer
Panics []*RawPanic
TypeFromItab map[uint64]uint64 // map from itab address to the type address that itab represents
TypeFromAddr map[uint64]*RawType // map from RawType.Addr to RawType
MemProfMap map[uint64]*RawMemProfEntry
AllocSamples []*RawAllocSample
fmap *mmapFile
}
// RawParams holds metadata about the program that generated the dump.
type RawParams struct {
// Info about the memory space
ByteOrder binary.ByteOrder // byte order of all memory in this dump
PtrSize uint64 // in bytes
HeapStart uint64 // heap start address
HeapEnd uint64 // heap end address (this is the last byte in the heap + 1)
// Info about the program that generated this heapdump
GoArch string // GOARCH of the runtime library that generated this dump
GoExperiment string // GOEXPERIMENT of the toolchain that build the runtime library
NCPU uint64 // number of physical cpus available to the program
}
// RawSegment represents a segment of memory.
type RawSegment struct {
Addr uint64 // base address of the segment
Data []byte // data for this segment
PtrFields RawPtrFields // offsets of ptr fields within this segment
}
// RawPtrFields represents a pointer field.
type RawPtrFields struct {
encoded []byte // list of uvarint-encoded offsets, or nil if none
startOff, endOff uint64 // decoded offsets are translated and clipped to [startOff,endOff)
}
// RawOSThread represents an OS thread.
type RawOSThread struct {
MAddr uint64 // address of the OS thread descriptor (M)
GoID uint64 // go's internal ID for the thread
ProcID uint64 // kernel's ID for the thread
}
// RawGoroutine represents a goroutine structure.
type RawGoroutine struct {
GAddr uint64 // address of the goroutine descriptor
SP uint64 // current stack pointer (lowest address in the currently running frame)
GoID uint64 // goroutine ID
GoPC uint64 // PC of the go statement that created this goroutine
Status uint64
IsSystem bool // true if started by the system
IsBackground bool // always false in go1.7
WaitSince uint64 // time the goroutine started waiting, in nanoseconds since the Unix epoch
WaitReason string
CtxtAddr uint64 // address of the scheduling ctxt
MAddr uint64 // address of the OS thread descriptor (M)
TopDeferAddr uint64 // address of the top defer record
TopPanicAddr uint64 // address of the top panic record
}
// RawStackFrame represents a stack frame.
type RawStackFrame struct {
Name string
Depth uint64 // 0 = bottom of stack (currently running frame)
CalleeSP uint64 // stack pointer of the child frame (or 0 for the bottom-most frame)
EntryPC uint64 // entry PC for the function
PC uint64 // current PC being executed
NextPC uint64 // for callers, where the function resumes (if anywhere) after the callee is done
Segment RawSegment // local vars (Segment.Addr is the stack pointer, i.e., lowest address in the frame)
}
// RawOtherRoot represents the other roots not in RawDump's other fields.
type RawOtherRoot struct {
Description string
Addr uint64 // address pointed to by this root
}
// RawFinalizer represents a finalizer.
type RawFinalizer struct {
IsQueued bool // if true, the object is unreachable and the finalizer is ready to run
ObjAddr uint64 // address of the object to finalize
ObjTypeAddr uint64 // address of the descriptor for typeof(obj)
FnAddr uint64 // function to be run (a FuncVal*)
FnArgTypeAddr uint64 // address of the descriptor for the type of the function argument
FnPC uint64 // PC of finalizer entry point
}
// RawDefer represents a defer.
type RawDefer struct {
Addr uint64 // address of the defer record
GAddr uint64 // address of the containing goroutine's descriptor
ArgP uint64 // stack pointer giving the args for defer (TODO: is this right?)
PC uint64 // PC of the defer instruction
FnAddr uint64 // function to be run (a FuncVal*)
FnPC uint64 // PC of the defered function's entry point
LinkAddr uint64 // address of the next defer record in this chain
}
// RawPanic represents a panic.
type RawPanic struct {
Addr uint64 // address of the panic record
GAddr uint64 // address of the containing goroutine's descriptor
ArgTypeAddr uint64 // type of the panic arg
ArgAddr uint64 // address of the panic arg
DeferAddr uint64 // address of the defer record that is currently running
LinkAddr uint64 // address of the next panic record in this chain
}
// RawType repesents the Go runtime's representation of a type.
type RawType struct {
Addr uint64 // address of the type descriptor
Size uint64 // in bytes
Name string // not necessarily unique
// If true, this type is equivalent to a single pointer, so ifaces can store
// this type directly in the data field (without indirection).
DirectIFace bool
}
// RawMemProfEntry represents a memory profiler entry.
type RawMemProfEntry struct {
Size uint64 // size of the allocated object
NumAllocs uint64 // number of allocations
NumFrees uint64 // number of frees
Stacks []RawMemProfFrame // call stacks
}
// RawMemProfFrame represents a memory profiler frame.
type RawMemProfFrame struct {
Func []byte // string left as []byte reference to save memory
File []byte // string left as []byte reference to save memory
Line uint64
}
// RawAllocSample represents a memory profiler allocation sample.
type RawAllocSample struct {
Addr uint64 // address of object
Prof *RawMemProfEntry // record of allocation site
}
// Close closes the file.
func (r *RawDump) Close() error {
return r.fmap.Close()
}
// FindSegment returns the segment that contains the given address, or
// nil of no segment contains the address.
func (r *RawDump) FindSegment(addr uint64) *RawSegment {
// Binary search for an upper-bound heap object, then check
// if the previous object contains addr.
k := sort.Search(len(r.HeapObjects), func(k int) bool {
return addr < r.HeapObjects[k].Addr
})
k--
if k >= 0 && r.HeapObjects[k].Contains(addr) {
return &r.HeapObjects[k]
}
// Check all global segments.
for k := range r.GlobalSegments {
if r.GlobalSegments[k].Contains(addr) {
return &r.GlobalSegments[k]
}
}
// NB: Stack-local vars are technically allocated in the heap, since stack frames are
// allocated in the heap space, however, stack frames don't show up in r.HeapObjects.
for _, f := range r.StackFrames {
if f.Segment.Contains(addr) {
return &f.Segment
}
}
return nil
}
// Contains returns true if the segment contains the given address.
func (r RawSegment) Contains(addr uint64) bool {
return r.Addr <= addr && addr < r.Addr+r.Size()
}
// ContainsRange returns true if the segment contains the range [addr, addr+size).
func (r RawSegment) ContainsRange(addr, size uint64) bool {
if !r.Contains(addr) {
return false
}
if size > 0 && !r.Contains(addr+size-1) {
return false
}
return true
}
// Size returns the size of the segment in bytes.
func (r RawSegment) Size() uint64 {
return uint64(len(r.Data))
}
// Slice takes a slice of the given segment. Panics if [offset,offset+size)
// is out-of-bounds. The resulting RawSegment.PtrOffsets will clipped and
// translated into the new segment.
func (r RawSegment) Slice(offset, size uint64) *RawSegment {
if offset+size > uint64(len(r.Data)) {
panic(fmt.Errorf("slice(%d,%d) out-of-bounds of segment @%x sz=%d", offset, size, r.Addr, len(r.Data)))
}
return &RawSegment{
Addr: r.Addr + offset,
Data: r.Data[offset : offset+size : offset+size],
PtrFields: RawPtrFields{
encoded: r.PtrFields.encoded,
startOff: r.PtrFields.startOff + offset,
endOff: r.PtrFields.startOff + offset + size,
},
}
}
// Offsets decodes the list of ptr field offsets.
func (r RawPtrFields) Offsets() []uint64 {
if r.encoded == nil {
return nil
}
// NB: This should never fail since we already decoded the varints once
// when parsing the file originally. Hence we panic on failure.
reader := bytes.NewReader(r.encoded)
readUint64 := func() uint64 {
x, err := binary.ReadUvarint(reader)
if err != nil {
panic(fmt.Errorf("unexpected failure decoding uvarint: %v", err))
}
return x
}
var out []uint64
for {
k := readUint64()
switch k {
case 0: // end
return out
case 1: // ptr
x := readUint64()
if r.startOff <= x && x < r.endOff {
out = append(out, x-r.startOff)
}
default:
panic(fmt.Errorf("unexpected FieldKind %d", k))
}
}
}
// ReadPtr decodes a ptr from the given byte slice.
func (r *RawParams) ReadPtr(b []byte) uint64 {
switch r.PtrSize {
case 4:
return uint64(r.ByteOrder.Uint32(b))
case 8:
return r.ByteOrder.Uint64(b)
default:
panic(fmt.Errorf("unsupported PtrSize=%d", r.PtrSize))
}
}
// WritePtr encodes a ptr into the given byte slice.
func (r *RawParams) WritePtr(b []byte, addr uint64) {
switch r.PtrSize {
case 4:
r.ByteOrder.PutUint32(b, uint32(addr))
case 8:
r.ByteOrder.PutUint64(b, addr)
default:
panic(fmt.Errorf("unsupported PtrSize=%d", r.PtrSize))
}
}

83
cmd/heapview/main.go Normal file
View File

@ -0,0 +1,83 @@
// 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.
// heapview is a tool for viewing Go heap dumps.
package main
import (
"flag"
"fmt"
"go/build"
"io"
"log"
"net/http"
"os"
"path/filepath"
)
var host = flag.String("host", "", "host addr to listen on")
var port = flag.Int("port", 8080, "service port")
var index = `<!DOCTYPE html>
<script src="js/customelements.js"></script>
<script src="js/typescript.js"></script>
<script src="js/moduleloader.js"></script>
<script>
System.transpiler = 'typescript';
System.typescriptOptions = {target: ts.ScriptTarget.ES2015};
System.locate = (load) => load.name + '.ts';
</script>
<script type="module">
import {main} from './client/main';
main();
</script>
`
func toolsDir() string {
p, err := build.Import("golang.org/x/tools", "", build.FindOnly)
if err != nil {
log.Println("error: can't find client files:", err)
os.Exit(1)
}
return p.Dir
}
var parseFlags = func() {
flag.Parse()
}
var addHandlers = func() {
toolsDir := toolsDir()
// Directly serve typescript code in client directory for development.
http.Handle("/client/", http.StripPrefix("/client",
http.FileServer(http.Dir(filepath.Join(toolsDir, "cmd/heapview/client")))))
// Serve typescript.js and moduleloader.js for development.
http.HandleFunc("/js/typescript.js", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, filepath.Join(toolsDir, "third_party/typescript/typescript.js"))
})
http.HandleFunc("/js/moduleloader.js", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, filepath.Join(toolsDir, "third_party/moduleloader/moduleloader.js"))
})
http.HandleFunc("/js/customelements.js", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, filepath.Join(toolsDir, "third_party/webcomponents/customelements.js"))
})
// Serve index.html using html string above.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
io.WriteString(w, index)
})
}
var listenAndServe = func() error {
return http.ListenAndServe(fmt.Sprintf("%s:%d", *host, *port), nil)
}
func main() {
parseFlags()
addHandlers()
log.Fatal(listenAndServe())
}

22
cmd/present/appengine.go Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2012 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 (
"mime"
"golang.org/x/tools/present"
)
func init() {
initTemplates("./present/")
present.PlayEnabled = true
initPlayground("./present/", nil)
// App Engine has no /etc/mime.types
mime.AddExtensionType(".svg", "image/svg+xml")
}

View File

@ -13,7 +13,6 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"golang.org/x/tools/present"
)
@ -22,18 +21,19 @@ func init() {
http.HandleFunc("/", dirHandler)
}
// dirHandler serves a directory listing for the requested path, rooted at *contentPath.
// dirHandler serves a directory listing for the requested path, rooted at basePath.
func dirHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/favicon.ico" {
http.NotFound(w, r)
http.Error(w, "not found", 404)
return
}
name := filepath.Join(*contentPath, r.URL.Path)
const base = "."
name := filepath.Join(base, r.URL.Path)
if isDoc(name) {
err := renderDoc(w, name)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
}
return
}
@ -43,12 +43,12 @@ func dirHandler(w http.ResponseWriter, r *http.Request) {
addr = r.RemoteAddr
}
log.Printf("request from %s: %s", addr, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
} else if isDir {
return
}
http.FileServer(http.Dir(*contentPath)).ServeHTTP(w, r)
http.FileServer(http.Dir(base)).ServeHTTP(w, r)
}
func isDoc(path string) bool {
@ -113,7 +113,7 @@ func parse(name string, mode present.ParseMode) (*present.Doc, error) {
return nil, err
}
defer f.Close()
return present.Parse(f, name, mode)
return present.Parse(f, name, 0)
}
// dirList scans the given path and writes a directory listing to w.
@ -138,9 +138,7 @@ func dirList(w io.Writer, name string) (isDir bool, err error) {
if err != nil {
return false, err
}
strippedPath := strings.TrimPrefix(name, filepath.Clean(*contentPath))
strippedPath = strings.TrimPrefix(strippedPath, "/")
d := &dirListData{Path: strippedPath}
d := &dirListData{Path: name}
for _, fi := range fis {
// skip the golang.org directory
if name == "." && fi.Name() == "golang.org" {
@ -148,16 +146,15 @@ func dirList(w io.Writer, name string) (isDir bool, err error) {
}
e := dirEntry{
Name: fi.Name(),
Path: filepath.ToSlash(filepath.Join(strippedPath, fi.Name())),
Path: filepath.ToSlash(filepath.Join(name, fi.Name())),
}
if fi.IsDir() && showDir(e.Name) {
d.Dirs = append(d.Dirs, e)
continue
}
if isDoc(e.Name) {
fn := filepath.ToSlash(filepath.Join(name, fi.Name()))
if p, err := parse(fn, present.TitlesOnly); err != nil {
log.Printf("parse(%q, present.TitlesOnly): %v", fn, err)
if p, err := parse(e.Path, present.TitlesOnly); err != nil {
log.Println(err)
} else {
e.Title = p.Title
}

View File

@ -8,38 +8,43 @@ presents slide and article files from the current directory.
It may be run as a stand-alone command or an App Engine app.
Usage of present:
-base="": base path for slide template and static resources
-http="127.0.0.1:3999": HTTP service address (e.g., '127.0.0.1:3999')
-nacl=false: use Native Client environment playground (prevents non-Go code execution)
-notes=false: enable presenter notes (press 'N' from the browser to display them)
-orighost="": host component of web origin URL (e.g., 'localhost')
-play=true: enable playground (permit execution of arbitrary user code)
The setup of the Go version of NaCl is documented at:
https://golang.org/wiki/NativeClient
To use with App Engine, copy the files in the tools/cmd/present directory to the
root of your application and create an app.yaml file similar to this:
To use with App Engine, copy the tools/cmd/present directory to the root of
your application and create an app.yaml file similar to this:
runtime: go111
application: [application]
version: [version]
runtime: go
api_version: go1
handlers:
- url: /favicon.ico
static_files: static/favicon.ico
upload: static/favicon.ico
static_files: present/static/favicon.ico
upload: present/static/favicon.ico
- url: /static
static_dir: static
static_dir: present/static
application_readable: true
- url: /.*
script: auto
script: _go_app
# nobuild_files is a regexp that identifies which files to not build. It
# is useful for embedding static assets like code snippets and preventing
# them from producing build errors for your project.
nobuild_files: [path regexp for talk materials]
When running on App Engine, content will be served from the ./content/
subdirectory.
Present then can be tested in a local App Engine environment with
GAE_ENV=standard go run .
And deployed using
gcloud app deploy
goapp serve
Input files are named foo.extension, where "extension" defines the format of
the generated output. The supported formats are:
@ -49,4 +54,4 @@ the generated output. The supported formats are:
The present file format is documented by the present package:
http://godoc.org/golang.org/x/tools/present
*/
package main
package main // import "golang.org/x/tools/cmd/present"

View File

@ -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 (
@ -24,9 +26,7 @@ var (
httpAddr = flag.String("http", "127.0.0.1:3999", "HTTP service address (e.g., '127.0.0.1:3999')")
originHost = flag.String("orighost", "", "host component of web origin URL (e.g., 'localhost')")
basePath = flag.String("base", "", "base path for slide template and static resources")
contentPath = flag.String("content", ".", "base path for presentation content")
usePlayground = flag.Bool("use_playground", false, "run code snippets using play.golang.org; if false, run them locally and deliver results by WebSocket transport")
nativeClient = flag.Bool("nacl", false, "use Native Client environment playground (prevents non-Go code execution) when using local WebSocket transport")
nativeClient = flag.Bool("nacl", false, "use Native Client environment playground (prevents non-Go code execution)")
)
func main() {
@ -34,22 +34,6 @@ func main() {
flag.BoolVar(&present.NotesEnabled, "notes", false, "enable presenter notes (press 'N' from the browser to display them)")
flag.Parse()
if os.Getenv("GAE_ENV") == "standard" {
log.Print("Configuring for App Engine Standard")
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
*httpAddr = fmt.Sprintf("0.0.0.0:%s", port)
pwd, err := os.Getwd()
if err != nil {
log.Fatalf("Couldn't get pwd: %v\n", err)
}
*basePath = pwd
*usePlayground = true
*contentPath = "./content/"
}
if *basePath == "" {
p, err := build.Default.Import(basePkg, "", build.FindOnly)
if err != nil {
@ -97,7 +81,7 @@ func main() {
http.Handle("/static/", http.FileServer(http.Dir(*basePath)))
if !ln.Addr().(*net.TCPAddr).IP.IsLoopback() &&
present.PlayEnabled && !*nativeClient && !*usePlayground {
present.PlayEnabled && !*nativeClient {
log.Print(localhostWarning)
}
@ -137,12 +121,11 @@ You may use the -base flag to specify an alternate location.
const localhostWarning = `
WARNING! WARNING! WARNING!
The present server appears to be listening on an address that is not localhost
and is configured to run code snippets locally. Anyone with access to this address
and port will have access to this machine as the user running present.
The present server appears to be listening on an address that is not localhost.
Anyone with access to this address and port will have access to this machine as
the user running present.
To avoid this message, listen on localhost, run with -play=false, or run with
-play_socket=false.
To avoid this message, listen on localhost or run with -play=false.
If you don't understand this message, hit Control-C to terminate this process.

View File

@ -9,21 +9,10 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path/filepath"
"runtime"
"time"
"golang.org/x/tools/godoc/static"
"golang.org/x/tools/playground/socket"
"golang.org/x/tools/present"
// This will register handlers at /compile and /share that will proxy to the
// respective endpoints at play.golang.org. This allows the frontend to call
// these endpoints without needing cross-origin request sharing (CORS).
// Note that this is imported regardless of whether the endpoints are used or
// not (in the case of a local socket connection, they are not called).
_ "golang.org/x/tools/playground"
)
var scripts = []string{"jquery.js", "jquery-ui.js", "playground.js", "play.js"}
@ -52,38 +41,3 @@ func playScript(root, transport string) {
http.ServeContent(w, r, "", modTime, bytes.NewReader(b))
})
}
func initPlayground(basepath string, origin *url.URL) {
if !present.PlayEnabled {
return
}
if *usePlayground {
playScript(basepath, "HTTPTransport")
return
}
if *nativeClient {
// When specifying nativeClient, non-Go code cannot be executed
// because the NaCl setup doesn't support doing so.
socket.RunScripts = false
socket.Environ = func() []string {
if runtime.GOARCH == "amd64" {
return environ("GOOS=nacl", "GOARCH=amd64p32")
}
return environ("GOOS=nacl")
}
}
playScript(basepath, "SocketTransport")
http.Handle("/socket", socket.NewHandler(origin))
}
func playable(c present.Code) bool {
play := present.PlayEnabled && c.Play
// Restrict playable files to only Go source files when using play.golang.org,
// since there is no method to execute shell scripts there.
if *usePlayground {
return play && c.Ext == ".go"
}
return play
}

23
cmd/present/play_http.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build appengine appenginevm
package main
import (
"net/url"
"golang.org/x/tools/present"
_ "golang.org/x/tools/playground"
)
func initPlayground(basepath string, origin *url.URL) {
playScript(basepath, "HTTPTransport")
}
func playable(c present.Code) bool {
return present.PlayEnabled && c.Play && c.Ext == ".go"
}

View File

@ -0,0 +1,36 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine,!appenginevm
package main
import (
"net/http"
"net/url"
"runtime"
"golang.org/x/tools/playground/socket"
"golang.org/x/tools/present"
)
func initPlayground(basepath string, origin *url.URL) {
if present.PlayEnabled {
if *nativeClient {
socket.RunScripts = false
socket.Environ = func() []string {
if runtime.GOARCH == "amd64" {
return environ("GOOS=nacl", "GOARCH=amd64p32")
}
return environ("GOOS=nacl")
}
}
playScript(basepath, "SocketTransport")
http.Handle("/socket", socket.NewHandler(origin))
}
}
func playable(c present.Code) bool {
return present.PlayEnabled && c.Play
}

View File

@ -26,35 +26,12 @@ function toggleNotesWindow() {
initNotes();
};
// Create an unique key for the local storage so we don't mix the
// destSlide of different presentations. For golang.org/issue/24688.
function destSlideKey() {
var key = '';
if (notesWindow) {
var slides = notesWindow.document.getElementById('presenter-slides');
key = slides.src.split('#')[0];
} else {
key = window.location.href.split('#')[0];
}
return 'destSlide:' + key;
}
function initNotes() {
notesWindow = window.open('', '', 'width=1000,height=700');
var w = notesWindow;
var slidesUrl = window.location.href;
// Hack to apply css. Requires existing html on notesWindow.
w.document.write("<div style='display:none;'></div>");
w.document.title = window.document.title;
var slides = w.document.createElement('iframe');
slides.id = 'presenter-slides';
slides.src = slidesUrl;
w.document.body.appendChild(slides);
var curSlide = parseInt(localStorage.getItem(destSlideKey()), 10);
var curSlide = parseInt(localStorage.getItem('destSlide'), 10);
var formattedNotes = '';
var section = sections[curSlide - 1];
// curSlide is 0 when initialized from the first page of slides.
@ -65,6 +42,15 @@ function initNotes() {
formattedNotes = formatNotes(titleNotes);
}
// Hack to apply css. Requires existing html on notesWindow.
w.document.write("<div style='display:none;'></div>");
w.document.title = window.document.title;
var slides = w.document.createElement('iframe');
slides.id = 'presenter-slides';
slides.src = slidesUrl;
w.document.body.appendChild(slides);
// setTimeout needed for Firefox
setTimeout(function() {
slides.focus();
@ -107,7 +93,7 @@ function updateNotes() {
// When triggered from parent window, notesWindow is null
// The storage event listener on notesWindow will update notes
if (!notesWindow) return;
var destSlide = parseInt(localStorage.getItem(destSlideKey()), 10);
var destSlide = parseInt(localStorage.getItem('destSlide'), 10);
var section = sections[destSlide - 1];
var el = notesWindow.document.getElementById('presenter-notes');

View File

@ -212,7 +212,7 @@ function prevSlide() {
updateSlides();
}
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
};
function nextSlide() {
@ -223,7 +223,7 @@ function nextSlide() {
updateSlides();
}
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
};
/* Slide events */
@ -442,7 +442,7 @@ function handleBodyKeyDown(event) {
};
function scaleSmallViewports() {
var el = document.querySelector('section.slides');
var el = document.querySelector('.slides');
var transform = '';
var sWidthPx = 1250;
var sHeightPx = 750;
@ -468,20 +468,6 @@ function addEventListeners() {
scaleSmallViewports();
}, 50);
});
// Force reset transform property of section.slides when printing page.
// Use both onbeforeprint and matchMedia for compatibility with different browsers.
var beforePrint = function() {
var el = document.querySelector('section.slides');
el.style.transform = '';
};
window.onbeforeprint = beforePrint;
if (window.matchMedia) {
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener(function(mql) {
if (mql.matches) beforePrint();
});
}
}
/* Initialization */
@ -602,7 +588,7 @@ function setupNotesSync() {
setupPlayCodeSync();
setupPlayResizeSync();
localStorage.setItem(destSlideKey(), curSlide);
localStorage.setItem('destSlide', curSlide);
window.addEventListener('storage', updateOtherWindow, false);
}
@ -613,7 +599,7 @@ function updateOtherWindow(e) {
var isRemoveStorageEvent = !e.newValue;
if (isRemoveStorageEvent) return;
var destSlide = localStorage.getItem(destSlideKey());
var destSlide = localStorage.getItem('destSlide');
while (destSlide > curSlide) {
nextSlide();
}

View File

@ -415,14 +415,6 @@ p.link {
margin-left: 20px;
}
.pagenumber {
color: #8c8c8c;
font-size: 75%;
position: absolute;
bottom: 0px;
right: 10px;
}
/* Code */
div.code {
outline: 0px solid transparent;

View File

@ -62,9 +62,8 @@
{{else}}
<h2>{{$s.Title}}</h2>
{{end}}
<span class="pagenumber">{{pagenum $s 1}}</span>
</article>
<!-- end of slide {{$s.Number}} -->
<!-- end of slide {{$i}} -->
{{end}}{{/* of Slide block */}}
<article>

View File

@ -1,141 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macho
import (
"encoding/binary"
"io"
"os"
)
// A FatFile is a Mach-O universal binary that contains at least one architecture.
type FatFile struct {
Magic uint32
Arches []FatArch
closer io.Closer
}
// A FatArchHeader represents a fat header for a specific image architecture.
type FatArchHeader struct {
Cpu Cpu
SubCpu uint32
Offset uint32
Size uint32
Align uint32
}
const fatArchHeaderSize = 5 * 4
// A FatArch is a Mach-O File inside a FatFile.
type FatArch struct {
FatArchHeader
*File
}
// NewFatFile creates a new FatFile for accessing all the Mach-O images in a
// universal binary. The Mach-O binary is expected to start at position 0 in
// the ReaderAt.
func NewFatFile(r io.ReaderAt) (*FatFile, error) {
var ff FatFile
sr := io.NewSectionReader(r, 0, 1<<63-1)
// Read the fat_header struct, which is always in big endian.
// Start with the magic number.
err := binary.Read(sr, binary.BigEndian, &ff.Magic)
if err != nil {
return nil, formatError(0, "error reading magic number, %v", err)
} else if ff.Magic != MagicFat {
// See if this is a Mach-O file via its magic number. The magic
// must be converted to little endian first though.
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], ff.Magic)
leMagic := binary.LittleEndian.Uint32(buf[:])
if leMagic == Magic32 || leMagic == Magic64 {
return nil, formatError(0, "not a fat Mach-O file, leMagic=0x%x", leMagic)
} else {
return nil, formatError(0, "invalid magic number, leMagic=0x%x", leMagic)
}
}
offset := int64(4)
// Read the number of FatArchHeaders that come after the fat_header.
var narch uint32
err = binary.Read(sr, binary.BigEndian, &narch)
if err != nil {
return nil, formatError(offset, "invalid fat_header %v", err)
}
offset += 4
if narch < 1 {
return nil, formatError(offset, "file contains no images, narch=%d", narch)
}
// Combine the Cpu and SubCpu (both uint32) into a uint64 to make sure
// there are not duplicate architectures.
seenArches := make(map[uint64]bool, narch)
// Make sure that all images are for the same MH_ type.
var machoType HdrType
// Following the fat_header comes narch fat_arch structs that index
// Mach-O images further in the file.
ff.Arches = make([]FatArch, narch)
for i := uint32(0); i < narch; i++ {
fa := &ff.Arches[i]
err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader)
if err != nil {
return nil, formatError(offset, "invalid fat_arch header, %v", err)
}
offset += fatArchHeaderSize
fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size))
fa.File, err = NewFile(fr)
if err != nil {
return nil, err
}
// Make sure the architecture for this image is not duplicate.
seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu)
if o, k := seenArches[seenArch]; o || k {
return nil, formatError(offset, "duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu)
}
seenArches[seenArch] = true
// Make sure the Mach-O type matches that of the first image.
if i == 0 {
machoType = HdrType(fa.Type)
} else {
if HdrType(fa.Type) != machoType {
return nil, formatError(offset, "Mach-O type for architecture #%d (type=%#x) does not match first (type=%#x)", i, fa.Type, machoType)
}
}
}
return &ff, nil
}
// OpenFat opens the named file using os.Open and prepares it for use as a Mach-O
// universal binary.
func OpenFat(name string) (*FatFile, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
ff, err := NewFatFile(f)
if err != nil {
f.Close()
return nil, err
}
ff.closer = f
return ff, nil
}
func (ff *FatFile) Close() error {
var err error
if ff.closer != nil {
err = ff.closer.Close()
ff.closer = nil
}
return err
}

File diff suppressed because it is too large Load Diff

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