Compare commits
27 Commits
master
...
release-br
Author | SHA1 | Date |
---|---|---|
|
e9f45831fa | |
|
0114a6029e | |
|
b57c288cf0 | |
|
2646b7dc2e | |
|
0b025b1193 | |
|
de3c6a23a9 | |
|
9e8c73a9cf | |
|
147f5680bc | |
|
37bde98e6a | |
|
4d17acb3b1 | |
|
64f9b8ad70 | |
|
dab65e92bb | |
|
f1c3f9758c | |
|
d297d4d5a0 | |
|
421e503401 | |
|
6f8a893118 | |
|
f160a88a35 | |
|
934cdca383 | |
|
4dfc99feba | |
|
ddbd6bea01 | |
|
c11093697e | |
|
2dbf5794f6 | |
|
ef92c6f62f | |
|
927e542327 | |
|
9e9bf16a4e | |
|
b86cb9a852 | |
|
c03d3e005f |
|
@ -1,231 +0,0 @@
|
||||||
// Copyright 2019 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// authtest is a diagnostic tool for implementations of the GOAUTH protocol
|
|
||||||
// described in https://golang.org/issue/26232.
|
|
||||||
//
|
|
||||||
// It accepts a single URL as an argument, and executes the GOAUTH protocol to
|
|
||||||
// fetch and display the headers for that URL.
|
|
||||||
//
|
|
||||||
// CAUTION: authtest logs the GOAUTH responses, which may include user
|
|
||||||
// credentials, to stderr. Do not post its output unless you are certain that
|
|
||||||
// all of the credentials involved are fake!
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/textproto"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var v = flag.Bool("v", false, "if true, log GOAUTH responses to stderr")
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
|
||||||
flag.Parse()
|
|
||||||
args := flag.Args()
|
|
||||||
if len(args) != 1 {
|
|
||||||
log.Fatalf("usage: [GOAUTH=CMD...] %s URL", filepath.Base(os.Args[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := try(args[0], nil)
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = try(args[0], resp)
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func try(url string, prev *http.Response) *http.Response {
|
|
||||||
req := new(http.Request)
|
|
||||||
if prev != nil {
|
|
||||||
*req = *prev.Request
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
req, err = http.NewRequest("HEAD", os.Args[1], nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goauth:
|
|
||||||
for _, argList := range strings.Split(os.Getenv("GOAUTH"), ";") {
|
|
||||||
// TODO(golang.org/issue/26849): If we escape quoted strings in GOFLAGS, use
|
|
||||||
// the same quoting here.
|
|
||||||
args := strings.Split(argList, " ")
|
|
||||||
if len(args) == 0 || args[0] == "" {
|
|
||||||
log.Fatalf("invalid or empty command in GOAUTH")
|
|
||||||
}
|
|
||||||
|
|
||||||
creds, err := getCreds(args, prev)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, c := range creds {
|
|
||||||
if c.Apply(req) {
|
|
||||||
fmt.Fprintf(os.Stderr, "# request to %s\n", req.URL)
|
|
||||||
fmt.Fprintf(os.Stderr, "%s %s %s\n", req.Method, req.URL, req.Proto)
|
|
||||||
req.Header.Write(os.Stderr)
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
break goauth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode < 400 || resp.StatusCode > 500 {
|
|
||||||
log.Fatalf("unexpected status: %v", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "# response from %s\n", resp.Request.URL)
|
|
||||||
formatHead(os.Stderr, resp)
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatHead(out io.Writer, resp *http.Response) {
|
|
||||||
fmt.Fprintf(out, "%s %s\n", resp.Proto, resp.Status)
|
|
||||||
if err := resp.Header.Write(out); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cred struct {
|
|
||||||
URLPrefixes []*url.URL
|
|
||||||
Header http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Cred) Apply(req *http.Request) bool {
|
|
||||||
if req.URL == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ok := false
|
|
||||||
for _, prefix := range c.URLPrefixes {
|
|
||||||
if prefix.Host == req.URL.Host &&
|
|
||||||
(req.URL.Path == prefix.Path ||
|
|
||||||
(strings.HasPrefix(req.URL.Path, prefix.Path) &&
|
|
||||||
(strings.HasSuffix(prefix.Path, "/") ||
|
|
||||||
req.URL.Path[len(prefix.Path)] == '/'))) {
|
|
||||||
ok = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, vs := range c.Header {
|
|
||||||
req.Header.Del(k)
|
|
||||||
for _, v := range vs {
|
|
||||||
req.Header.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Cred) String() string {
|
|
||||||
var buf strings.Builder
|
|
||||||
for _, u := range c.URLPrefixes {
|
|
||||||
fmt.Fprintln(&buf, u)
|
|
||||||
}
|
|
||||||
buf.WriteString("\n")
|
|
||||||
c.Header.Write(&buf)
|
|
||||||
buf.WriteString("\n")
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCreds(args []string, resp *http.Response) ([]Cred, error) {
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
if resp != nil {
|
|
||||||
u := *resp.Request.URL
|
|
||||||
u.RawQuery = ""
|
|
||||||
cmd.Args = append(cmd.Args, u.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var head strings.Builder
|
|
||||||
if resp != nil {
|
|
||||||
formatHead(&head, resp)
|
|
||||||
}
|
|
||||||
cmd.Stdin = strings.NewReader(head.String())
|
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "# %s\n", strings.Join(cmd.Args, " "))
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s: %v", strings.Join(cmd.Args, " "), err)
|
|
||||||
}
|
|
||||||
os.Stderr.Write(out)
|
|
||||||
os.Stderr.WriteString("\n")
|
|
||||||
|
|
||||||
var creds []Cred
|
|
||||||
r := textproto.NewReader(bufio.NewReader(bytes.NewReader(out)))
|
|
||||||
line := 0
|
|
||||||
readLoop:
|
|
||||||
for {
|
|
||||||
var prefixes []*url.URL
|
|
||||||
for {
|
|
||||||
prefix, err := r.ReadLine()
|
|
||||||
if err == io.EOF {
|
|
||||||
if len(prefixes) > 0 {
|
|
||||||
return nil, fmt.Errorf("line %d: %v", line, io.ErrUnexpectedEOF)
|
|
||||||
}
|
|
||||||
break readLoop
|
|
||||||
}
|
|
||||||
line++
|
|
||||||
|
|
||||||
if prefix == "" {
|
|
||||||
if len(prefixes) == 0 {
|
|
||||||
return nil, fmt.Errorf("line %d: unexpected newline", line)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
u, err := url.Parse(prefix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("line %d: malformed URL: %v", line, err)
|
|
||||||
}
|
|
||||||
if u.Scheme != "https" {
|
|
||||||
return nil, fmt.Errorf("line %d: non-HTTPS URL %q", line, prefix)
|
|
||||||
}
|
|
||||||
if len(u.RawQuery) > 0 {
|
|
||||||
return nil, fmt.Errorf("line %d: unexpected query string in URL %q", line, prefix)
|
|
||||||
}
|
|
||||||
if len(u.Fragment) > 0 {
|
|
||||||
return nil, fmt.Errorf("line %d: unexpected fragment in URL %q", line, prefix)
|
|
||||||
}
|
|
||||||
prefixes = append(prefixes, u)
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := r.ReadMIMEHeader()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("headers at line %d: %v", line, err)
|
|
||||||
}
|
|
||||||
if len(header) > 0 {
|
|
||||||
creds = append(creds, Cred{
|
|
||||||
URLPrefixes: prefixes,
|
|
||||||
Header: http.Header(header),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return creds, nil
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
// Copyright 2019 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// cookieauth uses a “Netscape cookie file” to implement the GOAUTH protocol
|
|
||||||
// described in https://golang.org/issue/26232.
|
|
||||||
// It expects the location of the file as the first command-line argument.
|
|
||||||
//
|
|
||||||
// Example GOAUTH usage:
|
|
||||||
// export GOAUTH="cookieauth $(git config --get http.cookieFile)"
|
|
||||||
//
|
|
||||||
// See http://www.cookiecentral.com/faq/#3.5 for a description of the Netscape
|
|
||||||
// cookie file format.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
fmt.Fprintf(os.Stderr, "usage: %s COOKIEFILE [URL]\n", os.Args[0])
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetPrefix("cookieauth: ")
|
|
||||||
|
|
||||||
f, err := os.Open(os.Args[1])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to read cookie file: %v\n", os.Args[1])
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
var (
|
|
||||||
targetURL *url.URL
|
|
||||||
targetURLs = map[string]*url.URL{}
|
|
||||||
)
|
|
||||||
if len(os.Args) == 3 {
|
|
||||||
targetURL, err = url.ParseRequestURI(os.Args[2])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("invalid request URI (%v): %q\n", err, os.Args[2])
|
|
||||||
}
|
|
||||||
targetURLs[targetURL.String()] = targetURL
|
|
||||||
} else if len(os.Args) > 3 {
|
|
||||||
// Extra arguments were passed: maybe the protocol was expanded?
|
|
||||||
// We don't know how to interpret the request, so ignore it.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
entries, err := parseCookieFile(f.Name(), f)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error reading cookie file: %v\n", f.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
jar, err := cookiejar.New(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to initialize cookie jar: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range entries {
|
|
||||||
u := &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: e.Host,
|
|
||||||
Path: e.Cookie.Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
if targetURL == nil {
|
|
||||||
targetURLs[u.String()] = u
|
|
||||||
}
|
|
||||||
|
|
||||||
jar.SetCookies(u, []*http.Cookie{&e.Cookie})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, u := range targetURLs {
|
|
||||||
req := &http.Request{URL: u, Header: make(http.Header)}
|
|
||||||
for _, c := range jar.Cookies(req.URL) {
|
|
||||||
req.AddCookie(c)
|
|
||||||
}
|
|
||||||
fmt.Printf("%s\n\n", u)
|
|
||||||
req.Header.Write(os.Stdout)
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Entry struct {
|
|
||||||
Host string
|
|
||||||
Cookie http.Cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseCookieFile parses a Netscape cookie file as described in
|
|
||||||
// http://www.cookiecentral.com/faq/#3.5.
|
|
||||||
func parseCookieFile(name string, r io.Reader) ([]*Entry, error) {
|
|
||||||
var entries []*Entry
|
|
||||||
s := bufio.NewScanner(r)
|
|
||||||
line := 0
|
|
||||||
for s.Scan() {
|
|
||||||
line++
|
|
||||||
text := strings.TrimSpace(s.Text())
|
|
||||||
if len(text) < 2 || (text[0] == '#' && unicode.IsSpace(rune(text[1]))) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
e, err := parseCookieLine(text)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("%s:%d: %v\n", name, line, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entries = append(entries, e)
|
|
||||||
}
|
|
||||||
return entries, s.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseCookieLine(line string) (*Entry, error) {
|
|
||||||
f := strings.Fields(line)
|
|
||||||
if len(f) < 7 {
|
|
||||||
return nil, fmt.Errorf("found %d columns; want 7", len(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
e := new(Entry)
|
|
||||||
c := &e.Cookie
|
|
||||||
|
|
||||||
if domain := f[0]; strings.HasPrefix(domain, "#HttpOnly_") {
|
|
||||||
c.HttpOnly = true
|
|
||||||
e.Host = strings.TrimPrefix(domain[10:], ".")
|
|
||||||
} else {
|
|
||||||
e.Host = strings.TrimPrefix(domain, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
isDomain, err := strconv.ParseBool(f[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("non-boolean domain flag: %v", err)
|
|
||||||
}
|
|
||||||
if isDomain {
|
|
||||||
c.Domain = e.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Path = f[2]
|
|
||||||
|
|
||||||
c.Secure, err = strconv.ParseBool(f[3])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("non-boolean secure flag: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expiration, err := strconv.ParseInt(f[4], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("malformed expiration: %v", err)
|
|
||||||
}
|
|
||||||
c.Expires = time.Unix(expiration, 0)
|
|
||||||
|
|
||||||
c.Name = f[5]
|
|
||||||
c.Value = f[6]
|
|
||||||
|
|
||||||
return e, nil
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
// Copyright 2019 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// gitauth uses 'git credential' to implement the GOAUTH protocol described in
|
|
||||||
// https://golang.org/issue/26232. It expects an absolute path to the working
|
|
||||||
// directory for the 'git' command as the first command-line argument.
|
|
||||||
//
|
|
||||||
// Example GOAUTH usage:
|
|
||||||
// export GOAUTH="gitauth $HOME"
|
|
||||||
//
|
|
||||||
// See https://git-scm.com/docs/gitcredentials or run 'man gitcredentials' for
|
|
||||||
// information on how to configure 'git credential'.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) < 2 || !filepath.IsAbs(os.Args[1]) {
|
|
||||||
fmt.Fprintf(os.Stderr, "usage: %s WORKDIR [URL]", os.Args[0])
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetPrefix("gitauth: ")
|
|
||||||
|
|
||||||
if len(os.Args) != 3 {
|
|
||||||
// No explicit URL was passed on the command line, but 'git credential'
|
|
||||||
// provides no way to enumerate existing credentials.
|
|
||||||
// Wait for a request for a specific URL.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.ParseRequestURI(os.Args[2])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("invalid request URI (%v): %q\n", err, os.Args[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
prefix *url.URL
|
|
||||||
lastHeader http.Header
|
|
||||||
lastStatus = http.StatusUnauthorized
|
|
||||||
)
|
|
||||||
for lastStatus == http.StatusUnauthorized {
|
|
||||||
cmd := exec.Command("git", "credential", "fill")
|
|
||||||
|
|
||||||
// We don't want to execute a 'git' command in an arbitrary directory, since
|
|
||||||
// that opens up a number of config-injection attacks (for example,
|
|
||||||
// https://golang.org/issue/29230). Instead, we have the user configure a
|
|
||||||
// directory explicitly on the command line.
|
|
||||||
cmd.Dir = os.Args[1]
|
|
||||||
|
|
||||||
cmd.Stdin = strings.NewReader(fmt.Sprintf("url=%s\n", u))
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("'git credential fill' failed: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix = new(url.URL)
|
|
||||||
var username, password string
|
|
||||||
lines := strings.Split(string(out), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
frags := strings.SplitN(line, "=", 2)
|
|
||||||
if len(frags) != 2 {
|
|
||||||
continue // Ignore unrecognized response lines.
|
|
||||||
}
|
|
||||||
switch strings.TrimSpace(frags[0]) {
|
|
||||||
case "protocol":
|
|
||||||
prefix.Scheme = frags[1]
|
|
||||||
case "host":
|
|
||||||
prefix.Host = frags[1]
|
|
||||||
case "path":
|
|
||||||
prefix.Path = frags[1]
|
|
||||||
case "username":
|
|
||||||
username = frags[1]
|
|
||||||
case "password":
|
|
||||||
password = frags[1]
|
|
||||||
case "url":
|
|
||||||
// Write to a local variable instead of updating prefix directly:
|
|
||||||
// if the url field is malformed, we don't want to invalidate
|
|
||||||
// information parsed from the protocol, host, and path fields.
|
|
||||||
u, err := url.ParseRequestURI(frags[1])
|
|
||||||
if err == nil {
|
|
||||||
prefix = u
|
|
||||||
} else {
|
|
||||||
log.Printf("malformed URL from 'git credential fill' (%v): %q\n", err, frags[1])
|
|
||||||
// Proceed anyway: we might be able to parse the prefix from other fields of the response.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Double-check that the URL Git gave us is a prefix of the one we requested.
|
|
||||||
if !strings.HasPrefix(u.String(), prefix.String()) {
|
|
||||||
log.Fatalf("requested a credential for %q, but 'git credential fill' provided one for %q\n", u, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a HEAD request to try to detect whether the credential is valid.
|
|
||||||
// If the user just typed in a correct password and has caching enabled,
|
|
||||||
// we don't want to nag them for it again the next time they run a 'go' command.
|
|
||||||
req, err := http.NewRequest("HEAD", u.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("internal error constructing HTTP HEAD request: %v\n", err)
|
|
||||||
}
|
|
||||||
req.SetBasicAuth(username, password)
|
|
||||||
lastHeader = req.Header
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("HTTPS HEAD request failed to connect: %v\n", err)
|
|
||||||
// Couldn't verify the credential, but we have no evidence that it is invalid either.
|
|
||||||
// Proceed, but don't update git's credential cache.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
lastStatus = resp.StatusCode
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
log.Printf("%s: %v %s\n", u, resp.StatusCode, http.StatusText(resp.StatusCode))
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized {
|
|
||||||
// We learned something about the credential: it either worked or it was invalid.
|
|
||||||
// Approve or reject the credential (on a best-effort basis)
|
|
||||||
// so that the git credential helper can update its cache as appropriate.
|
|
||||||
action := "approve"
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
action = "reject"
|
|
||||||
}
|
|
||||||
cmd = exec.Command("git", "credential", action)
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdout = os.Stderr
|
|
||||||
cmd.Stdin = bytes.NewReader(out)
|
|
||||||
_ = cmd.Run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write out the credential in the format expected by the 'go' command.
|
|
||||||
fmt.Printf("%s\n\n", prefix)
|
|
||||||
lastHeader.Write(os.Stdout)
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// netrcauth uses a .netrc file (or _netrc file on Windows) to implement the
|
|
||||||
// GOAUTH protocol described in https://golang.org/issue/26232.
|
|
||||||
// It expects the location of the file as the first command-line argument.
|
|
||||||
//
|
|
||||||
// Example GOAUTH usage:
|
|
||||||
// export GOAUTH="netrcauth $HOME/.netrc"
|
|
||||||
//
|
|
||||||
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html
|
|
||||||
// or run 'man 5 netrc' for a description of the .netrc file format.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
fmt.Fprintf(os.Stderr, "usage: %s NETRCFILE [URL]", os.Args[0])
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetPrefix("netrcauth: ")
|
|
||||||
|
|
||||||
if len(os.Args) != 2 {
|
|
||||||
// An explicit URL was passed on the command line, but netrcauth does not
|
|
||||||
// have any URL-specific output: it dumps the entire .netrc file at the
|
|
||||||
// first call.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
path := os.Args[1]
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Fatalf("failed to read %s: %v\n", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
u := &url.URL{Scheme: "https"}
|
|
||||||
lines := parseNetrc(string(data))
|
|
||||||
for _, l := range lines {
|
|
||||||
u.Host = l.machine
|
|
||||||
fmt.Printf("%s\n\n", u)
|
|
||||||
|
|
||||||
req := &http.Request{Header: make(http.Header)}
|
|
||||||
req.SetBasicAuth(l.login, l.password)
|
|
||||||
req.Header.Write(os.Stdout)
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following functions were extracted from src/cmd/go/internal/web2/web.go
|
|
||||||
// as of https://golang.org/cl/161698.
|
|
||||||
|
|
||||||
type netrcLine struct {
|
|
||||||
machine string
|
|
||||||
login string
|
|
||||||
password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseNetrc(data string) []netrcLine {
|
|
||||||
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html
|
|
||||||
// for documentation on the .netrc format.
|
|
||||||
var nrc []netrcLine
|
|
||||||
var l netrcLine
|
|
||||||
inMacro := false
|
|
||||||
for _, line := range strings.Split(data, "\n") {
|
|
||||||
if inMacro {
|
|
||||||
if line == "" {
|
|
||||||
inMacro = false
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
f := strings.Fields(line)
|
|
||||||
i := 0
|
|
||||||
for ; i < len(f)-1; i += 2 {
|
|
||||||
// Reset at each "machine" token.
|
|
||||||
// “The auto-login process searches the .netrc file for a machine token
|
|
||||||
// that matches […]. Once a match is made, the subsequent .netrc tokens
|
|
||||||
// are processed, stopping when the end of file is reached or another
|
|
||||||
// machine or a default token is encountered.”
|
|
||||||
switch f[i] {
|
|
||||||
case "machine":
|
|
||||||
l = netrcLine{machine: f[i+1]}
|
|
||||||
case "default":
|
|
||||||
break
|
|
||||||
case "login":
|
|
||||||
l.login = f[i+1]
|
|
||||||
case "password":
|
|
||||||
l.password = f[i+1]
|
|
||||||
case "macdef":
|
|
||||||
// “A macro is defined with the specified name; its contents begin with
|
|
||||||
// the next .netrc line and continue until a null line (consecutive
|
|
||||||
// new-line characters) is encountered.”
|
|
||||||
inMacro = true
|
|
||||||
}
|
|
||||||
if l.machine != "" && l.login != "" && l.password != "" {
|
|
||||||
nrc = append(nrc, l)
|
|
||||||
l = netrcLine{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i < len(f) && f[i] == "default" {
|
|
||||||
// “There can be only one default token, and it must be after all machine tokens.”
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nrc
|
|
||||||
}
|
|
|
@ -105,8 +105,8 @@ var (
|
||||||
outputFile = flag.String("o", "", "write output to `file` (default standard output)")
|
outputFile = flag.String("o", "", "write output to `file` (default standard output)")
|
||||||
dstPath = flag.String("dst", "", "set destination import `path` (default taken from current directory)")
|
dstPath = flag.String("dst", "", "set destination import `path` (default taken from current directory)")
|
||||||
pkgName = flag.String("pkg", "", "set destination package `name` (default taken from current directory)")
|
pkgName = flag.String("pkg", "", "set destination package `name` (default taken from current directory)")
|
||||||
prefix = flag.String("prefix", "&_", "set bundled identifier prefix to `p` (default is \"&_\", where & stands for the original name)")
|
prefix = flag.String("prefix", "", "set bundled identifier prefix to `p` (default source package name + \"_\")")
|
||||||
underscore = flag.Bool("underscore", false, "rewrite golang.org/x/* to internal/x/* imports; temporary workaround for golang.org/issue/16333")
|
underscore = flag.Bool("underscore", false, "rewrite golang.org to golang_org in imports; temporary workaround for golang.org/issue/16333")
|
||||||
|
|
||||||
importMap = map[string]string{}
|
importMap = map[string]string{}
|
||||||
)
|
)
|
||||||
|
@ -203,8 +203,9 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
|
||||||
// Because there was a single Import call and Load succeeded,
|
// Because there was a single Import call and Load succeeded,
|
||||||
// InitialPackages is guaranteed to hold the sole requested package.
|
// InitialPackages is guaranteed to hold the sole requested package.
|
||||||
info := lprog.InitialPackages()[0]
|
info := lprog.InitialPackages()[0]
|
||||||
if strings.Contains(prefix, "&") {
|
if prefix == "" {
|
||||||
prefix = strings.Replace(prefix, "&", info.Files[0].Name.Name, -1)
|
pkgName := info.Files[0].Name.Name
|
||||||
|
prefix = pkgName + "_"
|
||||||
}
|
}
|
||||||
|
|
||||||
objsToUpdate := make(map[types.Object]bool)
|
objsToUpdate := make(map[types.Object]bool)
|
||||||
|
@ -298,7 +299,7 @@ func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
|
||||||
pkgStd[spec] = true
|
pkgStd[spec] = true
|
||||||
} else {
|
} else {
|
||||||
if *underscore {
|
if *underscore {
|
||||||
spec = strings.Replace(spec, "golang.org/x/", "internal/x/", 1)
|
spec = strings.Replace(spec, "golang.org/", "golang_org/", 1)
|
||||||
}
|
}
|
||||||
pkgExt[spec] = true
|
pkgExt[spec] = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,12 +182,9 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if packages.PrintErrors(initial) > 0 {
|
|
||||||
return fmt.Errorf("packages contain errors")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and build SSA-form program representation.
|
// Create and build SSA-form program representation.
|
||||||
prog, pkgs := ssautil.AllPackages(initial, 0)
|
prog, pkgs := ssautil.Packages(initial, 0)
|
||||||
prog.Build()
|
prog.Build()
|
||||||
|
|
||||||
// -- call graph construction ------------------------------------------
|
// -- call graph construction ------------------------------------------
|
||||||
|
|
|
@ -12,26 +12,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
// This test currently requires GOPATH mode.
|
|
||||||
// Explicitly disabling module mode should suffix, but
|
|
||||||
// we'll also turn off GOPROXY just for good measure.
|
|
||||||
if err := os.Setenv("GO111MODULE", "off"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.Setenv("GOPROXY", "off"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallgraph(t *testing.T) {
|
func TestCallgraph(t *testing.T) {
|
||||||
|
t.Skip("golang.org/issue/29201")
|
||||||
gopath, err := filepath.Abs("testdata")
|
gopath, err := filepath.Abs("testdata")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -22,21 +22,12 @@
|
||||||
// -compileflags 'list'
|
// -compileflags 'list'
|
||||||
// Pass the space-separated list of flags to the compilation.
|
// 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
|
// -count n
|
||||||
// Run each benchmark n times (default 1).
|
// Run each benchmark n times (default 1).
|
||||||
//
|
//
|
||||||
// -cpuprofile file
|
// -cpuprofile file
|
||||||
// Write a CPU profile of the compiler to file.
|
// Write a CPU profile of the compiler to file.
|
||||||
//
|
//
|
||||||
// -go path
|
|
||||||
// Path to "go" command (default "go").
|
|
||||||
//
|
|
||||||
// -memprofile file
|
// -memprofile file
|
||||||
// Write a memory profile of the compiler to file.
|
// Write a memory profile of the compiler to file.
|
||||||
//
|
//
|
||||||
|
@ -46,15 +37,12 @@
|
||||||
// -obj
|
// -obj
|
||||||
// Report object file statistics.
|
// Report object file statistics.
|
||||||
//
|
//
|
||||||
// -pkg pkg
|
// -pkg
|
||||||
// Benchmark compiling a single package.
|
// Benchmark compiling a single package.
|
||||||
//
|
//
|
||||||
// -run regexp
|
// -run regexp
|
||||||
// Only run benchmarks with names matching regexp.
|
// Only run benchmarks with names matching regexp.
|
||||||
//
|
//
|
||||||
// -short
|
|
||||||
// Skip long-running benchmarks.
|
|
||||||
//
|
|
||||||
// Although -cpuprofile and -memprofile are intended to write a
|
// Although -cpuprofile and -memprofile are intended to write a
|
||||||
// combined profile for all the executed benchmarks to file,
|
// combined profile for all the executed benchmarks to file,
|
||||||
// today they write only the profile for the last benchmark executed.
|
// today they write only the profile for the last benchmark executed.
|
||||||
|
@ -96,7 +84,6 @@ import (
|
||||||
var (
|
var (
|
||||||
goroot string
|
goroot string
|
||||||
compiler string
|
compiler string
|
||||||
linker string
|
|
||||||
runRE *regexp.Regexp
|
runRE *regexp.Regexp
|
||||||
is6g bool
|
is6g bool
|
||||||
)
|
)
|
||||||
|
@ -107,8 +94,6 @@ var (
|
||||||
flagObj = flag.Bool("obj", false, "report object file stats")
|
flagObj = flag.Bool("obj", false, "report object file stats")
|
||||||
flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary")
|
flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary")
|
||||||
flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile")
|
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`")
|
flagRun = flag.String("run", "", "run benchmarks matching `regexp`")
|
||||||
flagCount = flag.Int("count", 1, "run benchmarks `n` times")
|
flagCount = flag.Int("count", 1, "run benchmarks `n` times")
|
||||||
flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`")
|
flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`")
|
||||||
|
@ -118,31 +103,24 @@ var (
|
||||||
flagShort = flag.Bool("short", false, "skip long-running benchmarks")
|
flagShort = flag.Bool("short", false, "skip long-running benchmarks")
|
||||||
)
|
)
|
||||||
|
|
||||||
type test struct {
|
var tests = []struct {
|
||||||
name string
|
name string
|
||||||
r runner
|
dir string
|
||||||
}
|
long bool
|
||||||
|
}{
|
||||||
type runner interface {
|
{"BenchmarkTemplate", "html/template", false},
|
||||||
long() bool
|
{"BenchmarkUnicode", "unicode", false},
|
||||||
run(name string, count int) error
|
{"BenchmarkGoTypes", "go/types", false},
|
||||||
}
|
{"BenchmarkCompiler", "cmd/compile/internal/gc", false},
|
||||||
|
{"BenchmarkSSA", "cmd/compile/internal/ssa", false},
|
||||||
var tests = []test{
|
{"BenchmarkFlate", "compress/flate", false},
|
||||||
{"BenchmarkTemplate", compile{"html/template"}},
|
{"BenchmarkGoParser", "go/parser", false},
|
||||||
{"BenchmarkUnicode", compile{"unicode"}},
|
{"BenchmarkReflect", "reflect", false},
|
||||||
{"BenchmarkGoTypes", compile{"go/types"}},
|
{"BenchmarkTar", "archive/tar", false},
|
||||||
{"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}},
|
{"BenchmarkXML", "encoding/xml", false},
|
||||||
{"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}},
|
{"BenchmarkStdCmd", "", true},
|
||||||
{"BenchmarkFlate", compile{"compress/flate"}},
|
{"BenchmarkHelloSize", "", false},
|
||||||
{"BenchmarkGoParser", compile{"go/parser"}},
|
{"BenchmarkCmdGoSize", "", true},
|
||||||
{"BenchmarkReflect", compile{"reflect"}},
|
|
||||||
{"BenchmarkTar", compile{"archive/tar"}},
|
|
||||||
{"BenchmarkXML", compile{"encoding/xml"}},
|
|
||||||
{"BenchmarkLinkCompiler", link{"cmd/compile"}},
|
|
||||||
{"BenchmarkStdCmd", goBuild{[]string{"std", "cmd"}}},
|
|
||||||
{"BenchmarkHelloSize", size{"$GOROOT/test/helloworld.go", false}},
|
|
||||||
{"BenchmarkCmdGoSize", size{"cmd/go", true}},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
|
@ -170,23 +148,16 @@ func main() {
|
||||||
|
|
||||||
compiler = *flagCompiler
|
compiler = *flagCompiler
|
||||||
if compiler == "" {
|
if compiler == "" {
|
||||||
var foundTool string
|
out, err := exec.Command(*flagGoCmd, "tool", "-n", "compile").CombinedOutput()
|
||||||
foundTool, compiler = toolPath("compile", "6g")
|
if err != nil {
|
||||||
if foundTool == "6g" {
|
out, err = exec.Command(*flagGoCmd, "tool", "-n", "6g").CombinedOutput()
|
||||||
is6g = true
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compiler = strings.TrimSpace(string(out))
|
||||||
linker = *flagLinker
|
|
||||||
if linker == "" && !is6g { // TODO: Support 6l
|
|
||||||
_, linker = toolPath("link")
|
|
||||||
}
|
|
||||||
|
|
||||||
if is6g {
|
|
||||||
*flagMemprofilerate = -1
|
|
||||||
*flagAlloc = false
|
|
||||||
*flagCpuprofile = ""
|
|
||||||
*flagMemprofile = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flagRun != "" {
|
if *flagRun != "" {
|
||||||
|
@ -197,117 +168,67 @@ func main() {
|
||||||
runRE = r
|
runRE = r
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flagPackage != "" {
|
|
||||||
tests = []test{
|
|
||||||
{"BenchmarkPkg", compile{*flagPackage}},
|
|
||||||
{"BenchmarkPkgLink", link{*flagPackage}},
|
|
||||||
}
|
|
||||||
runRE = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < *flagCount; i++ {
|
for i := 0; i < *flagCount; i++ {
|
||||||
|
if *flagPackage != "" {
|
||||||
|
runBuild("BenchmarkPkg", *flagPackage, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
if tt.r.long() && *flagShort {
|
if tt.long && *flagShort {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if runRE == nil || runRE.MatchString(tt.name) {
|
if runRE == nil || runRE.MatchString(tt.name) {
|
||||||
if err := tt.r.run(tt.name, i); err != nil {
|
runBuild(tt.name, tt.dir, i)
|
||||||
log.Printf("%s: %v", tt.name, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toolPath(names ...string) (found, path string) {
|
func runCmd(name string, cmd *exec.Cmd) {
|
||||||
var out1 []byte
|
|
||||||
var err1 error
|
|
||||||
for i, name := range names {
|
|
||||||
out, err := exec.Command(*flagGoCmd, "tool", "-n", name).CombinedOutput()
|
|
||||||
if err == nil {
|
|
||||||
return name, strings.TrimSpace(string(out))
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
out1, err1 = out, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Fatalf("go tool -n %s: %v\n%s", names[0], err1, out1)
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pkg struct {
|
|
||||||
Dir string
|
|
||||||
GoFiles []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func goList(dir string) (*Pkg, error) {
|
|
||||||
var pkg Pkg
|
|
||||||
out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("go list -json %s: %v\n", dir, err)
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(out, &pkg); err != nil {
|
|
||||||
return nil, fmt.Errorf("go list -json %s: unmarshal: %v", dir, err)
|
|
||||||
}
|
|
||||||
return &pkg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCmd(name string, cmd *exec.Cmd) error {
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
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())
|
fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type goBuild struct{ pkgs []string }
|
func runStdCmd() {
|
||||||
|
|
||||||
func (goBuild) long() bool { return true }
|
|
||||||
|
|
||||||
func (r goBuild) run(name string, count int) error {
|
|
||||||
args := []string{"build", "-a"}
|
args := []string{"build", "-a"}
|
||||||
if *flagCompilerFlags != "" {
|
if *flagCompilerFlags != "" {
|
||||||
args = append(args, "-gcflags", *flagCompilerFlags)
|
args = append(args, "-gcflags", *flagCompilerFlags)
|
||||||
}
|
}
|
||||||
args = append(args, r.pkgs...)
|
args = append(args, "std", "cmd")
|
||||||
cmd := exec.Command(*flagGoCmd, args...)
|
cmd := exec.Command(*flagGoCmd, args...)
|
||||||
cmd.Dir = filepath.Join(goroot, "src")
|
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 is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
|
||||||
path string
|
func runSize(name, path string) {
|
||||||
isLong bool
|
cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", path)
|
||||||
}
|
|
||||||
|
|
||||||
func (r size) long() bool { return r.isLong }
|
|
||||||
|
|
||||||
func (r size) run(name string, count int) error {
|
|
||||||
if strings.HasPrefix(r.path, "$GOROOT/") {
|
|
||||||
r.path = goroot + "/" + r.path[len("$GOROOT/"):]
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", r.path)
|
|
||||||
cmd.Stdout = os.Stderr
|
cmd.Stdout = os.Stderr
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return err
|
log.Print(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove("_compilebenchout_")
|
defer os.Remove("_compilebenchout_")
|
||||||
info, err := os.Stat("_compilebenchout_")
|
info, err := os.Stat("_compilebenchout_")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Print(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
|
out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
|
||||||
if err != nil {
|
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")
|
lines := strings.Split(string(out), "\n")
|
||||||
if len(lines) < 2 {
|
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])
|
f := strings.Fields(lines[1])
|
||||||
if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
|
if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
|
||||||
|
@ -315,31 +236,127 @@ func (r size) run(name string, count int) error {
|
||||||
} else if strings.Contains(lines[0], "bss") && len(f) >= 3 {
|
} 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())
|
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 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
|
||||||
|
}
|
||||||
|
|
||||||
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.
|
// Make sure dependencies needed by go tool compile are installed to GOROOT/pkg.
|
||||||
out, err := exec.Command(*flagGoCmd, "build", "-i", c.dir).CombinedOutput()
|
out, err := exec.Command(*flagGoCmd, "build", "-i", dir).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("go build -i %s: %v\n%s", c.dir, err, out)
|
log.Printf("go build -i %s: %v\n%s", dir, err, out)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find dir and source file list.
|
// Find dir and source file list.
|
||||||
pkg, err := goList(c.dir)
|
var pkg struct {
|
||||||
|
Dir string
|
||||||
|
GoFiles []string
|
||||||
|
}
|
||||||
|
out, err = exec.Command(*flagGoCmd, "list", "-json", dir).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Printf("go list -json %s: %v\n", dir, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(out, &pkg); err != nil {
|
||||||
|
log.Printf("go list -json %s: unmarshal: %v", dir, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"-o", "_compilebench_.o"}
|
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)...)
|
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...)
|
args = append(args, pkg.GoFiles...)
|
||||||
if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil {
|
cmd := exec.Command(compiler, args...)
|
||||||
return err
|
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"
|
opath := pkg.Dir + "/_compilebench_.o"
|
||||||
|
@ -358,147 +375,4 @@ func (c compile) run(name string, count int) error {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
os.Remove(opath)
|
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,83 +1,18 @@
|
||||||
// Copyright 2019 The Go Authors. All rights reserved.
|
// The digraph command performs queries over unlabelled directed graphs
|
||||||
// Use of this source code is governed by a BSD-style
|
// represented in text form. It is intended to integrate nicely with
|
||||||
// license that can be found in the LICENSE file.
|
// typical UNIX command pipelines.
|
||||||
|
//
|
||||||
/*
|
// Since directed graphs (import graphs, reference graphs, call graphs,
|
||||||
The digraph command performs queries over unlabelled directed graphs
|
// etc) often arise during software tool development and debugging, this
|
||||||
represented in text form. It is intended to integrate nicely with
|
// command is included in the go.tools repository.
|
||||||
typical UNIX command pipelines.
|
//
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
your-application | digraph [command]
|
|
||||||
|
|
||||||
The support commands are:
|
|
||||||
|
|
||||||
nodes
|
|
||||||
the set of all nodes
|
|
||||||
degree
|
|
||||||
the in-degree and out-degree of each node
|
|
||||||
preds <node> ...
|
|
||||||
the set of immediate predecessors of the specified nodes
|
|
||||||
succs <node> ...
|
|
||||||
the set of immediate successors of the specified nodes
|
|
||||||
forward <node> ...
|
|
||||||
the set of nodes transitively reachable from the specified nodes
|
|
||||||
reverse <node> ...
|
|
||||||
the set of nodes that transitively reach the specified nodes
|
|
||||||
somepath <node> <node>
|
|
||||||
the list of nodes on some arbitrary path from the first node to the second
|
|
||||||
allpaths <node> <node>
|
|
||||||
the set of nodes on all paths from the first node to the second
|
|
||||||
sccs
|
|
||||||
all strongly connected components (one per line)
|
|
||||||
scc <node>
|
|
||||||
the set of nodes nodes strongly connected to the specified one
|
|
||||||
|
|
||||||
Input format:
|
|
||||||
|
|
||||||
Each line contains zero or more words. Words are separated by unquoted
|
|
||||||
whitespace; words may contain Go-style double-quoted portions, allowing spaces
|
|
||||||
and other characters to be expressed.
|
|
||||||
|
|
||||||
Each word declares a node, and if there are more than one, an edge from the
|
|
||||||
first to each subsequent one. The graph is provided on the standard input.
|
|
||||||
|
|
||||||
For instance, the following (acyclic) graph specifies a partial order among the
|
|
||||||
subtasks of getting dressed:
|
|
||||||
|
|
||||||
$ cat clothes.txt
|
|
||||||
socks shoes
|
|
||||||
"boxer shorts" pants
|
|
||||||
pants belt shoes
|
|
||||||
shirt tie sweater
|
|
||||||
sweater jacket
|
|
||||||
hat
|
|
||||||
|
|
||||||
The line "shirt tie sweater" indicates the two edges shirt -> tie and
|
|
||||||
shirt -> sweater, not shirt -> tie -> sweater.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
Using digraph with existing Go tools:
|
|
||||||
|
|
||||||
$ go mod graph | digraph nodes # Operate on the Go module graph.
|
|
||||||
$ go list -m all | digraph nodes # Operate on the Go package graph.
|
|
||||||
|
|
||||||
Show the transitive closure of imports of the digraph tool itself:
|
|
||||||
$ go list -f '{{.ImportPath}} {{join .Imports " "}}' ... | digraph forward golang.org/x/tools/cmd/digraph
|
|
||||||
|
|
||||||
Show which clothes (see above) must be donned before a jacket:
|
|
||||||
$ digraph reverse jacket
|
|
||||||
|
|
||||||
*/
|
|
||||||
package main // import "golang.org/x/tools/cmd/digraph"
|
|
||||||
|
|
||||||
// TODO(adonovan):
|
// TODO(adonovan):
|
||||||
// - support input files other than stdin
|
// - support input files other than stdin
|
||||||
// - support alternative formats (AT&T GraphViz, CSV, etc),
|
// - support alternative formats (AT&T GraphViz, CSV, etc),
|
||||||
// a comment syntax, etc.
|
// a comment syntax, etc.
|
||||||
// - allow queries to nest, like Blaze query language.
|
// - allow queries to nest, like Blaze query language.
|
||||||
|
//
|
||||||
|
package main // import "golang.org/x/tools/cmd/digraph"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -93,41 +28,73 @@ import (
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage() {
|
const Usage = `digraph: queries over directed graphs in text form.
|
||||||
fmt.Fprintf(os.Stderr, `Usage: your-application | digraph [command]
|
|
||||||
|
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
|
nodes
|
||||||
the set of all nodes
|
the set of all nodes
|
||||||
degree
|
degree
|
||||||
the in-degree and out-degree of each node
|
the in-degree and out-degree of each node.
|
||||||
preds <node> ...
|
preds <label> ...
|
||||||
the set of immediate predecessors of the specified nodes
|
the set of immediate predecessors of the specified nodes
|
||||||
succs <node> ...
|
succs <label> ...
|
||||||
the set of immediate successors of the specified nodes
|
the set of immediate successors of the specified nodes
|
||||||
forward <node> ...
|
forward <label> ...
|
||||||
the set of nodes transitively reachable from the specified nodes
|
the set of nodes transitively reachable from the specified nodes
|
||||||
reverse <node> ...
|
reverse <label> ...
|
||||||
the set of nodes that transitively reach the specified nodes
|
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
|
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
|
the set of nodes on all paths from the first node to the second
|
||||||
sccs
|
sccs
|
||||||
all strongly connected components (one per line)
|
all strongly connected components (one per line)
|
||||||
scc <node>
|
scc <label>
|
||||||
the set of nodes nodes strongly connected to the specified one
|
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() {
|
func main() {
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
usage()
|
fmt.Println(Usage)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := digraph(args[0], args[1:]); err != nil {
|
if err := digraph(args[0], args[1:]); err != nil {
|
||||||
|
@ -262,47 +229,6 @@ func (g graph) sccs() []nodeset {
|
||||||
return sccs
|
return sccs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g graph) allpaths(from, to string) error {
|
|
||||||
// Mark all nodes to "to".
|
|
||||||
seen := make(nodeset) // value of seen[x] indicates whether x is on some path to "to"
|
|
||||||
var visit func(node string) bool
|
|
||||||
visit = func(node string) bool {
|
|
||||||
reachesTo, ok := seen[node]
|
|
||||||
if !ok {
|
|
||||||
reachesTo = node == to
|
|
||||||
seen[node] = reachesTo
|
|
||||||
for e := range g[node] {
|
|
||||||
if visit(e) {
|
|
||||||
reachesTo = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if reachesTo && node != to {
|
|
||||||
seen[node] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reachesTo
|
|
||||||
}
|
|
||||||
visit(from)
|
|
||||||
|
|
||||||
// For each marked node, collect its marked successors.
|
|
||||||
var edges []string
|
|
||||||
for n := range seen {
|
|
||||||
for succ := range g[n] {
|
|
||||||
if seen[succ] {
|
|
||||||
edges = append(edges, n+" "+succ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort (so that this method is deterministic) and print edges.
|
|
||||||
sort.Strings(edges)
|
|
||||||
for _, e := range edges {
|
|
||||||
fmt.Fprintln(stdout, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(rd io.Reader) (graph, error) {
|
func parse(rd io.Reader) (graph, error) {
|
||||||
g := make(graph)
|
g := make(graph)
|
||||||
|
|
||||||
|
@ -325,7 +251,6 @@ func parse(rd io.Reader) (graph, error) {
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overridable for testing purposes.
|
|
||||||
var stdin io.Reader = os.Stdin
|
var stdin io.Reader = os.Stdin
|
||||||
var stdout io.Writer = os.Stdout
|
var stdout io.Writer = os.Stdout
|
||||||
|
|
||||||
|
@ -440,7 +365,33 @@ func digraph(cmd string, args []string) error {
|
||||||
if g[to] == nil {
|
if g[to] == nil {
|
||||||
return fmt.Errorf("no such 'to' node %q", to)
|
return fmt.Errorf("no such 'to' node %q", to)
|
||||||
}
|
}
|
||||||
g.allpaths(from, to)
|
|
||||||
|
seen := make(nodeset) // value of seen[x] indicates whether x is on some path to 'to'
|
||||||
|
var visit func(label string) bool
|
||||||
|
visit = func(label string) bool {
|
||||||
|
reachesTo, ok := seen[label]
|
||||||
|
if !ok {
|
||||||
|
reachesTo = label == to
|
||||||
|
|
||||||
|
seen[label] = reachesTo
|
||||||
|
for e := range g[label] {
|
||||||
|
if visit(e) {
|
||||||
|
reachesTo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seen[label] = reachesTo
|
||||||
|
}
|
||||||
|
return reachesTo
|
||||||
|
}
|
||||||
|
if !visit(from) {
|
||||||
|
return fmt.Errorf("no path from %q to %q", from, to)
|
||||||
|
}
|
||||||
|
for label, reachesTo := range seen {
|
||||||
|
if !reachesTo {
|
||||||
|
delete(seen, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seen.sort().println("\n")
|
||||||
|
|
||||||
case "sccs":
|
case "sccs":
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
// Copyright 2019 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -29,34 +26,35 @@ d c
|
||||||
`
|
`
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
name string
|
|
||||||
input string
|
input string
|
||||||
cmd string
|
cmd string
|
||||||
args []string
|
args []string
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
|
{g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
|
||||||
{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
|
{g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
|
||||||
{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
|
{g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
|
||||||
{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
|
{g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
|
||||||
{"scss", g2, "sccs", nil, "a\nb\nc d\n"},
|
|
||||||
{"scc", g2, "scc", []string{"d"}, "c\nd\n"},
|
{g2, "allpaths", []string{"a", "d"}, "a\nb\nc\nd\n"},
|
||||||
{"succs", g2, "succs", []string{"a"}, "b\nc\n"},
|
|
||||||
{"preds", g2, "preds", []string{"c"}, "a\nd\n"},
|
{g2, "sccs", nil, "a\nb\nc d\n"},
|
||||||
{"preds multiple args", g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\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)
|
stdin = strings.NewReader(test.input)
|
||||||
stdout = new(bytes.Buffer)
|
stdout = new(bytes.Buffer)
|
||||||
if err := digraph(test.cmd, test.args); err != nil {
|
if err := digraph(test.cmd, test.args); err != nil {
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
got := stdout.(fmt.Stringer).String()
|
got := stdout.(fmt.Stringer).String()
|
||||||
if got != test.want {
|
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):
|
// TODO(adonovan):
|
||||||
|
@ -64,110 +62,6 @@ d c
|
||||||
// - test errors
|
// - test errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllpaths(t *testing.T) {
|
|
||||||
for _, test := range []struct {
|
|
||||||
name string
|
|
||||||
in string
|
|
||||||
to string // from is always "A"
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Basic",
|
|
||||||
in: "A B\nB C",
|
|
||||||
to: "B",
|
|
||||||
want: "A B\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Long",
|
|
||||||
in: "A B\nB C\n",
|
|
||||||
to: "C",
|
|
||||||
want: "A B\nB C\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Cycle Basic",
|
|
||||||
in: "A B\nB A",
|
|
||||||
to: "B",
|
|
||||||
want: "A B\nB A\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Cycle Path Out",
|
|
||||||
// A <-> B -> C -> D
|
|
||||||
in: "A B\nB A\nB C\nC D",
|
|
||||||
to: "C",
|
|
||||||
want: "A B\nB A\nB C\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Cycle Path Out Further Out",
|
|
||||||
// A -> B <-> C -> D -> E
|
|
||||||
in: "A B\nB C\nC D\nC B\nD E",
|
|
||||||
to: "D",
|
|
||||||
want: "A B\nB C\nC B\nC D\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Two Paths Basic",
|
|
||||||
// /-> C --\
|
|
||||||
// A -> B -- -> E -> F
|
|
||||||
// \-> D --/
|
|
||||||
in: "A B\nB C\nC E\nB D\nD E\nE F",
|
|
||||||
to: "E",
|
|
||||||
want: "A B\nB C\nB D\nC E\nD E\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Two Paths With One Immediately From Start",
|
|
||||||
// /-> B -+ -> D
|
|
||||||
// A -- |
|
|
||||||
// \-> C <+
|
|
||||||
in: "A B\nA C\nB C\nB D",
|
|
||||||
to: "C",
|
|
||||||
want: "A B\nA C\nB C\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Two Paths Further Up",
|
|
||||||
// /-> B --\
|
|
||||||
// A -- -> D -> E -> F
|
|
||||||
// \-> C --/
|
|
||||||
in: "A B\nA C\nB D\nC D\nD E\nE F",
|
|
||||||
to: "E",
|
|
||||||
want: "A B\nA C\nB D\nC D\nD E\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// We should include A - C - D even though it's further up the
|
|
||||||
// second path than D (which would already be in the graph by
|
|
||||||
// the time we get around to integrating the second path).
|
|
||||||
name: "Two Splits",
|
|
||||||
// /-> B --\ /-> E --\
|
|
||||||
// A -- -> D -- -> G -> H
|
|
||||||
// \-> C --/ \-> F --/
|
|
||||||
in: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\nG H",
|
|
||||||
to: "G",
|
|
||||||
want: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// D - E should not be duplicated.
|
|
||||||
name: "Two Paths - Two Splits With Gap",
|
|
||||||
// /-> B --\ /-> F --\
|
|
||||||
// A -- -> D -> E -- -> H -> I
|
|
||||||
// \-> C --/ \-> G --/
|
|
||||||
in: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\nH I",
|
|
||||||
to: "H",
|
|
||||||
want: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\n",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
stdin = strings.NewReader(test.in)
|
|
||||||
stdout = new(bytes.Buffer)
|
|
||||||
if err := digraph("allpaths", []string{"A", test.to}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := stdout.(fmt.Stringer).String()
|
|
||||||
if got != test.want {
|
|
||||||
t.Errorf("digraph(allpaths, A, %s) = got %q, want %q", test.to, got, test.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSplit(t *testing.T) {
|
func TestSplit(t *testing.T) {
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
line string
|
line string
|
||||||
|
|
|
@ -491,18 +491,15 @@ func list(args ...string) ([]*listPackage, error) {
|
||||||
return pkgs, nil
|
return pkgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// cwd contains the current working directory of the tool.
|
var cwd string
|
||||||
//
|
|
||||||
// It is initialized directly so that its value will be set for any other
|
func init() {
|
||||||
// package variables or init functions that depend on it, such as the gopath
|
var err error
|
||||||
// variable in main_test.go.
|
cwd, err = os.Getwd()
|
||||||
var cwd string = func() string {
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("os.Getwd: %v", err)
|
log.Fatalf("os.Getwd: %v", err)
|
||||||
}
|
}
|
||||||
return cwd
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
// shortPath returns an absolute or relative name for path, whatever is shorter.
|
// shortPath returns an absolute or relative name for path, whatever is shorter.
|
||||||
// Plundered from $GOROOT/src/cmd/go/build.go.
|
// Plundered from $GOROOT/src/cmd/go/build.go.
|
||||||
|
|
|
@ -10,7 +10,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -33,25 +32,11 @@ import (
|
||||||
// titanic.biz/bar -- domain is sinking; package has jumped ship to new.com/bar
|
// titanic.biz/bar -- domain is sinking; package has jumped ship to new.com/bar
|
||||||
// titanic.biz/foo -- domain is sinking but package has no import comment yet
|
// titanic.biz/foo -- domain is sinking but package has no import comment yet
|
||||||
|
|
||||||
var gopath = filepath.Join(cwd, "testdata")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if err := os.Setenv("GOPATH", gopath); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This test currently requires GOPATH mode.
|
|
||||||
// Explicitly disabling module mode should suffix, but
|
|
||||||
// we'll also turn off GOPROXY just for good measure.
|
|
||||||
if err := os.Setenv("GO111MODULE", "off"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.Setenv("GOPROXY", "off"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFixImports(t *testing.T) {
|
func TestFixImports(t *testing.T) {
|
||||||
|
gopath := filepath.Join(cwd, "testdata")
|
||||||
|
if err := os.Setenv("GOPATH", gopath); err != nil {
|
||||||
|
t.Fatalf("os.Setenv: %v", err)
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
stderr = os.Stderr
|
stderr = os.Stderr
|
||||||
*badDomains = "code.google.com"
|
*badDomains = "code.google.com"
|
||||||
|
@ -239,6 +224,11 @@ import (
|
||||||
|
|
||||||
// TestDryRun tests that the -n flag suppresses calls to writeFile.
|
// TestDryRun tests that the -n flag suppresses calls to writeFile.
|
||||||
func TestDryRun(t *testing.T) {
|
func TestDryRun(t *testing.T) {
|
||||||
|
gopath := filepath.Join(cwd, "testdata")
|
||||||
|
if err := os.Setenv("GOPATH", gopath); err != nil {
|
||||||
|
t.Fatalf("os.Setenv: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
*dryrun = true
|
*dryrun = true
|
||||||
defer func() { *dryrun = false }() // restore
|
defer func() { *dryrun = false }() // restore
|
||||||
stderr = new(bytes.Buffer)
|
stderr = new(bytes.Buffer)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
|
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
index.split.*
|
||||||
|
godoc.index
|
||||||
|
godoc.zip
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Builder
|
||||||
|
#########
|
||||||
|
|
||||||
|
FROM golang:1.11 AS build
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
zip # required for generate-index.bash
|
||||||
|
|
||||||
|
# Check out the desired version of Go, both to build the godoc binary and serve
|
||||||
|
# as the goroot for content serving.
|
||||||
|
ARG GO_REF
|
||||||
|
RUN test -n "$GO_REF" # GO_REF is required.
|
||||||
|
RUN git clone --single-branch --depth=1 -b $GO_REF https://go.googlesource.com/go /goroot
|
||||||
|
RUN cd /goroot/src && ./make.bash
|
||||||
|
|
||||||
|
ENV GOROOT /goroot
|
||||||
|
ENV PATH=/goroot/bin:$PATH
|
||||||
|
|
||||||
|
RUN go version
|
||||||
|
|
||||||
|
RUN go get -v -d \
|
||||||
|
golang.org/x/net/context \
|
||||||
|
google.golang.org/appengine \
|
||||||
|
cloud.google.com/go/datastore \
|
||||||
|
golang.org/x/build \
|
||||||
|
github.com/gomodule/redigo/redis
|
||||||
|
|
||||||
|
COPY . /go/src/golang.org/x/tools
|
||||||
|
|
||||||
|
WORKDIR /go/src/golang.org/x/tools/cmd/godoc
|
||||||
|
RUN GODOC_DOCSET=/goroot ./generate-index.bash
|
||||||
|
|
||||||
|
RUN go build -o /godoc -tags=golangorg golang.org/x/tools/cmd/godoc
|
||||||
|
|
||||||
|
# Clean up goroot for the final image.
|
||||||
|
RUN cd /goroot && git clean -xdf
|
||||||
|
|
||||||
|
# Add build metadata.
|
||||||
|
RUN cd /goroot && echo "go repo HEAD: $(git rev-parse HEAD)" >> /goroot/buildinfo
|
||||||
|
RUN echo "requested go ref: ${GO_REF}" >> /goroot/buildinfo
|
||||||
|
ARG TOOLS_HEAD
|
||||||
|
RUN echo "x/tools HEAD: ${TOOLS_HEAD}" >> /goroot/buildinfo
|
||||||
|
ARG TOOLS_CLEAN
|
||||||
|
RUN echo "x/tools clean: ${TOOLS_CLEAN}" >> /goroot/buildinfo
|
||||||
|
ARG DOCKER_TAG
|
||||||
|
RUN echo "image: ${DOCKER_TAG}" >> /goroot/buildinfo
|
||||||
|
ARG BUILD_ENV
|
||||||
|
RUN echo "build env: ${BUILD_ENV}" >> /goroot/buildinfo
|
||||||
|
|
||||||
|
RUN rm -rf /goroot/.git
|
||||||
|
|
||||||
|
# Final image
|
||||||
|
#############
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/base
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /godoc /app/
|
||||||
|
COPY --from=build /go/src/golang.org/x/tools/cmd/godoc/hg-git-mapping.bin /app/
|
||||||
|
|
||||||
|
COPY --from=build /goroot /goroot
|
||||||
|
ENV GOROOT /goroot
|
||||||
|
|
||||||
|
COPY --from=build /go/src/golang.org/x/tools/cmd/godoc/index.split.* /app/
|
||||||
|
ENV GODOC_INDEX_GLOB index.split.*
|
||||||
|
|
||||||
|
CMD ["/app/godoc"]
|
|
@ -0,0 +1,80 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
.PHONY: usage
|
||||||
|
|
||||||
|
GO_REF ?= release-branch.go1.11
|
||||||
|
TOOLS_HEAD := $(shell git rev-parse HEAD)
|
||||||
|
TOOLS_CLEAN := $(shell (git status --porcelain | grep -q .) && echo dirty || echo clean)
|
||||||
|
ifeq ($(TOOLS_CLEAN),clean)
|
||||||
|
DOCKER_VERSION ?= $(TOOLS_HEAD)
|
||||||
|
else
|
||||||
|
DOCKER_VERSION ?= $(TOOLS_HEAD)-dirty
|
||||||
|
endif
|
||||||
|
GCP_PROJECT := golang-org
|
||||||
|
DOCKER_TAG := gcr.io/$(GCP_PROJECT)/godoc:$(DOCKER_VERSION)
|
||||||
|
|
||||||
|
usage:
|
||||||
|
@echo "See Makefile and README.godoc-app"
|
||||||
|
@exit 1
|
||||||
|
|
||||||
|
cloud-build: Dockerfile.prod
|
||||||
|
gcloud builds submit \
|
||||||
|
--project=$(GCP_PROJECT) \
|
||||||
|
--config=cloudbuild.yaml \
|
||||||
|
--substitutions=_GO_REF=$(GO_REF),_TOOLS_HEAD=$(TOOLS_HEAD),_TOOLS_CLEAN=$(TOOLS_CLEAN),_DOCKER_TAG=$(DOCKER_TAG) \
|
||||||
|
../.. # source code
|
||||||
|
|
||||||
|
docker-build: Dockerfile.prod
|
||||||
|
# NOTE(cbro): move up in directory to include entire tools repo.
|
||||||
|
# NOTE(cbro): any changes made to this command must also be made in cloudbuild.yaml.
|
||||||
|
cd ../..; docker build \
|
||||||
|
-f=cmd/godoc/Dockerfile.prod \
|
||||||
|
--build-arg=GO_REF=$(GO_REF) \
|
||||||
|
--build-arg=TOOLS_HEAD=$(TOOLS_HEAD) \
|
||||||
|
--build-arg=TOOLS_CLEAN=$(TOOLS_CLEAN) \
|
||||||
|
--build-arg=DOCKER_TAG=$(DOCKER_TAG) \
|
||||||
|
--build-arg=BUILD_ENV=local \
|
||||||
|
--tag=$(DOCKER_TAG) \
|
||||||
|
.
|
||||||
|
|
||||||
|
docker-push: docker-build
|
||||||
|
docker push $(DOCKER_TAG)
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
gcloud -q app deploy app.prod.yaml \
|
||||||
|
--project=$(GCP_PROJECT) \
|
||||||
|
--no-promote \
|
||||||
|
--image-url=$(DOCKER_TAG)
|
||||||
|
|
||||||
|
get-latest-url:
|
||||||
|
@gcloud app versions list \
|
||||||
|
--service=default \
|
||||||
|
--project=$(GCP_PROJECT) \
|
||||||
|
--sort-by=~version.createTime \
|
||||||
|
--format='value(version.versionUrl)' \
|
||||||
|
--limit 1 | cut -f1 # NOTE(cbro): gcloud prints out createTime as the second field.
|
||||||
|
|
||||||
|
get-latest-id:
|
||||||
|
@gcloud app versions list \
|
||||||
|
--service=default \
|
||||||
|
--project=$(GCP_PROJECT) \
|
||||||
|
--sort-by=~version.createTime \
|
||||||
|
--format='value(version.id)' \
|
||||||
|
--limit 1 | cut -f1 # NOTE(cbro): gcloud prints out createTime as the second field.
|
||||||
|
|
||||||
|
regtest:
|
||||||
|
go test -v \
|
||||||
|
-regtest.host=$(shell make get-latest-url) \
|
||||||
|
-run=Live
|
||||||
|
|
||||||
|
publish: regtest
|
||||||
|
gcloud -q app services set-traffic default \
|
||||||
|
--splits=$(shell make get-latest-id)=1 \
|
||||||
|
--project=$(GCP_PROJECT)
|
||||||
|
|
||||||
|
@echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
@echo Stop and/or delete old versions:
|
||||||
|
@echo "https://console.cloud.google.com/appengine/versions?project=$(GCP_PROJECT)&serviceId=default&versionssize=50"
|
||||||
|
@echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
@ -0,0 +1,94 @@
|
||||||
|
godoc on Google App Engine
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
-------------
|
||||||
|
|
||||||
|
* Google Cloud SDK
|
||||||
|
https://cloud.google.com/sdk/
|
||||||
|
|
||||||
|
* Redis
|
||||||
|
|
||||||
|
* Go sources under $GOROOT
|
||||||
|
|
||||||
|
* Godoc sources inside $GOPATH
|
||||||
|
(go get -d golang.org/x/tools/cmd/godoc)
|
||||||
|
|
||||||
|
|
||||||
|
Running locally, in production mode
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
Build the app:
|
||||||
|
|
||||||
|
go build -tags golangorg
|
||||||
|
|
||||||
|
Run the app:
|
||||||
|
|
||||||
|
./godoc
|
||||||
|
|
||||||
|
godoc should come up at http://localhost:8080
|
||||||
|
|
||||||
|
Use the PORT environment variable to change the port:
|
||||||
|
|
||||||
|
PORT=8081 ./godoc
|
||||||
|
|
||||||
|
Running locally, in production mode, using Docker
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
Build the app's Docker container:
|
||||||
|
|
||||||
|
make docker-build
|
||||||
|
|
||||||
|
Make sure redis is running on port 6379:
|
||||||
|
|
||||||
|
$ echo PING | nc localhost 6379
|
||||||
|
+PONG
|
||||||
|
^C
|
||||||
|
|
||||||
|
Run the datastore emulator:
|
||||||
|
|
||||||
|
gcloud beta emulators datastore start --project golang-org
|
||||||
|
|
||||||
|
In another terminal window, run the container:
|
||||||
|
|
||||||
|
$(gcloud beta emulators datastore env-init)
|
||||||
|
|
||||||
|
docker run --rm \
|
||||||
|
--net host \
|
||||||
|
--env GODOC_REDIS_ADDR=localhost:6379 \
|
||||||
|
--env DATASTORE_EMULATOR_HOST=$DATASTORE_EMULATOR_HOST \
|
||||||
|
--env DATASTORE_PROJECT_ID=$DATASTORE_PROJECT_ID \
|
||||||
|
gcr.io/golang-org/godoc
|
||||||
|
|
||||||
|
godoc should come up at http://localhost:8080
|
||||||
|
|
||||||
|
|
||||||
|
Deploying to golang.org
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Make sure you're signed in to gcloud:
|
||||||
|
|
||||||
|
gcloud auth login
|
||||||
|
|
||||||
|
Build the image, push it to gcr.io, and deploy to Flex:
|
||||||
|
|
||||||
|
make cloud-build deploy
|
||||||
|
|
||||||
|
Point the load balancer to the newly deployed version:
|
||||||
|
(This also runs regression tests)
|
||||||
|
|
||||||
|
make publish
|
||||||
|
|
||||||
|
Stop and/or delete down any very old versions. (Stopped versions can be re-started.)
|
||||||
|
Keep at least one older verson to roll back to, just in case.
|
||||||
|
You can also migrate traffic to the new version via this UI.
|
||||||
|
|
||||||
|
https://console.cloud.google.com/appengine/versions?project=golang-org&serviceId=default&versionssize=50
|
||||||
|
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Ensure the Cloud SDK is on your PATH and you have the app-engine-go component
|
||||||
|
installed (gcloud components install app-engine-go) and your components are
|
||||||
|
up-to-date (gcloud components update)
|
|
@ -0,0 +1,13 @@
|
||||||
|
runtime: go
|
||||||
|
api_version: go1
|
||||||
|
instance_class: F4_1G
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- url: /s
|
||||||
|
script: _go_app
|
||||||
|
login: admin
|
||||||
|
- url: /dl/init
|
||||||
|
script: _go_app
|
||||||
|
login: admin
|
||||||
|
- url: /.*
|
||||||
|
script: _go_app
|
|
@ -0,0 +1,16 @@
|
||||||
|
runtime: custom
|
||||||
|
env: flex
|
||||||
|
|
||||||
|
env_variables:
|
||||||
|
GODOC_PROD: true
|
||||||
|
GODOC_ENFORCE_HOSTS: true
|
||||||
|
GODOC_REDIS_ADDR: 10.0.0.4:6379 # instance "gophercache"
|
||||||
|
GODOC_ANALYTICS: UA-11222381-2
|
||||||
|
DATASTORE_PROJECT_ID: golang-org
|
||||||
|
|
||||||
|
network:
|
||||||
|
name: golang
|
||||||
|
|
||||||
|
resources:
|
||||||
|
cpu: 4
|
||||||
|
memory_gb: 7.50
|
|
@ -0,0 +1,152 @@
|
||||||
|
// 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 golangorg
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// This file replaces main.go when running godoc under app-engine.
|
||||||
|
// See README.godoc-app for details.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/godoc"
|
||||||
|
"golang.org/x/tools/godoc/dl"
|
||||||
|
"golang.org/x/tools/godoc/proxy"
|
||||||
|
"golang.org/x/tools/godoc/redirect"
|
||||||
|
"golang.org/x/tools/godoc/short"
|
||||||
|
"golang.org/x/tools/godoc/static"
|
||||||
|
"golang.org/x/tools/godoc/vfs"
|
||||||
|
"golang.org/x/tools/godoc/vfs/gatefs"
|
||||||
|
"golang.org/x/tools/godoc/vfs/mapfs"
|
||||||
|
"golang.org/x/tools/godoc/vfs/zipfs"
|
||||||
|
|
||||||
|
"cloud.google.com/go/datastore"
|
||||||
|
"golang.org/x/tools/internal/memcache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(log.Lshortfile | log.LstdFlags)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// .zip filename
|
||||||
|
zipFilename = os.Getenv("GODOC_ZIP")
|
||||||
|
|
||||||
|
// goroot directory in .zip file
|
||||||
|
zipGoroot = os.Getenv("GODOC_ZIP_PREFIX")
|
||||||
|
|
||||||
|
// glob pattern describing search index files
|
||||||
|
// (if empty, the index is built at run-time)
|
||||||
|
indexFilenames = os.Getenv("GODOC_INDEX_GLOB")
|
||||||
|
)
|
||||||
|
|
||||||
|
playEnabled = true
|
||||||
|
|
||||||
|
log.Println("initializing godoc ...")
|
||||||
|
log.Printf(".zip file = %s", zipFilename)
|
||||||
|
log.Printf(".zip GOROOT = %s", zipGoroot)
|
||||||
|
log.Printf("index files = %s", indexFilenames)
|
||||||
|
|
||||||
|
if zipFilename != "" {
|
||||||
|
goroot := path.Join("/", zipGoroot) // fsHttp paths are relative to '/'
|
||||||
|
// read .zip file and set up file systems
|
||||||
|
rc, err := zip.OpenReader(zipFilename)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%s: %s\n", zipFilename, err)
|
||||||
|
}
|
||||||
|
// rc is never closed (app running forever)
|
||||||
|
fs.Bind("/", zipfs.New(rc, zipFilename), goroot, vfs.BindReplace)
|
||||||
|
} else {
|
||||||
|
rootfs := gatefs.New(vfs.OS(runtime.GOROOT()), make(chan bool, 20))
|
||||||
|
fs.Bind("/", rootfs, "/", 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
|
||||||
|
corpus.InitVersionInfo()
|
||||||
|
go corpus.RunIndexer()
|
||||||
|
|
||||||
|
pres = godoc.NewPresentation(corpus)
|
||||||
|
pres.TabWidth = 8
|
||||||
|
pres.ShowPlayground = true
|
||||||
|
pres.ShowExamples = true
|
||||||
|
pres.DeclLinks = true
|
||||||
|
pres.NotesRx = regexp.MustCompile("BUG")
|
||||||
|
pres.GoogleAnalytics = os.Getenv("GODOC_ANALYTICS")
|
||||||
|
|
||||||
|
readTemplates(pres, true)
|
||||||
|
|
||||||
|
datastoreClient, memcacheClient := getClients()
|
||||||
|
|
||||||
|
// NOTE(cbro): registerHandlers registers itself against DefaultServeMux.
|
||||||
|
// The mux returned has host enforcement, so it's important to register
|
||||||
|
// against this mux and not DefaultServeMux.
|
||||||
|
mux := registerHandlers(pres)
|
||||||
|
dl.RegisterHandlers(mux, datastoreClient, memcacheClient)
|
||||||
|
short.RegisterHandlers(mux, datastoreClient, memcacheClient)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
http.HandleFunc("/_ah/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
io.WriteString(w, "ok")
|
||||||
|
})
|
||||||
|
|
||||||
|
http.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
io.WriteString(w, "User-agent: *\nDisallow: /search\n")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := redirect.LoadChangeMap("hg-git-mapping.bin"); err != nil {
|
||||||
|
log.Fatalf("LoadChangeMap: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("godoc initialization complete")
|
||||||
|
|
||||||
|
// TODO(cbro): add instrumentation via opencensus.
|
||||||
|
port := "8080"
|
||||||
|
if p := os.Getenv("PORT"); p != "" { // PORT is set by GAE flex.
|
||||||
|
port = p
|
||||||
|
}
|
||||||
|
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClients() (*datastore.Client, *memcache.Client) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
datastoreClient, err := datastore.NewClient(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "missing project") {
|
||||||
|
log.Fatalf("Missing datastore project. Set the DATASTORE_PROJECT_ID env variable. Use `gcloud beta emulators datastore` to start a local datastore.")
|
||||||
|
}
|
||||||
|
log.Fatalf("datastore.NewClient: %v.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
redisAddr := os.Getenv("GODOC_REDIS_ADDR")
|
||||||
|
if redisAddr == "" {
|
||||||
|
log.Fatalf("Missing redis server for godoc in production mode. set GODOC_REDIS_ADDR environment variable.")
|
||||||
|
}
|
||||||
|
memcacheClient := memcache.New(redisAddr)
|
||||||
|
return datastoreClient, memcacheClient
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// 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() {
|
||||||
|
runHTTPS = runHTTPSAutocert
|
||||||
|
certInit = certInitAutocert
|
||||||
|
wrapHTTPMux = wrapHTTPMuxAutocert
|
||||||
|
}
|
||||||
|
|
||||||
|
var autocertManager *autocert.Manager
|
||||||
|
|
||||||
|
func certInitAutocert() {
|
||||||
|
autocertManager = &autocert.Manager{
|
||||||
|
Cache: autocert.DirCache(*autoCertDirFlag),
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
}
|
||||||
|
if *autoCertHostFlag != "" {
|
||||||
|
autocertManager.HostPolicy = autocert.HostWhitelist(*autoCertHostFlag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHTTPSAutocert(h http.Handler) error {
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: h,
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
GetCertificate: autocertManager.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))
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapHTTPMuxAutocert(h http.Handler) http.Handler {
|
||||||
|
return autocertManager.HTTPHandler(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# NOTE(cbro): any changes to the docker command must also be
|
||||||
|
# made in docker-build in the Makefile.
|
||||||
|
#
|
||||||
|
# Variable substitutions must have a preceding underscore. See:
|
||||||
|
# https://cloud.google.com/cloud-build/docs/configuring-builds/substitute-variable-values#using_user-defined_substitutions
|
||||||
|
steps:
|
||||||
|
- name: 'gcr.io/cloud-builders/docker'
|
||||||
|
args: [
|
||||||
|
'build',
|
||||||
|
'-f=cmd/godoc/Dockerfile.prod',
|
||||||
|
'--build-arg=GO_REF=${_GO_REF}',
|
||||||
|
'--build-arg=TOOLS_HEAD=${_TOOLS_HEAD}',
|
||||||
|
'--build-arg=TOOLS_CLEAN=${_TOOLS_CLEAN}',
|
||||||
|
'--build-arg=DOCKER_TAG=${_DOCKER_TAG}',
|
||||||
|
'--build-arg=BUILD_ENV=cloudbuild',
|
||||||
|
'--tag=${_DOCKER_TAG}',
|
||||||
|
'.',
|
||||||
|
]
|
||||||
|
images: ['${_DOCKER_TAG}']
|
||||||
|
options:
|
||||||
|
machineType: 'N1_HIGHCPU_8' # building the godoc index takes a lot of memory.
|
|
@ -2,6 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !golangorg
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
|
@ -6,19 +6,50 @@
|
||||||
|
|
||||||
Godoc extracts and generates documentation for Go programs.
|
Godoc extracts and generates documentation for Go programs.
|
||||||
|
|
||||||
It runs as a web server and presents the documentation as a
|
It has two modes.
|
||||||
|
|
||||||
|
Without the -http flag, it runs in command-line mode and prints plain text
|
||||||
|
documentation to standard output and exits. If both a library package and
|
||||||
|
a command with the same name exists, using the prefix cmd/ will force
|
||||||
|
documentation on the command rather than the library package. If the -src
|
||||||
|
flag is specified, godoc prints the exported interface of a package in Go
|
||||||
|
source form, or the implementation of a specific exported language entity:
|
||||||
|
|
||||||
|
godoc fmt # documentation for package fmt
|
||||||
|
godoc fmt Printf # documentation for fmt.Printf
|
||||||
|
godoc cmd/go # force documentation for the go command
|
||||||
|
godoc -src fmt # fmt package interface in Go source form
|
||||||
|
godoc -src fmt Printf # implementation of fmt.Printf
|
||||||
|
|
||||||
|
In command-line mode, the -q flag enables search queries against a godoc running
|
||||||
|
as a webserver. If no explicit server address is specified with the -server flag,
|
||||||
|
godoc first tries localhost:6060 and then http://golang.org.
|
||||||
|
|
||||||
|
godoc -q Reader
|
||||||
|
godoc -q math.Sin
|
||||||
|
godoc -server=:6060 -q sin
|
||||||
|
|
||||||
|
With the -http flag, it runs as a web server and presents the documentation as a
|
||||||
web page.
|
web page.
|
||||||
|
|
||||||
godoc -http=:6060
|
godoc -http=:6060
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
godoc [flag]
|
godoc [flag] package [name ...]
|
||||||
|
|
||||||
The flags are:
|
The flags are:
|
||||||
|
|
||||||
-v
|
-v
|
||||||
verbose mode
|
verbose mode
|
||||||
|
-q
|
||||||
|
arguments are considered search queries: a legal query is a
|
||||||
|
single identifier (such as ToLower) or a qualified identifier
|
||||||
|
(such as math.Sin)
|
||||||
|
-src
|
||||||
|
print (exported) source in command-line mode
|
||||||
|
-tabwidth=4
|
||||||
|
width of tabs in units of spaces
|
||||||
-timestamps=true
|
-timestamps=true
|
||||||
show timestamps with directory listings
|
show timestamps with directory listings
|
||||||
-index
|
-index
|
||||||
|
@ -32,12 +63,7 @@ The flags are:
|
||||||
to the indexer (the indexer will never finish), a value of 1.0
|
to the indexer (the indexer will never finish), a value of 1.0
|
||||||
means that index creation is running at full throttle (other
|
means that index creation is running at full throttle (other
|
||||||
goroutines may get no time while the index is built)
|
goroutines may get no time while the index is built)
|
||||||
-index_interval=0
|
-links=true:
|
||||||
interval of indexing; a value of 0 sets it to 5 minutes, a
|
|
||||||
negative value indexes only once at startup
|
|
||||||
-play=false
|
|
||||||
enable playground
|
|
||||||
-links=true
|
|
||||||
link identifiers to their declarations
|
link identifiers to their declarations
|
||||||
-write_index=false
|
-write_index=false
|
||||||
write index to a file; the file name must be specified with
|
write index to a file; the file name must be specified with
|
||||||
|
@ -48,17 +74,21 @@ The flags are:
|
||||||
-notes="BUG"
|
-notes="BUG"
|
||||||
regular expression matching note markers to show
|
regular expression matching note markers to show
|
||||||
(e.g., "BUG|TODO", ".*")
|
(e.g., "BUG|TODO", ".*")
|
||||||
|
-html
|
||||||
|
print HTML in command-line mode
|
||||||
-goroot=$GOROOT
|
-goroot=$GOROOT
|
||||||
Go root directory
|
Go root directory
|
||||||
-http=addr
|
-http=addr
|
||||||
HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
|
HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
|
||||||
|
-server=addr
|
||||||
|
webserver address for command line searches
|
||||||
-analysis=type,pointer
|
-analysis=type,pointer
|
||||||
comma-separated list of analyses to perform
|
comma-separated list of analyses to perform
|
||||||
"type": display identifier resolution, type info, method sets,
|
"type": display identifier resolution, type info, method sets,
|
||||||
'implements', and static callees
|
'implements', and static callees
|
||||||
"pointer": display channel peers, callers and dynamic callees
|
"pointer": display channel peers, callers and dynamic callees
|
||||||
(significantly slower)
|
(significantly slower)
|
||||||
See https://golang.org/lib/godoc/analysis/help.html for details.
|
See http://golang.org/lib/godoc/analysis/help.html for details.
|
||||||
-templates=""
|
-templates=""
|
||||||
directory containing alternate template files; if set,
|
directory containing alternate template files; if set,
|
||||||
the directory may provide alternative template files
|
the directory may provide alternative template files
|
||||||
|
@ -73,7 +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
|
This behavior can be altered by providing an alternative $GOROOT with the -goroot
|
||||||
flag.
|
flag.
|
||||||
|
|
||||||
When the -index flag is set, a search index is maintained.
|
When godoc runs as a web server and -index is set, a search index is maintained.
|
||||||
The index is created at startup.
|
The index is created at startup.
|
||||||
|
|
||||||
The index contains both identifier and full text search information (searchable
|
The index contains both identifier and full text search information (searchable
|
||||||
|
@ -81,19 +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
|
can be set with the -maxresults flag; if set to 0, no full text results are
|
||||||
shown, and only an identifier index but no full text search index is created.
|
shown, and only an identifier index but no full text search index is created.
|
||||||
|
|
||||||
By default, godoc uses the system's GOOS/GOARCH. You can provide the URL parameters
|
By default, godoc uses the system's GOOS/GOARCH; in command-line mode you can
|
||||||
"GOOS" and "GOARCH" to set the output on the web page for the target system.
|
set the GOOS/GOARCH environment variables to get output for the system specified.
|
||||||
|
If -http was specified you can provide the URL parameters "GOOS" and "GOARCH"
|
||||||
|
to set the output on the web page.
|
||||||
|
|
||||||
The presentation mode of web pages served by godoc can be controlled with the
|
The presentation mode of web pages served by godoc can be controlled with the
|
||||||
"m" URL parameter; it accepts a comma-separated list of flag names as value:
|
"m" URL parameter; it accepts a comma-separated list of flag names as value:
|
||||||
|
|
||||||
all show documentation for all declarations, not just the exported ones
|
all show documentation for all declarations, not just the exported ones
|
||||||
methods show all embedded methods, not just those of unexported anonymous fields
|
methods show all embedded methods, not just those of unexported anonymous fields
|
||||||
src show the original source code rather than the extracted documentation
|
src show the original source code rather then the extracted documentation
|
||||||
|
text present the page in textual (command-line) form rather than HTML
|
||||||
flat present flat (not indented) directory listings using full paths
|
flat present flat (not indented) directory listings using full paths
|
||||||
|
|
||||||
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation
|
For instance, http://golang.org/pkg/math/big/?m=all,text shows the documentation
|
||||||
for all (not just the exported) declarations of package big.
|
for all (not just the exported) declarations of package big, in textual form (as
|
||||||
|
it would appear when using godoc from the command line: "godoc -src math/big .*").
|
||||||
|
|
||||||
By default, godoc serves files from the file system of the underlying OS.
|
By default, godoc serves files from the file system of the underlying OS.
|
||||||
Instead, a .zip file may be provided via the -zip flag, which contains
|
Instead, a .zip file may be provided via the -zip flag, which contains
|
||||||
|
@ -109,11 +143,11 @@ one may run godoc as follows:
|
||||||
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
|
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
|
||||||
|
|
||||||
Godoc documentation is converted to HTML or to text using the go/doc package;
|
Godoc documentation is converted to HTML or to text using the go/doc package;
|
||||||
see https://golang.org/pkg/go/doc/#ToHTML for the exact rules.
|
see http://golang.org/pkg/go/doc/#ToHTML for the exact rules.
|
||||||
Godoc also shows example code that is runnable by the testing package;
|
Godoc also shows example code that is runnable by the testing package;
|
||||||
see https://golang.org/pkg/testing/#hdr-Examples for the conventions.
|
see http://golang.org/pkg/testing/#hdr-Examples for the conventions.
|
||||||
See "Godoc: documenting Go code" for how to write good comments for godoc:
|
See "Godoc: documenting Go code" for how to write good comments for godoc:
|
||||||
https://golang.org/doc/articles/godoc_documenting_go_code.html
|
http://golang.org/doc/articles/godoc_documenting_go_code.html
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package main // import "golang.org/x/tools/cmd/godoc"
|
package main // import "golang.org/x/tools/cmd/godoc"
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/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 .zip file representing the $GOROOT file system
|
||||||
|
# and computes the corresponding search index files.
|
||||||
|
#
|
||||||
|
# These are used in production (see app.prod.yaml)
|
||||||
|
|
||||||
|
set -e -u -x
|
||||||
|
|
||||||
|
ZIPFILE=godoc.zip
|
||||||
|
INDEXFILE=godoc.index
|
||||||
|
SPLITFILES=index.split.
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo "error: $1"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
install() {
|
||||||
|
go install
|
||||||
|
}
|
||||||
|
|
||||||
|
getArgs() {
|
||||||
|
if [ ! -v GODOC_DOCSET ]; then
|
||||||
|
GODOC_DOCSET="$(go env GOROOT)"
|
||||||
|
echo "GODOC_DOCSET not set explicitly, using GOROOT instead"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# safety checks
|
||||||
|
if [ ! -d "$GODOC_DOCSET" ]; then
|
||||||
|
error "$GODOC_DOCSET is not a directory"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# reporting
|
||||||
|
echo "GODOC_DOCSET = $GODOC_DOCSET"
|
||||||
|
}
|
||||||
|
|
||||||
|
makeZipfile() {
|
||||||
|
echo "*** make $ZIPFILE"
|
||||||
|
rm -f $ZIPFILE goroot
|
||||||
|
ln -s "$GODOC_DOCSET" goroot
|
||||||
|
zip -q -r $ZIPFILE goroot/* # glob to ignore dotfiles (like .git)
|
||||||
|
rm goroot
|
||||||
|
}
|
||||||
|
|
||||||
|
makeIndexfile() {
|
||||||
|
echo "*** make $INDEXFILE"
|
||||||
|
godoc=$(go env GOPATH)/bin/godoc
|
||||||
|
# NOTE: run godoc without GOPATH set. Otherwise third-party packages will end up in the index.
|
||||||
|
GOPATH= $godoc -write_index -goroot goroot -index_files=$INDEXFILE -zip=$ZIPFILE
|
||||||
|
}
|
||||||
|
|
||||||
|
splitIndexfile() {
|
||||||
|
echo "*** split $INDEXFILE"
|
||||||
|
rm -f $SPLITFILES*
|
||||||
|
split -b8m $INDEXFILE $SPLITFILES
|
||||||
|
}
|
||||||
|
|
||||||
|
cd $(dirname $0)
|
||||||
|
|
||||||
|
install
|
||||||
|
getArgs "$@"
|
||||||
|
makeZipfile
|
||||||
|
makeIndexfile
|
||||||
|
splitIndexfile
|
||||||
|
rm $INDEXFILE
|
||||||
|
|
||||||
|
echo "*** setup complete"
|
|
@ -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 }
|
|
@ -32,10 +32,6 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
|
||||||
if runtime.GOARCH == "arm" {
|
if runtime.GOARCH == "arm" {
|
||||||
t.Skip("skipping test on arm platforms; too slow")
|
t.Skip("skipping test on arm platforms; too slow")
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "android" {
|
|
||||||
t.Skipf("the dependencies are not available on android")
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "godoc-regtest-")
|
tmp, err := ioutil.TempDir("", "godoc-regtest-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -58,6 +54,91 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
|
||||||
return bin, func() { os.RemoveAll(tmp) }
|
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 {
|
func serverAddress(t *testing.T) string {
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -138,46 +219,6 @@ func killAndWait(cmd *exec.Cmd) {
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestURL(t *testing.T) {
|
|
||||||
if runtime.GOOS == "plan9" {
|
|
||||||
t.Skip("skipping on plan9; fails to start up quickly enough")
|
|
||||||
}
|
|
||||||
bin, cleanup := buildGodoc(t)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
testcase := func(url string, contents string) func(t *testing.T) {
|
|
||||||
return func(t *testing.T) {
|
|
||||||
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
|
|
||||||
|
|
||||||
args := []string{fmt.Sprintf("-url=%s", url)}
|
|
||||||
cmd := exec.Command(bin, args...)
|
|
||||||
cmd.Stdout = stdout
|
|
||||||
cmd.Stderr = stderr
|
|
||||||
cmd.Args[0] = "godoc"
|
|
||||||
|
|
||||||
// Set GOPATH variable to non-existing path
|
|
||||||
// and GOPROXY=off to disable module fetches.
|
|
||||||
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
|
|
||||||
// (We don't want the indexer looking at the local workspace during tests.)
|
|
||||||
cmd.Env = append(os.Environ(),
|
|
||||||
"GOPATH=does_not_exist",
|
|
||||||
"GOPROXY=off",
|
|
||||||
"GO111MODULE=off")
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
t.Fatalf("failed to run godoc -url=%q: %s\nstderr:\n%s", url, err, stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(stdout.String(), contents) {
|
|
||||||
t.Errorf("did not find substring %q in output of godoc -url=%q:\n%s", contents, url, stdout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("index", testcase("/", "Go is an open source programming language"))
|
|
||||||
t.Run("fmt", testcase("/pkg/fmt", "Package fmt implements formatted I/O"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic integration test for godoc HTTP interface.
|
// Basic integration test for godoc HTTP interface.
|
||||||
func TestWeb(t *testing.T) {
|
func TestWeb(t *testing.T) {
|
||||||
testWeb(t, false)
|
testWeb(t, false)
|
||||||
|
@ -194,7 +235,7 @@ func TestWebIndex(t *testing.T) {
|
||||||
// Basic integration test for godoc HTTP interface.
|
// Basic integration test for godoc HTTP interface.
|
||||||
func testWeb(t *testing.T, withIndex bool) {
|
func testWeb(t *testing.T, withIndex bool) {
|
||||||
if runtime.GOOS == "plan9" {
|
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)
|
bin, cleanup := buildGodoc(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
@ -208,14 +249,10 @@ func testWeb(t *testing.T, withIndex bool) {
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Args[0] = "godoc"
|
cmd.Args[0] = "godoc"
|
||||||
|
|
||||||
// Set GOPATH variable to non-existing path
|
// 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 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.)
|
// (We don't want the indexer looking at the local workspace during tests.)
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(), "GOPATH=does_not_exist")
|
||||||
"GOPATH=does_not_exist",
|
|
||||||
"GOPROXY=off",
|
|
||||||
"GO111MODULE=off")
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
t.Fatalf("failed to start godoc: %s", err)
|
t.Fatalf("failed to start godoc: %s", err)
|
||||||
|
@ -427,8 +464,6 @@ func main() { print(lib.V) }
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", filepath.Join(tmpdir, "goroot")))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", filepath.Join(tmpdir, "goroot")))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath")))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath")))
|
||||||
cmd.Env = append(cmd.Env, "GO111MODULE=off")
|
|
||||||
cmd.Env = append(cmd.Env, "GOPROXY=off")
|
|
||||||
cmd.Stdout = os.Stderr
|
cmd.Stdout = os.Stderr
|
||||||
stderr, err := cmd.StderrPipe()
|
stderr, err := cmd.StderrPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"golang.org/x/tools/godoc"
|
"golang.org/x/tools/godoc"
|
||||||
"golang.org/x/tools/godoc/golangorgenv"
|
"golang.org/x/tools/godoc/env"
|
||||||
"golang.org/x/tools/godoc/redirect"
|
"golang.org/x/tools/godoc/redirect"
|
||||||
"golang.org/x/tools/godoc/vfs"
|
"golang.org/x/tools/godoc/vfs"
|
||||||
)
|
)
|
||||||
|
@ -40,7 +40,7 @@ type hostEnforcerHandler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if !golangorgenv.EnforceHosts() {
|
if !env.EnforceHosts() {
|
||||||
h.h.ServeHTTP(w, r)
|
h.h.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,11 @@ func readTemplate(name string) *template.Template {
|
||||||
return t
|
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")
|
codewalkHTML = readTemplate("codewalk.html")
|
||||||
codewalkdirHTML = readTemplate("codewalkdir.html")
|
codewalkdirHTML = readTemplate("codewalkdir.html")
|
||||||
p.CallGraphHTML = readTemplate("callgraph.html")
|
p.CallGraphHTML = readTemplate("callgraph.html")
|
||||||
|
@ -130,6 +134,7 @@ func readTemplates(p *godoc.Presentation) {
|
||||||
p.SearchTxtHTML = readTemplate("searchtxt.html")
|
p.SearchTxtHTML = readTemplate("searchtxt.html")
|
||||||
p.SearchDescXML = readTemplate("opensearch.xml")
|
p.SearchDescXML = readTemplate("opensearch.xml")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fmtResponse struct {
|
type fmtResponse struct {
|
||||||
Body string
|
Body string
|
||||||
|
|
Binary file not shown.
|
@ -14,18 +14,28 @@
|
||||||
// (idea is if you say import "compress/zlib", you go to
|
// (idea is if you say import "compress/zlib", you go to
|
||||||
// http://godoc/pkg/compress/zlib)
|
// http://godoc/pkg/compress/zlib)
|
||||||
//
|
//
|
||||||
|
// Command-line interface:
|
||||||
|
//
|
||||||
|
// godoc packagepath [name ...]
|
||||||
|
//
|
||||||
|
// godoc compress/zlib
|
||||||
|
// - prints doc for package compress/zlib
|
||||||
|
// godoc crypto/block Cipher NewCMAC
|
||||||
|
// - prints doc for Cipher and NewCMAC in package crypto/block
|
||||||
|
|
||||||
|
// +build !golangorg
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
|
||||||
_ "expvar" // to serve /debug/vars
|
_ "expvar" // to serve /debug/vars
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
_ "net/http/pprof" // to serve /debug/pprof/*
|
_ "net/http/pprof" // to serve /debug/pprof/*
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -43,7 +53,7 @@ import (
|
||||||
"golang.org/x/tools/godoc/vfs/zipfs"
|
"golang.org/x/tools/godoc/vfs/zipfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultAddr = "localhost:6060" // default webserver address
|
const defaultAddr = ":6060" // default webserver address
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// file system to serve
|
// file system to serve
|
||||||
|
@ -56,11 +66,18 @@ var (
|
||||||
analysisFlag = flag.String("analysis", "", `comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html`)
|
analysisFlag = flag.String("analysis", "", `comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html`)
|
||||||
|
|
||||||
// network
|
// network
|
||||||
httpAddr = flag.String("http", defaultAddr, "HTTP service address")
|
httpAddr = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')")
|
||||||
|
serverAddr = flag.String("server", "", "webserver address for command line searches")
|
||||||
|
|
||||||
// layout control
|
// layout control
|
||||||
|
html = flag.Bool("html", false, "print HTML in command-line mode")
|
||||||
|
srcMode = flag.Bool("src", false, "print (exported) source in command-line mode")
|
||||||
|
allMode = flag.Bool("all", false, "include unexported identifiers in command-line mode")
|
||||||
urlFlag = flag.String("url", "", "print HTML for named URL")
|
urlFlag = flag.String("url", "", "print HTML for named URL")
|
||||||
|
|
||||||
|
// command-line searches
|
||||||
|
query = flag.Bool("q", false, "arguments are considered search queries")
|
||||||
|
|
||||||
verbose = flag.Bool("v", false, "verbose mode")
|
verbose = flag.Bool("v", false, "verbose mode")
|
||||||
|
|
||||||
// file system roots
|
// file system roots
|
||||||
|
@ -68,9 +85,11 @@ var (
|
||||||
goroot = flag.String("goroot", findGOROOT(), "Go root directory")
|
goroot = flag.String("goroot", findGOROOT(), "Go root directory")
|
||||||
|
|
||||||
// layout control
|
// layout control
|
||||||
|
tabWidth = flag.Int("tabwidth", 4, "tab width")
|
||||||
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
|
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
|
||||||
templateDir = flag.String("templates", "", "load templates/JS/CSS from disk in this directory")
|
templateDir = flag.String("templates", "", "load templates/JS/CSS from disk in this directory")
|
||||||
showPlayground = flag.Bool("play", false, "enable playground")
|
showPlayground = flag.Bool("play", false, "enable playground in web interface")
|
||||||
|
showExamples = flag.Bool("ex", false, "show examples in command line mode")
|
||||||
declLinks = flag.Bool("links", true, "link identifiers to their declarations")
|
declLinks = flag.Bool("links", true, "link identifiers to their declarations")
|
||||||
|
|
||||||
// search index
|
// search index
|
||||||
|
@ -84,19 +103,10 @@ var (
|
||||||
notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show")
|
notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show")
|
||||||
)
|
)
|
||||||
|
|
||||||
// An httpResponseRecorder is an http.ResponseWriter
|
|
||||||
type httpResponseRecorder struct {
|
|
||||||
body *bytes.Buffer
|
|
||||||
header http.Header
|
|
||||||
code int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *httpResponseRecorder) Header() http.Header { return w.header }
|
|
||||||
func (w *httpResponseRecorder) Write(b []byte) (int, error) { return w.body.Write(b) }
|
|
||||||
func (w *httpResponseRecorder) WriteHeader(code int) { w.code = code }
|
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Fprintf(os.Stderr, "usage: godoc -http="+defaultAddr+"\n")
|
fmt.Fprintf(os.Stderr,
|
||||||
|
"usage: godoc package [name ...]\n"+
|
||||||
|
" godoc -http="+defaultAddr+"\n")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
@ -123,22 +133,22 @@ func handleURLFlag() {
|
||||||
|
|
||||||
// Invoke default HTTP handler to serve request
|
// Invoke default HTTP handler to serve request
|
||||||
// to our buffering httpWriter.
|
// to our buffering httpWriter.
|
||||||
w := &httpResponseRecorder{code: 200, header: make(http.Header), body: new(bytes.Buffer)}
|
w := httptest.NewRecorder()
|
||||||
http.DefaultServeMux.ServeHTTP(w, req)
|
http.DefaultServeMux.ServeHTTP(w, req)
|
||||||
|
|
||||||
// Return data, error, or follow redirect.
|
// Return data, error, or follow redirect.
|
||||||
switch w.code {
|
switch w.Code {
|
||||||
case 200: // ok
|
case 200: // ok
|
||||||
os.Stdout.Write(w.body.Bytes())
|
os.Stdout.Write(w.Body.Bytes())
|
||||||
return
|
return
|
||||||
case 301, 302, 303, 307: // redirect
|
case 301, 302, 303, 307: // redirect
|
||||||
redirect := w.header.Get("Location")
|
redirect := w.HeaderMap.Get("Location")
|
||||||
if redirect == "" {
|
if redirect == "" {
|
||||||
log.Fatalf("HTTP %d without Location header", w.code)
|
log.Fatalf("HTTP %d without Location header", w.Code)
|
||||||
}
|
}
|
||||||
urlstr = redirect
|
urlstr = redirect
|
||||||
default:
|
default:
|
||||||
log.Fatalf("HTTP error %d", w.code)
|
log.Fatalf("HTTP error %d", w.Code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Fatalf("too many redirects")
|
log.Fatalf("too many redirects")
|
||||||
|
@ -161,20 +171,23 @@ func main() {
|
||||||
|
|
||||||
playEnabled = *showPlayground
|
playEnabled = *showPlayground
|
||||||
|
|
||||||
// Check usage.
|
// Check usage: server and no args.
|
||||||
if flag.NArg() > 0 {
|
if (*httpAddr != "" || *urlFlag != "") && (flag.NArg() > 0) {
|
||||||
fmt.Fprintln(os.Stderr, `Unexpected arguments. Use "go doc" for command-line help output instead. For example, "go doc fmt.Printf".`)
|
fmt.Fprintln(os.Stderr, "can't use -http with args.")
|
||||||
usage()
|
|
||||||
}
|
|
||||||
if *httpAddr == "" && *urlFlag == "" && !*writeIndex {
|
|
||||||
fmt.Fprintln(os.Stderr, "At least one of -http, -url, or -write_index must be set to a non-zero value.")
|
|
||||||
usage()
|
usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the resolved goroot.
|
// Check usage: command line args or index creation mode.
|
||||||
|
if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex {
|
||||||
|
fmt.Fprintln(os.Stderr, "missing args.")
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the resolved goroot.
|
||||||
vfs.GOROOT = *goroot
|
vfs.GOROOT = *goroot
|
||||||
|
|
||||||
fsGate := make(chan bool, 20)
|
var fsGate chan bool
|
||||||
|
fsGate = make(chan bool, 20)
|
||||||
|
|
||||||
// Determine file system to use.
|
// Determine file system to use.
|
||||||
if *zipfile == "" {
|
if *zipfile == "" {
|
||||||
|
@ -201,6 +214,8 @@ func main() {
|
||||||
fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
|
fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpMode := *httpAddr != ""
|
||||||
|
|
||||||
var typeAnalysis, pointerAnalysis bool
|
var typeAnalysis, pointerAnalysis bool
|
||||||
if *analysisFlag != "" {
|
if *analysisFlag != "" {
|
||||||
for _, a := range strings.Split(*analysisFlag, ",") {
|
for _, a := range strings.Split(*analysisFlag, ",") {
|
||||||
|
@ -218,7 +233,7 @@ func main() {
|
||||||
corpus := godoc.NewCorpus(fs)
|
corpus := godoc.NewCorpus(fs)
|
||||||
corpus.Verbose = *verbose
|
corpus.Verbose = *verbose
|
||||||
corpus.MaxResults = *maxResults
|
corpus.MaxResults = *maxResults
|
||||||
corpus.IndexEnabled = *indexEnabled
|
corpus.IndexEnabled = *indexEnabled && httpMode
|
||||||
if *maxResults == 0 {
|
if *maxResults == 0 {
|
||||||
corpus.IndexFullText = false
|
corpus.IndexFullText = false
|
||||||
}
|
}
|
||||||
|
@ -226,12 +241,16 @@ func main() {
|
||||||
corpus.IndexDirectory = indexDirectoryDefault
|
corpus.IndexDirectory = indexDirectoryDefault
|
||||||
corpus.IndexThrottle = *indexThrottle
|
corpus.IndexThrottle = *indexThrottle
|
||||||
corpus.IndexInterval = *indexInterval
|
corpus.IndexInterval = *indexInterval
|
||||||
if *writeIndex || *urlFlag != "" {
|
if *writeIndex {
|
||||||
corpus.IndexThrottle = 1.0
|
corpus.IndexThrottle = 1.0
|
||||||
corpus.IndexEnabled = true
|
corpus.IndexEnabled = true
|
||||||
initCorpus(corpus)
|
}
|
||||||
} else {
|
if *writeIndex || httpMode || *urlFlag != "" {
|
||||||
|
if httpMode {
|
||||||
go initCorpus(corpus)
|
go initCorpus(corpus)
|
||||||
|
} else {
|
||||||
|
initCorpus(corpus)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the version info before readTemplates, which saves
|
// Initialize the version info before readTemplates, which saves
|
||||||
|
@ -239,14 +258,19 @@ func main() {
|
||||||
corpus.InitVersionInfo()
|
corpus.InitVersionInfo()
|
||||||
|
|
||||||
pres = godoc.NewPresentation(corpus)
|
pres = godoc.NewPresentation(corpus)
|
||||||
|
pres.TabWidth = *tabWidth
|
||||||
pres.ShowTimestamps = *showTimestamps
|
pres.ShowTimestamps = *showTimestamps
|
||||||
pres.ShowPlayground = *showPlayground
|
pres.ShowPlayground = *showPlayground
|
||||||
|
pres.ShowExamples = *showExamples
|
||||||
pres.DeclLinks = *declLinks
|
pres.DeclLinks = *declLinks
|
||||||
|
pres.SrcMode = *srcMode
|
||||||
|
pres.HTMLMode = *html
|
||||||
|
pres.AllMode = *allMode
|
||||||
if *notesRx != "" {
|
if *notesRx != "" {
|
||||||
pres.NotesRx = regexp.MustCompile(*notesRx)
|
pres.NotesRx = regexp.MustCompile(*notesRx)
|
||||||
}
|
}
|
||||||
|
|
||||||
readTemplates(pres)
|
readTemplates(pres, httpMode || *urlFlag != "")
|
||||||
registerHandlers(pres)
|
registerHandlers(pres)
|
||||||
|
|
||||||
if *writeIndex {
|
if *writeIndex {
|
||||||
|
@ -281,12 +305,15 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if httpMode {
|
||||||
|
// HTTP server mode.
|
||||||
var handler http.Handler = http.DefaultServeMux
|
var handler http.Handler = http.DefaultServeMux
|
||||||
if *verbose {
|
if *verbose {
|
||||||
log.Printf("Go Documentation Server")
|
log.Printf("Go Documentation Server")
|
||||||
log.Printf("version = %s", runtime.Version())
|
log.Printf("version = %s", runtime.Version())
|
||||||
log.Printf("address = %s", *httpAddr)
|
log.Printf("address = %s", *httpAddr)
|
||||||
log.Printf("goroot = %s", *goroot)
|
log.Printf("goroot = %s", *goroot)
|
||||||
|
log.Printf("tabwidth = %d", *tabWidth)
|
||||||
switch {
|
switch {
|
||||||
case !*indexEnabled:
|
case !*indexEnabled:
|
||||||
log.Print("search index disabled")
|
log.Print("search index disabled")
|
||||||
|
@ -327,6 +354,19 @@ func main() {
|
||||||
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
|
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
|
||||||
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
|
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *query {
|
||||||
|
handleRemoteSearch()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
build.Default.GOROOT = *goroot
|
||||||
|
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
|
// Hooks that are set non-nil in autocert.go if the "autocert" build tag
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !golangorg
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// This package registers "/compile" and "/share" handlers
|
// This package registers "/compile" and "/share" handlers
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Regression tests to run against a production instance of godoc.
|
||||||
|
|
||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var host = flag.String("regtest.host", "", "host to run regression test against")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
*host = strings.TrimSuffix(*host, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLiveServer(t *testing.T) {
|
||||||
|
if *host == "" {
|
||||||
|
t.Skip("regtest.host flag missing.")
|
||||||
|
}
|
||||||
|
substringTests := []struct {
|
||||||
|
Message string
|
||||||
|
Path string
|
||||||
|
Substring string
|
||||||
|
Regexp string
|
||||||
|
NoAnalytics bool // expect the response to not contain GA.
|
||||||
|
PostBody string
|
||||||
|
StatusCode int // if 0, expect 2xx status code.
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Path: "/doc/faq",
|
||||||
|
Substring: "What is the purpose of the project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/pkg/",
|
||||||
|
Substring: "Package tar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/pkg/os/",
|
||||||
|
Substring: "func Open",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/pkg/net/http/",
|
||||||
|
Substring: `title="Added in Go 1.11"`,
|
||||||
|
Message: "version information not present - failed InitVersionInfo?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/robots.txt",
|
||||||
|
Substring: "Disallow: /search",
|
||||||
|
Message: "robots not present - not deployed from Dockerfile?",
|
||||||
|
NoAnalytics: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/change/75944e2e3a63",
|
||||||
|
Substring: "bdb10cf",
|
||||||
|
Message: "no change redirect - hg to git mapping not registered?",
|
||||||
|
NoAnalytics: true,
|
||||||
|
StatusCode: 302,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/dl/",
|
||||||
|
Substring: "go1.11.windows-amd64.msi",
|
||||||
|
Message: "missing data on dl page - misconfiguration of datastore?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/dl/?mode=json",
|
||||||
|
Substring: ".windows-amd64.msi",
|
||||||
|
NoAnalytics: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: "broken shortlinks - misconfiguration of datastore or memcache?",
|
||||||
|
Path: "/s/go2design",
|
||||||
|
Regexp: "proposal.*Found",
|
||||||
|
NoAnalytics: true,
|
||||||
|
StatusCode: 302,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: "incorrect search result - broken index?",
|
||||||
|
Path: "/search?q=IsDir",
|
||||||
|
Substring: "src/os/types.go",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/compile",
|
||||||
|
PostBody: "body=" + url.QueryEscape("package main; func main() { print(6*7); }"),
|
||||||
|
Regexp: `^{"compile_errors":"","output":"42"}$`,
|
||||||
|
NoAnalytics: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/compile",
|
||||||
|
PostBody: "body=" + url.QueryEscape("//empty"),
|
||||||
|
Substring: "expected 'package', found 'EOF'",
|
||||||
|
NoAnalytics: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/compile",
|
||||||
|
PostBody: "version=2&body=package+main%3Bimport+(%22fmt%22%3B%22time%22)%3Bfunc+main()%7Bfmt.Print(%22A%22)%3Btime.Sleep(time.Second)%3Bfmt.Print(%22B%22)%7D",
|
||||||
|
Regexp: `^{"Errors":"","Events":\[{"Message":"A","Kind":"stdout","Delay":0},{"Message":"B","Kind":"stdout","Delay":1000000000}\]}$`,
|
||||||
|
NoAnalytics: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/share",
|
||||||
|
PostBody: "package main",
|
||||||
|
Substring: "", // just check it is a 2xx.
|
||||||
|
NoAnalytics: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range substringTests {
|
||||||
|
t.Run(tc.Path, func(t *testing.T) {
|
||||||
|
method := "GET"
|
||||||
|
var reqBody io.Reader
|
||||||
|
if tc.PostBody != "" {
|
||||||
|
method = "POST"
|
||||||
|
reqBody = strings.NewReader(tc.PostBody)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(method, *host+tc.Path, reqBody)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewRequest: %v", err)
|
||||||
|
}
|
||||||
|
if reqBody != nil {
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultTransport.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RoundTrip: %v", err)
|
||||||
|
}
|
||||||
|
if tc.StatusCode == 0 {
|
||||||
|
if resp.StatusCode > 299 {
|
||||||
|
t.Errorf("Non-OK status code: %v", resp.StatusCode)
|
||||||
|
}
|
||||||
|
} else if tc.StatusCode != resp.StatusCode {
|
||||||
|
t.Errorf("StatusCode; got %v, want %v", resp.StatusCode, tc.StatusCode)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadAll: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const googleAnalyticsID = "UA-11222381-2" // golang.org analytics ID
|
||||||
|
if !tc.NoAnalytics && !bytes.Contains(body, []byte(googleAnalyticsID)) {
|
||||||
|
t.Errorf("want response to contain analytics tracking ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.Substring != "" {
|
||||||
|
tc.Regexp = regexp.QuoteMeta(tc.Substring)
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile(tc.Regexp)
|
||||||
|
|
||||||
|
if !re.Match(body) {
|
||||||
|
t.Log("------ actual output -------")
|
||||||
|
t.Log(string(body))
|
||||||
|
t.Log("----------------------------")
|
||||||
|
if tc.Message != "" {
|
||||||
|
t.Log(tc.Message)
|
||||||
|
}
|
||||||
|
t.Fatalf("wanted response to match %s", tc.Regexp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 !golangorg
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleRemoteSearch() {
|
||||||
|
// Command-line queries.
|
||||||
|
for i := 0; i < flag.NArg(); i++ {
|
||||||
|
res, err := remoteSearch(flag.Arg(i))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("remoteSearch: %s", err)
|
||||||
|
}
|
||||||
|
io.Copy(os.Stdout, res.Body)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// remoteSearchURL returns the search URL for a given query as needed by
|
||||||
|
// remoteSearch. If html is set, an html result is requested; otherwise
|
||||||
|
// the result is in textual form.
|
||||||
|
// Adjust this function as necessary if modeNames or FormValue parameters
|
||||||
|
// change.
|
||||||
|
func remoteSearchURL(query string, html bool) string {
|
||||||
|
s := "/search?m=text&q="
|
||||||
|
if html {
|
||||||
|
s = "/search?q="
|
||||||
|
}
|
||||||
|
return s + url.QueryEscape(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func remoteSearch(query string) (res *http.Response, err error) {
|
||||||
|
// list of addresses to try
|
||||||
|
var addrs []string
|
||||||
|
if *serverAddr != "" {
|
||||||
|
// explicit server address - only try this one
|
||||||
|
addrs = []string{*serverAddr}
|
||||||
|
} else {
|
||||||
|
addrs = []string{
|
||||||
|
defaultAddr,
|
||||||
|
"golang.org",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remote search
|
||||||
|
search := remoteSearchURL(query, *html)
|
||||||
|
for _, addr := range addrs {
|
||||||
|
url := "http://" + addr + search
|
||||||
|
res, err = http.Get(url)
|
||||||
|
if err == nil && res.StatusCode == http.StatusOK {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && res.StatusCode != http.StatusOK {
|
||||||
|
err = errors.New(res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
// 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"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
|
||||||
|
"arch": {"https://go.googlesource.com/arch", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
"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"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
"mobile": {"https://go.googlesource.com/mobile", "git"},
|
||||||
|
"net": {"https://go.googlesource.com/net", "git"},
|
||||||
|
"oauth2": {"https://go.googlesource.com/oauth2", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
"perf": {"https://go.googlesource.com/perf", "git"},
|
||||||
|
"playground": {"https://go.googlesource.com/playground", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
"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"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
"term": {"https://go.googlesource.com/term", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
"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"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
"website": {"https://go.googlesource.com/website", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
"xerrors": {"https://go.googlesource.com/xerrors", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
`))
|
|
@ -13,6 +13,8 @@ For emacs, make sure you have the latest go-mode.el:
|
||||||
https://github.com/dominikh/go-mode.el
|
https://github.com/dominikh/go-mode.el
|
||||||
Then in your .emacs file:
|
Then in your .emacs file:
|
||||||
(setq gofmt-command "goimports")
|
(setq gofmt-command "goimports")
|
||||||
|
(add-to-list 'load-path "/home/you/somewhere/emacs/")
|
||||||
|
(require 'go-mode-autoloads)
|
||||||
(add-hook 'before-save-hook 'gofmt-before-save)
|
(add-hook 'before-save-hook 'gofmt-before-save)
|
||||||
|
|
||||||
For vim, set "gofmt_command" to "goimports":
|
For vim, set "gofmt_command" to "goimports":
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
|
||||||
"go/scanner"
|
"go/scanner"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -22,7 +21,7 @@ import (
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/imports"
|
"golang.org/x/tools/imports"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -31,7 +30,6 @@ var (
|
||||||
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
|
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
|
||||||
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
|
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
|
||||||
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
|
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
|
||||||
|
|
||||||
verbose bool // verbose logging
|
verbose bool // verbose logging
|
||||||
|
|
||||||
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
|
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
|
||||||
|
@ -43,19 +41,13 @@ var (
|
||||||
TabIndent: true,
|
TabIndent: true,
|
||||||
Comments: true,
|
Comments: true,
|
||||||
Fragment: true,
|
Fragment: true,
|
||||||
// This environment, and its caches, will be reused for the whole run.
|
|
||||||
Env: &imports.ProcessEnv{
|
|
||||||
GOPATH: build.Default.GOPATH,
|
|
||||||
GOROOT: build.Default.GOROOT,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
exitCode = 0
|
exitCode = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
|
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
|
||||||
flag.StringVar(&options.Env.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; comma-separated list")
|
flag.StringVar(&imports.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; 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.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func report(err error) {
|
func report(err error) {
|
||||||
|
@ -258,7 +250,7 @@ func gofmtMain() {
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
||||||
options.Env.Debug = true
|
imports.Debug = true
|
||||||
}
|
}
|
||||||
if options.TabWidth < 0 {
|
if options.TabWidth < 0 {
|
||||||
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
|
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
gopls*.vsix
|
|
||||||
out
|
|
||||||
node_modules
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Launch Extension",
|
|
||||||
"type": "extensionHost",
|
|
||||||
"request": "launch",
|
|
||||||
"runtimeExecutable": "${execPath}",
|
|
||||||
"args": [
|
|
||||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
|
||||||
],
|
|
||||||
"stopOnEntry": false,
|
|
||||||
"sourceMaps": true,
|
|
||||||
"outFiles": [
|
|
||||||
"${workspaceFolder}/out/**/*.js"
|
|
||||||
],
|
|
||||||
"preLaunchTask": "npm"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
// Available variables which can be used inside of strings.
|
|
||||||
// ${workspaceFolder}: the root folder of the team
|
|
||||||
// ${file}: the current opened file
|
|
||||||
// ${fileBasename}: the current opened file's basename
|
|
||||||
// ${fileDirname}: the current opened file's dirname
|
|
||||||
// ${fileExtname}: the current opened file's extension
|
|
||||||
// ${cwd}: the current working directory of the spawned process
|
|
||||||
// A task runner that calls the Typescript compiler (tsc) and
|
|
||||||
// compiles the extension.
|
|
||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
// we want to run npm
|
|
||||||
"command": "npm",
|
|
||||||
// the command is a shell script
|
|
||||||
"type": "shell",
|
|
||||||
// show the output window only if unrecognized errors occur.
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "silent"
|
|
||||||
},
|
|
||||||
// we run the custom script "compile" as defined in package.json
|
|
||||||
"args": [
|
|
||||||
"run",
|
|
||||||
"compile"
|
|
||||||
],
|
|
||||||
// The tsc compiler is started in watching mode
|
|
||||||
"isBackground": true,
|
|
||||||
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
|
||||||
"problemMatcher": "$tsc-watch"
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
# gopls testing extension
|
|
||||||
|
|
||||||
An extension for debugging the Go Language Server provided by
|
|
||||||
https://golang.org/x/tools/cmd/gopls. The code for this extension comes from
|
|
||||||
a combination of
|
|
||||||
https://github.com/Microsoft/vscode-extension-samples/blob/master/lsp-sample
|
|
||||||
and https://github.com/Microsoft/vscode-go.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
* Diagnostics (on file change)
|
|
||||||
* Completion (Ctrl + Space)
|
|
||||||
* Jump to definition (F12 or right-click -> Go to Definition)
|
|
||||||
* Signature help (Ctrl + Shift + Space)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
To package the extension, run `vsce package` from this directory. To install
|
|
||||||
the extension, navigate to the "Extensions" panel in VSCode, and select
|
|
||||||
"Install from VSIX..." from the menu in the top right corner. Choose the
|
|
||||||
`gopls-1.0.0.vsix file` and reload VSCode.
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,58 +0,0 @@
|
||||||
{
|
|
||||||
"name": "gopls",
|
|
||||||
"description": "Go Language Server Client for testing",
|
|
||||||
"author": "The Go authors",
|
|
||||||
"license": "SEE LICENSE IN ../../../../LICENSE",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://golang.org/x/tools"
|
|
||||||
},
|
|
||||||
"publisher": "golang",
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.23.0"
|
|
||||||
},
|
|
||||||
"activationEvents": [
|
|
||||||
"onLanguage:go"
|
|
||||||
],
|
|
||||||
"main": "./out/extension",
|
|
||||||
"scripts": {
|
|
||||||
"vscode:prepublish": "tsc -p ./",
|
|
||||||
"compile": "tsc -watch -p ./",
|
|
||||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
|
||||||
"lint": "node ./node_modules/tslint/bin/tslint ./src/*.ts"
|
|
||||||
},
|
|
||||||
"extensionDependencies": [],
|
|
||||||
"dependencies": {
|
|
||||||
"vscode-languageclient": "~4.3.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/mocha": "^5.2.5",
|
|
||||||
"@types/node": "^8.10.39",
|
|
||||||
"tslint": "^5.11.0",
|
|
||||||
"typescript": "^3.1.3",
|
|
||||||
"vscode": "^1.1.24"
|
|
||||||
},
|
|
||||||
"contributes": {
|
|
||||||
"configuration": {
|
|
||||||
"title": "gopls",
|
|
||||||
"properties": {
|
|
||||||
"gopls.flags": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [],
|
|
||||||
"description": "Flags to pass to gopls",
|
|
||||||
"scope": "resource"
|
|
||||||
},
|
|
||||||
"gopls.command": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "gopls",
|
|
||||||
"description": "Name of the gopls binary",
|
|
||||||
"scope": "resource"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import fs = require('fs');
|
|
||||||
import lsp = require('vscode-languageclient');
|
|
||||||
import vscode = require('vscode');
|
|
||||||
import path = require('path');
|
|
||||||
|
|
||||||
export function activate(ctx: vscode.ExtensionContext): void {
|
|
||||||
// The handleDiagnostics middleware writes to the diagnostics log, in order
|
|
||||||
// to confirm the order in which the extension received diagnostics.
|
|
||||||
let r = Math.floor(Math.random() * 100000000);
|
|
||||||
let diagnosticsLog = fs.openSync('/tmp/diagnostics' + r + '.log', 'w');
|
|
||||||
|
|
||||||
let document = vscode.window.activeTextEditor.document;
|
|
||||||
let config = vscode.workspace.getConfiguration('gopls', document.uri);
|
|
||||||
let goplsCommand: string = config['command'];
|
|
||||||
let goplsFlags: string[] = config['flags'];
|
|
||||||
let serverOptions:
|
|
||||||
lsp.ServerOptions = {command: getBinPath(goplsCommand), args: goplsFlags};
|
|
||||||
let clientOptions: lsp.LanguageClientOptions = {
|
|
||||||
initializationOptions: {},
|
|
||||||
documentSelector: ['go'],
|
|
||||||
uriConverters: {
|
|
||||||
code2Protocol: (uri: vscode.Uri): string =>
|
|
||||||
(uri.scheme ? uri : uri.with({scheme: 'file'})).toString(),
|
|
||||||
protocol2Code: (uri: string) => vscode.Uri.parse(uri),
|
|
||||||
},
|
|
||||||
middleware: {
|
|
||||||
handleDiagnostics: (uri: vscode.Uri, diagnostics: vscode.Diagnostic[], next: lsp.HandleDiagnosticsSignature) => {
|
|
||||||
let diagString = "-------------------------------------------\n";
|
|
||||||
diagString += uri.toString(); + ": " + diagnostics.length + "\n";
|
|
||||||
if (diagnostics.length > 0) {
|
|
||||||
diagString += "\n";
|
|
||||||
for (const diag of diagnostics) {
|
|
||||||
diagString += diag.message + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.writeSync(diagnosticsLog, diagString);
|
|
||||||
return next(uri, diagnostics);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
revealOutputChannelOn: lsp.RevealOutputChannelOn.Never,
|
|
||||||
};
|
|
||||||
const c = new lsp.LanguageClient('gopls', serverOptions, clientOptions);
|
|
||||||
c.onReady().then(() => {
|
|
||||||
const capabilities = c.initializeResult && c.initializeResult.capabilities;
|
|
||||||
if (!capabilities) {
|
|
||||||
return vscode.window.showErrorMessage(
|
|
||||||
'The language server is not able to serve any features at the moment.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ctx.subscriptions.push(c.start());
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBinPath(toolName: string): string {
|
|
||||||
toolName = correctBinname(toolName);
|
|
||||||
let tool = findToolIn(toolName, 'PATH', false);
|
|
||||||
if (tool) {
|
|
||||||
return tool;
|
|
||||||
}
|
|
||||||
return findToolIn(toolName, 'GOPATH', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findToolIn(
|
|
||||||
toolName: string, envVar: string, appendBinToPath: boolean): string {
|
|
||||||
let value = process.env[envVar];
|
|
||||||
if (value) {
|
|
||||||
let paths = value.split(path.delimiter);
|
|
||||||
for (let i = 0; i < paths.length; i++) {
|
|
||||||
let binpath = path.join(paths[i], appendBinToPath ? 'bin' : '', toolName);
|
|
||||||
if (fileExists(binpath)) {
|
|
||||||
return binpath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileExists(filePath: string): boolean {
|
|
||||||
try {
|
|
||||||
return fs.statSync(filePath).isFile();
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function correctBinname(toolName: string) {
|
|
||||||
if (process.platform === 'win32')
|
|
||||||
return toolName + '.exe';
|
|
||||||
else
|
|
||||||
return toolName;
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es6",
|
|
||||||
"outDir": "out",
|
|
||||||
"rootDir": "src",
|
|
||||||
"lib": [
|
|
||||||
"es6"
|
|
||||||
],
|
|
||||||
"sourceMap": true
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
true,
|
|
||||||
"tabs"
|
|
||||||
],
|
|
||||||
"semicolon": [
|
|
||||||
true,
|
|
||||||
"always"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// The gopls command is an LSP server for Go.
|
|
||||||
// The Language Server Protocol allows any text editor
|
|
||||||
// to be extended with IDE-like features;
|
|
||||||
// see https://langserver.org/ for details.
|
|
||||||
package main // import "golang.org/x/tools/cmd/gopls"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp/cmd"
|
|
||||||
"golang.org/x/tools/internal/lsp/debug"
|
|
||||||
"golang.org/x/tools/internal/tool"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
debug.Version += "-cmd.gopls"
|
|
||||||
tool.Main(context.Background(), cmd.New("gopls-legacy", "", nil), os.Args[1:])
|
|
||||||
}
|
|
|
@ -48,8 +48,6 @@ func TestGeneratedFiles(t *testing.T) {
|
||||||
env = append(env, envVar)
|
env = append(env, envVar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// gorename currently requires GOPATH mode.
|
|
||||||
env = append(env, "GO111MODULE=off")
|
|
||||||
|
|
||||||
// Testing renaming in packages that include cgo files:
|
// Testing renaming in packages that include cgo files:
|
||||||
for iter, renameTest := range []test{
|
for iter, renameTest := range []test{
|
||||||
|
@ -312,9 +310,6 @@ func g() { fmt.Println(test.Foo(3)) }
|
||||||
// buildGorename builds the gorename executable.
|
// buildGorename builds the gorename executable.
|
||||||
// It returns its path, and a cleanup function.
|
// It returns its path, and a cleanup function.
|
||||||
func buildGorename(t *testing.T) (tmp, bin string, cleanup func()) {
|
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-")
|
tmp, err := ioutil.TempDir("", "gorename-regtest-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1272,7 +1272,7 @@ l1:
|
||||||
func cpyact(curprod []int, max int) {
|
func cpyact(curprod []int, max int) {
|
||||||
|
|
||||||
if !lflag {
|
if !lflag {
|
||||||
fmt.Fprintf(fcode, "\n//line %v:%v", infile, lineno)
|
fmt.Fprintf(fcode, "\n\t\t//line %v:%v", infile, lineno)
|
||||||
}
|
}
|
||||||
fmt.Fprint(fcode, "\n\t\t")
|
fmt.Fprint(fcode, "\n\t\t")
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
"golang.org/x/tools/go/ssa/ssautil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The callees function reports the possible callees of the function call site
|
// Callees reports the possible callees of the function call site
|
||||||
// identified by the specified source location.
|
// identified by the specified source location.
|
||||||
func callees(q *Query) error {
|
func callees(q *Query) error {
|
||||||
lconf := loader.Config{Build: q.Build}
|
lconf := loader.Config{Build: q.Build}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
"golang.org/x/tools/go/ssa/ssautil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The callers function reports the possible callers of the function
|
// Callers reports the possible callers of the function
|
||||||
// immediately enclosing the specified source location.
|
// immediately enclosing the specified source location.
|
||||||
//
|
//
|
||||||
func callers(q *Query) error {
|
func callers(q *Query) error {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
"golang.org/x/tools/go/ssa/ssautil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The callstack function displays an arbitrary path from a root of the callgraph
|
// Callstack displays an arbitrary path from a root of the callgraph
|
||||||
// to the function at the current position.
|
// to the function at the current position.
|
||||||
//
|
//
|
||||||
// The information may be misleading in a context-insensitive
|
// The information may be misleading in a context-insensitive
|
||||||
|
|
|
@ -162,9 +162,6 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
|
||||||
path = append([]ast.Node{n.Name}, path...)
|
path = append([]ast.Node{n.Name}, path...)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
|
||||||
return path, actionUnknown // uninteresting
|
|
||||||
|
|
||||||
case ast.Stmt:
|
case ast.Stmt:
|
||||||
return path, actionStmt
|
return path, actionStmt
|
||||||
|
|
||||||
|
@ -176,6 +173,9 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
|
||||||
*ast.ChanType:
|
*ast.ChanType:
|
||||||
return path, actionType
|
return path, actionType
|
||||||
|
|
||||||
|
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
||||||
|
return path, actionUnknown // uninteresting
|
||||||
|
|
||||||
case *ast.Ellipsis:
|
case *ast.Ellipsis:
|
||||||
// Continue to enclosing node.
|
// Continue to enclosing node.
|
||||||
// e.g. [...]T in ArrayType
|
// e.g. [...]T in ArrayType
|
||||||
|
@ -340,7 +340,6 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error
|
||||||
qpos: qpos,
|
qpos: qpos,
|
||||||
expr: expr,
|
expr: expr,
|
||||||
typ: typ,
|
typ: typ,
|
||||||
names: appendNames(nil, typ),
|
|
||||||
constVal: constVal,
|
constVal: constVal,
|
||||||
obj: obj,
|
obj: obj,
|
||||||
methods: accessibleMethods(typ, qpos.info.Pkg),
|
methods: accessibleMethods(typ, qpos.info.Pkg),
|
||||||
|
@ -348,34 +347,10 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendNames returns named types found within the Type by
|
|
||||||
// removing map, pointer, channel, slice, and array constructors.
|
|
||||||
// It does not descend into structs or interfaces.
|
|
||||||
func appendNames(names []*types.Named, typ types.Type) []*types.Named {
|
|
||||||
// elemType specifies type that has some element in it
|
|
||||||
// such as array, slice, chan, pointer
|
|
||||||
type elemType interface {
|
|
||||||
Elem() types.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t := typ.(type) {
|
|
||||||
case *types.Named:
|
|
||||||
names = append(names, t)
|
|
||||||
case *types.Map:
|
|
||||||
names = appendNames(names, t.Key())
|
|
||||||
names = appendNames(names, t.Elem())
|
|
||||||
case elemType:
|
|
||||||
names = appendNames(names, t.Elem())
|
|
||||||
}
|
|
||||||
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeValueResult struct {
|
type describeValueResult struct {
|
||||||
qpos *queryPos
|
qpos *queryPos
|
||||||
expr ast.Expr // query node
|
expr ast.Expr // query node
|
||||||
typ types.Type // type of expression
|
typ types.Type // type of expression
|
||||||
names []*types.Named // named types within typ
|
|
||||||
constVal constant.Value // value of expression, if constant
|
constVal constant.Value // value of expression, if constant
|
||||||
obj types.Object // var/func/const object, if expr was Ident
|
obj types.Object // var/func/const object, if expr was Ident
|
||||||
methods []*types.Selection
|
methods []*types.Selection
|
||||||
|
@ -423,7 +398,6 @@ func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
||||||
|
|
||||||
printMethods(printf, r.expr, r.methods)
|
printMethods(printf, r.expr, r.methods)
|
||||||
printFields(printf, r.expr, r.fields)
|
printFields(printf, r.expr, r.fields)
|
||||||
printNamedTypes(printf, r.expr, r.names)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
||||||
|
@ -435,21 +409,12 @@ func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
||||||
objpos = fset.Position(r.obj.Pos()).String()
|
objpos = fset.Position(r.obj.Pos()).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
typesPos := make([]serial.Definition, len(r.names))
|
|
||||||
for i, t := range r.names {
|
|
||||||
typesPos[i] = serial.Definition{
|
|
||||||
ObjPos: fset.Position(t.Obj().Pos()).String(),
|
|
||||||
Desc: r.qpos.typeString(t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toJSON(&serial.Describe{
|
return toJSON(&serial.Describe{
|
||||||
Desc: astutil.NodeDescription(r.expr),
|
Desc: astutil.NodeDescription(r.expr),
|
||||||
Pos: fset.Position(r.expr.Pos()).String(),
|
Pos: fset.Position(r.expr.Pos()).String(),
|
||||||
Detail: "value",
|
Detail: "value",
|
||||||
Value: &serial.DescribeValue{
|
Value: &serial.DescribeValue{
|
||||||
Type: r.qpos.typeString(r.typ),
|
Type: r.qpos.typeString(r.typ),
|
||||||
TypesPos: typesPos,
|
|
||||||
Value: value,
|
Value: value,
|
||||||
ObjPos: objpos,
|
ObjPos: objpos,
|
||||||
},
|
},
|
||||||
|
@ -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) {
|
func (r *describeTypeResult) PrintPlain(printf printfFunc) {
|
||||||
printf(r.node, "%s", r.description)
|
printf(r.node, "%s", r.description)
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -50,18 +49,6 @@ import (
|
||||||
guru "golang.org/x/tools/cmd/guru"
|
guru "golang.org/x/tools/cmd/guru"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
// This test currently requires GOPATH mode.
|
|
||||||
// Explicitly disabling module mode should suffix, but
|
|
||||||
// we'll also turn off GOPROXY just for good measure.
|
|
||||||
if err := os.Setenv("GO111MODULE", "off"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.Setenv("GOPROXY", "off"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var updateFlag = flag.Bool("update", false, "Update the golden files.")
|
var updateFlag = flag.Bool("update", false, "Update the golden files.")
|
||||||
|
|
||||||
type query struct {
|
type query struct {
|
||||||
|
@ -236,9 +223,10 @@ func TestGuru(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filename := range []string{
|
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/calls/main.go",
|
||||||
"testdata/src/describe/main.go",
|
"testdata/src/describe/main.go",
|
||||||
|
"testdata/src/describe/main19.go", // iff go1.9
|
||||||
"testdata/src/freevars/main.go",
|
"testdata/src/freevars/main.go",
|
||||||
"testdata/src/implements/main.go",
|
"testdata/src/implements/main.go",
|
||||||
"testdata/src/implements-methods/main.go",
|
"testdata/src/implements-methods/main.go",
|
||||||
|
@ -255,6 +243,7 @@ func TestGuru(t *testing.T) {
|
||||||
"testdata/src/calls-json/main.go",
|
"testdata/src/calls-json/main.go",
|
||||||
"testdata/src/peers-json/main.go",
|
"testdata/src/peers-json/main.go",
|
||||||
"testdata/src/definition-json/main.go",
|
"testdata/src/definition-json/main.go",
|
||||||
|
"testdata/src/definition-json/main19.go",
|
||||||
"testdata/src/describe-json/main.go",
|
"testdata/src/describe-json/main.go",
|
||||||
"testdata/src/implements-json/main.go",
|
"testdata/src/implements-json/main.go",
|
||||||
"testdata/src/implements-methods-json/main.go",
|
"testdata/src/implements-methods-json/main.go",
|
||||||
|
@ -271,6 +260,15 @@ func TestGuru(t *testing.T) {
|
||||||
// wording for a "no such file or directory" error.
|
// wording for a "no such file or directory" error.
|
||||||
t.Skip()
|
t.Skip()
|
||||||
}
|
}
|
||||||
|
if filename == "testdata/src/alias/alias.go" && !guru.HasAlias {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
json := strings.Contains(filename, "-json/")
|
json := strings.Contains(filename, "-json/")
|
||||||
queries := parseQueries(t, filename)
|
queries := parseQueries(t, filename)
|
||||||
golden := filename + "lden"
|
golden := filename + "lden"
|
||||||
|
@ -314,6 +312,15 @@ 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) {
|
func TestIssue14684(t *testing.T) {
|
||||||
var buildContext = build.Default
|
var buildContext = build.Default
|
||||||
buildContext.GOPATH = "testdata"
|
buildContext.GOPATH = "testdata"
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"golang.org/x/tools/refactor/importgraph"
|
"golang.org/x/tools/refactor/importgraph"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The implements function displays the "implements" relation as it pertains to the
|
// Implements displays the "implements" relation as it pertains to the
|
||||||
// selected type.
|
// selected type.
|
||||||
// If the selection is a method, 'implements' displays
|
// If the selection is a method, 'implements' displays
|
||||||
// the corresponding methods of the types that would have been reported
|
// the corresponding methods of the types that would have been reported
|
||||||
|
|
|
@ -27,7 +27,7 @@ import (
|
||||||
"golang.org/x/tools/refactor/importgraph"
|
"golang.org/x/tools/refactor/importgraph"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The referrers function reports all identifiers that resolve to the same object
|
// Referrers reports all identifiers that resolve to the same object
|
||||||
// as the queried identifier, within any package in the workspace.
|
// as the queried identifier, within any package in the workspace.
|
||||||
func referrers(q *Query) error {
|
func referrers(q *Query) error {
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
|
|
|
@ -196,7 +196,6 @@ type DescribeValue struct {
|
||||||
Type string `json:"type"` // type of the expression
|
Type string `json:"type"` // type of the expression
|
||||||
Value string `json:"value,omitempty"` // value of the expression, if constant
|
Value string `json:"value,omitempty"` // value of the expression, if constant
|
||||||
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
|
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
|
||||||
TypesPos []Definition `json:"typespos,omitempty"` // location of the named types, that type consist of
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DescribeMethod struct {
|
type DescribeMethod struct {
|
||||||
|
|
|
@ -9,7 +9,6 @@ package definition
|
||||||
import (
|
import (
|
||||||
"lib"
|
"lib"
|
||||||
lib2 "lib"
|
lib2 "lib"
|
||||||
"nosuchpkg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -28,7 +27,6 @@ func main() {
|
||||||
var _ lib.Const // @definition qualified-const "Const"
|
var _ lib.Const // @definition qualified-const "Const"
|
||||||
var _ lib2.Type // @definition qualified-type-renaming "Type"
|
var _ lib2.Type // @definition qualified-type-renaming "Type"
|
||||||
var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch"
|
var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch"
|
||||||
var _ nosuchpkg.T // @definition qualified-nopkg "nosuchpkg"
|
|
||||||
|
|
||||||
var u U
|
var u U
|
||||||
print(u.field) // @definition select-field "field"
|
print(u.field) // @definition select-field "field"
|
||||||
|
|
|
@ -11,17 +11,17 @@ Error: no object for identifier
|
||||||
}
|
}
|
||||||
-------- @definition lexical-func --------
|
-------- @definition lexical-func --------
|
||||||
{
|
{
|
||||||
"objpos": "$GOPATH/src/definition-json/main.go:38:6",
|
"objpos": "$GOPATH/src/definition-json/main.go:36:6",
|
||||||
"desc": "func f"
|
"desc": "func f"
|
||||||
}
|
}
|
||||||
-------- @definition lexical-var --------
|
-------- @definition lexical-var --------
|
||||||
{
|
{
|
||||||
"objpos": "$GOPATH/src/definition-json/main.go:19:6",
|
"objpos": "$GOPATH/src/definition-json/main.go:18:6",
|
||||||
"desc": "var x"
|
"desc": "var x"
|
||||||
}
|
}
|
||||||
-------- @definition lexical-shadowing --------
|
-------- @definition lexical-shadowing --------
|
||||||
{
|
{
|
||||||
"objpos": "$GOPATH/src/definition-json/main.go:22:5",
|
"objpos": "$GOPATH/src/definition-json/main.go:21:5",
|
||||||
"desc": "var x"
|
"desc": "var x"
|
||||||
}
|
}
|
||||||
-------- @definition qualified-type --------
|
-------- @definition qualified-type --------
|
||||||
|
@ -52,19 +52,14 @@ Error: no object for identifier
|
||||||
-------- @definition qualified-nomember --------
|
-------- @definition qualified-nomember --------
|
||||||
|
|
||||||
Error: couldn't find declaration of Nonesuch in "lib"
|
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 --------
|
-------- @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"
|
"desc": "field field int"
|
||||||
}
|
}
|
||||||
-------- @definition select-method --------
|
-------- @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()"
|
"desc": "func (T).method()"
|
||||||
}
|
}
|
||||||
-------- @definition embedded-other-file --------
|
-------- @definition embedded-other-file --------
|
||||||
|
@ -90,6 +85,6 @@ Error: int is built in
|
||||||
}
|
}
|
||||||
-------- @definition embedded-same-file --------
|
-------- @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"
|
"desc": "type T"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package definition
|
||||||
|
|
||||||
|
import "nosuchpkg"
|
||||||
|
|
||||||
|
var _ nosuchpkg.T // @definition qualified-nopkg "nosuchpkg"
|
|
@ -0,0 +1,5 @@
|
||||||
|
-------- @definition qualified-nopkg --------
|
||||||
|
{
|
||||||
|
"objpos": "testdata/src/definition-json/main19.go:3:8",
|
||||||
|
"desc": "package nosuchpkg"
|
||||||
|
}
|
|
@ -25,5 +25,5 @@ type I interface {
|
||||||
type C int // @describe desc-type-C "C"
|
type C int // @describe desc-type-C "C"
|
||||||
type D struct{}
|
type D struct{}
|
||||||
|
|
||||||
func (c C) f() {} // @describe desc-param-c "\\bc\\b"
|
func (c C) f() {}
|
||||||
func (d *D) f() {} // @describe desc-param-d "\\bd\\b"
|
func (d *D) f() {}
|
||||||
|
|
|
@ -68,13 +68,7 @@
|
||||||
"detail": "value",
|
"detail": "value",
|
||||||
"value": {
|
"value": {
|
||||||
"type": "I",
|
"type": "I",
|
||||||
"objpos": "testdata/src/describe-json/main.go:12:6",
|
"objpos": "testdata/src/describe-json/main.go:12:6"
|
||||||
"typespos": [
|
|
||||||
{
|
|
||||||
"objpos": "testdata/src/describe-json/main.go:21:6",
|
|
||||||
"desc": "I"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-------- @describe desc-stmt --------
|
-------- @describe desc-stmt --------
|
||||||
|
@ -100,35 +94,3 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-------- @describe desc-param-c --------
|
|
||||||
{
|
|
||||||
"desc": "identifier",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:28:7",
|
|
||||||
"detail": "value",
|
|
||||||
"value": {
|
|
||||||
"type": "C",
|
|
||||||
"objpos": "testdata/src/describe-json/main.go:28:7",
|
|
||||||
"typespos": [
|
|
||||||
{
|
|
||||||
"objpos": "testdata/src/describe-json/main.go:25:6",
|
|
||||||
"desc": "C"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @describe desc-param-d --------
|
|
||||||
{
|
|
||||||
"desc": "identifier",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:29:7",
|
|
||||||
"detail": "value",
|
|
||||||
"value": {
|
|
||||||
"type": "*D",
|
|
||||||
"objpos": "testdata/src/describe-json/main.go:29:7",
|
|
||||||
"typespos": [
|
|
||||||
{
|
|
||||||
"objpos": "testdata/src/describe-json/main.go:26:6",
|
|
||||||
"desc": "D"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,14 +8,9 @@ package describe // @describe pkgdecl "describe"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"lib"
|
"lib"
|
||||||
"nosuchpkg" // @describe badimport1 "nosuchpkg"
|
|
||||||
nosuchpkg2 "nosuchpkg" // @describe badimport2 "nosuchpkg2"
|
|
||||||
_ "unsafe" // @describe unsafe "unsafe"
|
_ "unsafe" // @describe unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ nosuchpkg.T
|
|
||||||
var _ nosuchpkg2.T
|
|
||||||
|
|
||||||
type cake float64 // @describe type-ref-builtin "float64"
|
type cake float64 // @describe type-ref-builtin "float64"
|
||||||
|
|
||||||
const c = iota // @describe const-ref-iota "iota"
|
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"
|
var i I // @describe type-I "I"
|
||||||
_ = d.f // @describe func-ref-d.f "d.f"
|
_ = d.f // @describe func-ref-d.f "d.f"
|
||||||
_ = i.f // @describe func-ref-i.f "i.f"
|
_ = i.f // @describe func-ref-i.f "i.f"
|
||||||
var slice []D // @describe slice-of-D "slice"
|
|
||||||
|
|
||||||
var dptr *D // @describe ptr-with-nonptr-methods "dptr"
|
var dptr *D // @describe ptr-with-nonptr-methods "dptr"
|
||||||
_ = dptr
|
_ = dptr
|
||||||
|
@ -91,11 +85,6 @@ func main() { // @describe func-def-main "main"
|
||||||
|
|
||||||
var _ lib.Outer // @describe lib-outer "Outer"
|
var _ lib.Outer // @describe lib-outer "Outer"
|
||||||
|
|
||||||
var mmm map[C]D // @describe var-map-of-C-D "mmm"
|
|
||||||
|
|
||||||
d := newD().ThirdField // @describe field-access "ThirdField"
|
|
||||||
|
|
||||||
astCopy := ast
|
|
||||||
unknown() // @describe call-unknown "\\("
|
unknown() // @describe call-unknown "\\("
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,10 +96,7 @@ type C int
|
||||||
type D struct {
|
type D struct {
|
||||||
Field int
|
Field int
|
||||||
AnotherField string
|
AnotherField string
|
||||||
ThirdField C
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *C) f() {}
|
func (c *C) f() {}
|
||||||
func (d D) f() {}
|
func (d D) f() {}
|
||||||
|
|
||||||
func newD() D { return D{} }
|
|
||||||
|
|
|
@ -10,16 +10,9 @@ definition of package "describe"
|
||||||
type cake float64
|
type cake float64
|
||||||
var global *string
|
var global *string
|
||||||
func main func()
|
func main func()
|
||||||
func newD func() D
|
|
||||||
const pi untyped float = 3.141
|
const pi untyped float = 3.141
|
||||||
const pie cake = 3.141
|
const pie cake = 3.141
|
||||||
|
|
||||||
-------- @describe badimport1 --------
|
|
||||||
import of package "nosuchpkg"
|
|
||||||
|
|
||||||
-------- @describe badimport2 --------
|
|
||||||
reference to package "nosuchpkg"
|
|
||||||
|
|
||||||
-------- @describe unsafe --------
|
-------- @describe unsafe --------
|
||||||
import of package "unsafe"
|
import of package "unsafe"
|
||||||
builtin Alignof
|
builtin Alignof
|
||||||
|
@ -38,8 +31,6 @@ definition of const pi untyped float of value 3.141
|
||||||
|
|
||||||
-------- @describe const-def-pie --------
|
-------- @describe const-def-pie --------
|
||||||
definition of const pie cake of value 3.141
|
definition of const pie cake of value 3.141
|
||||||
Named types:
|
|
||||||
type cake defined here
|
|
||||||
|
|
||||||
-------- @describe const-ref-pi --------
|
-------- @describe const-ref-pi --------
|
||||||
reference to const pi untyped float of value 3.141
|
reference to const pi untyped float of value 3.141
|
||||||
|
@ -65,14 +56,13 @@ reference to interface method func (I).f()
|
||||||
defined here
|
defined here
|
||||||
|
|
||||||
-------- @describe type-D --------
|
-------- @describe type-D --------
|
||||||
reference to type D (size 32, align 8)
|
reference to type D (size 24, align 8)
|
||||||
defined as struct{Field int; AnotherField string; ThirdField C}
|
defined as struct{Field int; AnotherField string}
|
||||||
Methods:
|
Methods:
|
||||||
method (D) f()
|
method (D) f()
|
||||||
Fields:
|
Fields:
|
||||||
Field int
|
Field int
|
||||||
AnotherField string
|
AnotherField string
|
||||||
ThirdField C
|
|
||||||
|
|
||||||
-------- @describe type-I --------
|
-------- @describe type-I --------
|
||||||
reference to type I (size 16, align 8)
|
reference to type I (size 16, align 8)
|
||||||
|
@ -88,11 +78,6 @@ defined here
|
||||||
reference to interface method func (I).f()
|
reference to interface method func (I).f()
|
||||||
defined here
|
defined here
|
||||||
|
|
||||||
-------- @describe slice-of-D --------
|
|
||||||
definition of var slice []D
|
|
||||||
Named types:
|
|
||||||
type D defined here
|
|
||||||
|
|
||||||
-------- @describe ptr-with-nonptr-methods --------
|
-------- @describe ptr-with-nonptr-methods --------
|
||||||
definition of var dptr *D
|
definition of var dptr *D
|
||||||
Methods:
|
Methods:
|
||||||
|
@ -100,9 +85,6 @@ Methods:
|
||||||
Fields:
|
Fields:
|
||||||
Field int
|
Field int
|
||||||
AnotherField string
|
AnotherField string
|
||||||
ThirdField C
|
|
||||||
Named types:
|
|
||||||
type D defined here
|
|
||||||
|
|
||||||
-------- @describe ref-lexical-d --------
|
-------- @describe ref-lexical-d --------
|
||||||
reference to var d D
|
reference to var d D
|
||||||
|
@ -112,9 +94,6 @@ Methods:
|
||||||
Fields:
|
Fields:
|
||||||
Field int
|
Field int
|
||||||
AnotherField string
|
AnotherField string
|
||||||
ThirdField C
|
|
||||||
Named types:
|
|
||||||
type D defined here
|
|
||||||
|
|
||||||
-------- @describe ref-anon --------
|
-------- @describe ref-anon --------
|
||||||
reference to var anon func()
|
reference to var anon func()
|
||||||
|
@ -144,32 +123,24 @@ reference to var i I
|
||||||
defined here
|
defined here
|
||||||
Methods:
|
Methods:
|
||||||
method (I) f()
|
method (I) f()
|
||||||
Named types:
|
|
||||||
type I defined here
|
|
||||||
|
|
||||||
-------- @describe var-ref-i-D --------
|
-------- @describe var-ref-i-D --------
|
||||||
reference to var i I
|
reference to var i I
|
||||||
defined here
|
defined here
|
||||||
Methods:
|
Methods:
|
||||||
method (I) f()
|
method (I) f()
|
||||||
Named types:
|
|
||||||
type I defined here
|
|
||||||
|
|
||||||
-------- @describe var-ref-i --------
|
-------- @describe var-ref-i --------
|
||||||
reference to var i I
|
reference to var i I
|
||||||
defined here
|
defined here
|
||||||
Methods:
|
Methods:
|
||||||
method (I) f()
|
method (I) f()
|
||||||
Named types:
|
|
||||||
type I defined here
|
|
||||||
|
|
||||||
-------- @describe const-local-pi --------
|
-------- @describe const-local-pi --------
|
||||||
definition of const localpi untyped float of value 3.141
|
definition of const localpi untyped float of value 3.141
|
||||||
|
|
||||||
-------- @describe const-local-pie --------
|
-------- @describe const-local-pie --------
|
||||||
definition of const localpie cake of value 3.141
|
definition of const localpie cake of value 3.141
|
||||||
Named types:
|
|
||||||
type cake defined here
|
|
||||||
|
|
||||||
-------- @describe const-ref-localpi --------
|
-------- @describe const-ref-localpi --------
|
||||||
reference to const localpi untyped float of value 3.141
|
reference to const localpi untyped float of value 3.141
|
||||||
|
@ -228,20 +199,6 @@ Fields:
|
||||||
inner.C bool
|
inner.C bool
|
||||||
inner.recursive.E bool
|
inner.recursive.E bool
|
||||||
|
|
||||||
-------- @describe var-map-of-C-D --------
|
|
||||||
definition of var mmm map[C]D
|
|
||||||
Named types:
|
|
||||||
type C defined here
|
|
||||||
type D defined here
|
|
||||||
|
|
||||||
-------- @describe field-access --------
|
|
||||||
reference to field ThirdField C
|
|
||||||
defined here
|
|
||||||
Methods:
|
|
||||||
method (*C) f()
|
|
||||||
Named types:
|
|
||||||
type C defined here
|
|
||||||
|
|
||||||
-------- @describe call-unknown --------
|
-------- @describe call-unknown --------
|
||||||
function call of type invalid type
|
function call of type invalid type
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
||||||
|
-------- @describe badimport1 --------
|
||||||
|
import of package "nosuchpkg"
|
||||||
|
|
||||||
|
-------- @describe badimport2 --------
|
||||||
|
reference to package "nosuchpkg"
|
||||||
|
|
|
@ -6,35 +6,35 @@
|
||||||
"package": "definition-json",
|
"package": "definition-json",
|
||||||
"refs": [
|
"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\""
|
"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\""
|
"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\""
|
"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\""
|
"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\""
|
"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\""
|
"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\""
|
"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\""
|
"text": "\tlib.Type // @definition embedded-other-pkg \"Type\""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
"package": "describe",
|
"package": "describe",
|
||||||
"refs": [
|
"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\""
|
"text": "\tvar _ lib.Outer // @describe lib-outer \"Outer\""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/token"
|
"go/token"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -203,7 +202,7 @@ func guessImportPath(filename string, buildContext *build.Context) (srcdir, impo
|
||||||
if d >= 0 && d < minD {
|
if d >= 0 && d < minD {
|
||||||
minD = d
|
minD = d
|
||||||
srcdir = gopathDir
|
srcdir = gopathDir
|
||||||
importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
|
importPath = strings.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:], string(os.PathSeparator))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if srcdir == "" {
|
if srcdir == "" {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
BasedOnStyle: Google
|
|
@ -0,0 +1 @@
|
||||||
|
/node_modules/
|
|
@ -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.
|
|
@ -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));
|
||||||
|
}
|
|
@ -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');
|
||||||
|
})
|
||||||
|
});
|
|
@ -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 ."
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
|
@ -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);
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 }
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/present"
|
"golang.org/x/tools/present"
|
||||||
)
|
)
|
||||||
|
@ -22,18 +21,19 @@ func init() {
|
||||||
http.HandleFunc("/", dirHandler)
|
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) {
|
func dirHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/favicon.ico" {
|
if r.URL.Path == "/favicon.ico" {
|
||||||
http.NotFound(w, r)
|
http.Error(w, "not found", 404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name := filepath.Join(*contentPath, r.URL.Path)
|
const base = "."
|
||||||
|
name := filepath.Join(base, r.URL.Path)
|
||||||
if isDoc(name) {
|
if isDoc(name) {
|
||||||
err := renderDoc(w, name)
|
err := renderDoc(w, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), 500)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -43,12 +43,12 @@ func dirHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
addr = r.RemoteAddr
|
addr = r.RemoteAddr
|
||||||
}
|
}
|
||||||
log.Printf("request from %s: %s", addr, err)
|
log.Printf("request from %s: %s", addr, err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
} else if isDir {
|
} else if isDir {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.FileServer(http.Dir(*contentPath)).ServeHTTP(w, r)
|
http.FileServer(http.Dir(base)).ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDoc(path string) bool {
|
func isDoc(path string) bool {
|
||||||
|
@ -138,9 +138,7 @@ func dirList(w io.Writer, name string) (isDir bool, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
strippedPath := strings.TrimPrefix(name, filepath.Clean(*contentPath))
|
d := &dirListData{Path: name}
|
||||||
strippedPath = strings.TrimPrefix(strippedPath, "/")
|
|
||||||
d := &dirListData{Path: strippedPath}
|
|
||||||
for _, fi := range fis {
|
for _, fi := range fis {
|
||||||
// skip the golang.org directory
|
// skip the golang.org directory
|
||||||
if name == "." && fi.Name() == "golang.org" {
|
if name == "." && fi.Name() == "golang.org" {
|
||||||
|
@ -148,16 +146,15 @@ func dirList(w io.Writer, name string) (isDir bool, err error) {
|
||||||
}
|
}
|
||||||
e := dirEntry{
|
e := dirEntry{
|
||||||
Name: fi.Name(),
|
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) {
|
if fi.IsDir() && showDir(e.Name) {
|
||||||
d.Dirs = append(d.Dirs, e)
|
d.Dirs = append(d.Dirs, e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if isDoc(e.Name) {
|
if isDoc(e.Name) {
|
||||||
fn := filepath.ToSlash(filepath.Join(name, fi.Name()))
|
if p, err := parse(e.Path, present.TitlesOnly); err != nil {
|
||||||
if p, err := parse(fn, present.TitlesOnly); err != nil {
|
log.Println(err)
|
||||||
log.Printf("parse(%q, present.TitlesOnly): %v", fn, err)
|
|
||||||
} else {
|
} else {
|
||||||
e.Title = p.Title
|
e.Title = p.Title
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
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:
|
The setup of the Go version of NaCl is documented at:
|
||||||
https://golang.org/wiki/NativeClient
|
https://golang.org/wiki/NativeClient
|
||||||
|
|
||||||
To use with App Engine, copy the files in the tools/cmd/present directory to the
|
To use with App Engine, copy the tools/cmd/present directory to the root of
|
||||||
root of your application and create an app.yaml file similar to this:
|
your application and create an app.yaml file similar to this:
|
||||||
|
|
||||||
runtime: go111
|
application: [application]
|
||||||
|
version: [version]
|
||||||
|
runtime: go
|
||||||
|
api_version: go1
|
||||||
|
|
||||||
handlers:
|
handlers:
|
||||||
- url: /favicon.ico
|
- url: /favicon.ico
|
||||||
static_files: static/favicon.ico
|
static_files: present/static/favicon.ico
|
||||||
upload: static/favicon.ico
|
upload: present/static/favicon.ico
|
||||||
- url: /static
|
- url: /static
|
||||||
static_dir: static
|
static_dir: present/static
|
||||||
|
application_readable: true
|
||||||
- url: /.*
|
- url: /.*
|
||||||
script: auto
|
script: _go_app
|
||||||
|
|
||||||
# nobuild_files is a regexp that identifies which files to not build. It
|
# nobuild_files is a regexp that identifies which files to not build. It
|
||||||
# is useful for embedding static assets like code snippets and preventing
|
# is useful for embedding static assets like code snippets and preventing
|
||||||
# them from producing build errors for your project.
|
# them from producing build errors for your project.
|
||||||
nobuild_files: [path regexp for talk materials]
|
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
|
Present then can be tested in a local App Engine environment with
|
||||||
|
|
||||||
GAE_ENV=standard go run .
|
goapp serve
|
||||||
|
|
||||||
And deployed using
|
|
||||||
|
|
||||||
gcloud app deploy
|
|
||||||
|
|
||||||
Input files are named foo.extension, where "extension" defines the format of
|
Input files are named foo.extension, where "extension" defines the format of
|
||||||
the generated output. The supported formats are:
|
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:
|
The present file format is documented by the present package:
|
||||||
http://godoc.org/golang.org/x/tools/present
|
http://godoc.org/golang.org/x/tools/present
|
||||||
*/
|
*/
|
||||||
package main
|
package main // import "golang.org/x/tools/cmd/present"
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
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')")
|
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')")
|
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")
|
basePath = flag.String("base", "", "base path for slide template and static resources")
|
||||||
contentPath = flag.String("content", ".", "base path for presentation content")
|
nativeClient = flag.Bool("nacl", false, "use Native Client environment playground (prevents non-Go code execution)")
|
||||||
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")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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.BoolVar(&present.NotesEnabled, "notes", false, "enable presenter notes (press 'N' from the browser to display them)")
|
||||||
flag.Parse()
|
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 == "" {
|
if *basePath == "" {
|
||||||
p, err := build.Default.Import(basePkg, "", build.FindOnly)
|
p, err := build.Default.Import(basePkg, "", build.FindOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -97,7 +81,7 @@ func main() {
|
||||||
http.Handle("/static/", http.FileServer(http.Dir(*basePath)))
|
http.Handle("/static/", http.FileServer(http.Dir(*basePath)))
|
||||||
|
|
||||||
if !ln.Addr().(*net.TCPAddr).IP.IsLoopback() &&
|
if !ln.Addr().(*net.TCPAddr).IP.IsLoopback() &&
|
||||||
present.PlayEnabled && !*nativeClient && !*usePlayground {
|
present.PlayEnabled && !*nativeClient {
|
||||||
log.Print(localhostWarning)
|
log.Print(localhostWarning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,12 +121,11 @@ You may use the -base flag to specify an alternate location.
|
||||||
const localhostWarning = `
|
const localhostWarning = `
|
||||||
WARNING! WARNING! WARNING!
|
WARNING! WARNING! WARNING!
|
||||||
|
|
||||||
The present server appears to be listening on an address that is not localhost
|
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
|
Anyone with access to this address and port will have access to this machine as
|
||||||
and port will have access to this machine as the user running present.
|
the user running present.
|
||||||
|
|
||||||
To avoid this message, listen on localhost, run with -play=false, or run with
|
To avoid this message, listen on localhost or run with -play=false.
|
||||||
-play_socket=false.
|
|
||||||
|
|
||||||
If you don't understand this message, hit Control-C to terminate this process.
|
If you don't understand this message, hit Control-C to terminate this process.
|
||||||
|
|
|
@ -9,21 +9,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/tools/godoc/static"
|
"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"}
|
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))
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -26,35 +26,12 @@ function toggleNotesWindow() {
|
||||||
initNotes();
|
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() {
|
function initNotes() {
|
||||||
notesWindow = window.open('', '', 'width=1000,height=700');
|
notesWindow = window.open('', '', 'width=1000,height=700');
|
||||||
var w = notesWindow;
|
var w = notesWindow;
|
||||||
var slidesUrl = window.location.href;
|
var slidesUrl = window.location.href;
|
||||||
|
|
||||||
// Hack to apply css. Requires existing html on notesWindow.
|
var curSlide = parseInt(localStorage.getItem('destSlide'), 10);
|
||||||
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 formattedNotes = '';
|
var formattedNotes = '';
|
||||||
var section = sections[curSlide - 1];
|
var section = sections[curSlide - 1];
|
||||||
// curSlide is 0 when initialized from the first page of slides.
|
// curSlide is 0 when initialized from the first page of slides.
|
||||||
|
@ -65,6 +42,15 @@ function initNotes() {
|
||||||
formattedNotes = formatNotes(titleNotes);
|
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 needed for Firefox
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
slides.focus();
|
slides.focus();
|
||||||
|
@ -107,7 +93,7 @@ function updateNotes() {
|
||||||
// When triggered from parent window, notesWindow is null
|
// When triggered from parent window, notesWindow is null
|
||||||
// The storage event listener on notesWindow will update notes
|
// The storage event listener on notesWindow will update notes
|
||||||
if (!notesWindow) return;
|
if (!notesWindow) return;
|
||||||
var destSlide = parseInt(localStorage.getItem(destSlideKey()), 10);
|
var destSlide = parseInt(localStorage.getItem('destSlide'), 10);
|
||||||
var section = sections[destSlide - 1];
|
var section = sections[destSlide - 1];
|
||||||
var el = notesWindow.document.getElementById('presenter-notes');
|
var el = notesWindow.document.getElementById('presenter-notes');
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,7 @@ function prevSlide() {
|
||||||
updateSlides();
|
updateSlides();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
|
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
|
||||||
};
|
};
|
||||||
|
|
||||||
function nextSlide() {
|
function nextSlide() {
|
||||||
|
@ -223,7 +223,7 @@ function nextSlide() {
|
||||||
updateSlides();
|
updateSlides();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
|
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Slide events */
|
/* Slide events */
|
||||||
|
@ -442,7 +442,7 @@ function handleBodyKeyDown(event) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function scaleSmallViewports() {
|
function scaleSmallViewports() {
|
||||||
var el = document.querySelector('section.slides');
|
var el = document.querySelector('.slides');
|
||||||
var transform = '';
|
var transform = '';
|
||||||
var sWidthPx = 1250;
|
var sWidthPx = 1250;
|
||||||
var sHeightPx = 750;
|
var sHeightPx = 750;
|
||||||
|
@ -468,20 +468,6 @@ function addEventListeners() {
|
||||||
scaleSmallViewports();
|
scaleSmallViewports();
|
||||||
}, 50);
|
}, 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 */
|
/* Initialization */
|
||||||
|
@ -602,7 +588,7 @@ function setupNotesSync() {
|
||||||
|
|
||||||
setupPlayCodeSync();
|
setupPlayCodeSync();
|
||||||
setupPlayResizeSync();
|
setupPlayResizeSync();
|
||||||
localStorage.setItem(destSlideKey(), curSlide);
|
localStorage.setItem('destSlide', curSlide);
|
||||||
window.addEventListener('storage', updateOtherWindow, false);
|
window.addEventListener('storage', updateOtherWindow, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -613,7 +599,7 @@ function updateOtherWindow(e) {
|
||||||
var isRemoveStorageEvent = !e.newValue;
|
var isRemoveStorageEvent = !e.newValue;
|
||||||
if (isRemoveStorageEvent) return;
|
if (isRemoveStorageEvent) return;
|
||||||
|
|
||||||
var destSlide = localStorage.getItem(destSlideKey());
|
var destSlide = localStorage.getItem('destSlide');
|
||||||
while (destSlide > curSlide) {
|
while (destSlide > curSlide) {
|
||||||
nextSlide();
|
nextSlide();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@ -1,389 +0,0 @@
|
||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package macho
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fileTest struct {
|
|
||||||
file string
|
|
||||||
hdr FileHeader
|
|
||||||
loads []interface{}
|
|
||||||
sections []*SectionHeader
|
|
||||||
relocations map[string][]Reloc
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileTests = []fileTest{
|
|
||||||
{
|
|
||||||
"testdata/gcc-386-darwin-exec",
|
|
||||||
FileHeader{0xfeedface, Cpu386, 0x3, 0x2, 0xc, 0x3c0, 0x85},
|
|
||||||
[]interface{}{
|
|
||||||
&SegmentHeader{LcSegment, 0x38, "__PAGEZERO", 0x0, 0x1000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0},
|
|
||||||
&SegmentHeader{LcSegment, 0xc0, "__TEXT", 0x1000, 0x1000, 0x0, 0x1000, 0x7, 0x5, 0x2, 0x0, 0},
|
|
||||||
&SegmentHeader{LcSegment, 0xc0, "__DATA", 0x2000, 0x1000, 0x1000, 0x1000, 0x7, 0x3, 0x2, 0x0, 2},
|
|
||||||
&SegmentHeader{LcSegment, 0x7c, "__IMPORT", 0x3000, 0x1000, 0x2000, 0x1000, 0x7, 0x7, 0x1, 0x0, 4},
|
|
||||||
&SegmentHeader{LcSegment, 0x38, "__LINKEDIT", 0x4000, 0x1000, 0x3000, 0x12c, 0x7, 0x1, 0x0, 0x0, 5},
|
|
||||||
nil, // LC_SYMTAB
|
|
||||||
nil, // LC_DYSYMTAB
|
|
||||||
nil, // LC_LOAD_DYLINKER
|
|
||||||
nil, // LC_UUID
|
|
||||||
nil, // LC_UNIXTHREAD
|
|
||||||
&Dylib{DylibCmd{}, "/usr/lib/libgcc_s.1.dylib", 0x2, 0x10000, 0x10000},
|
|
||||||
&Dylib{DylibCmd{}, "/usr/lib/libSystem.B.dylib", 0x2, 0x6f0104, 0x10000},
|
|
||||||
},
|
|
||||||
[]*SectionHeader{
|
|
||||||
{"__text", "__TEXT", 0x1f68, 0x88, 0xf68, 0x2, 0x0, 0x0, 0x80000400, 0, 0, 0},
|
|
||||||
{"__cstring", "__TEXT", 0x1ff0, 0xd, 0xff0, 0x0, 0x0, 0x0, 0x2, 0, 0, 0},
|
|
||||||
{"__data", "__DATA", 0x2000, 0x14, 0x1000, 0x2, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__dyld", "__DATA", 0x2014, 0x1c, 0x1014, 0x2, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__jump_table", "__IMPORT", 0x3000, 0xa, 0x2000, 0x6, 0x0, 0x0, 0x4000008, 0, 5, 0},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"testdata/gcc-amd64-darwin-exec",
|
|
||||||
FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0x2, 0xb, 0x568, 0x85},
|
|
||||||
[]interface{}{
|
|
||||||
&SegmentHeader{LcSegment64, 0x48, "__PAGEZERO", 0x0, 0x100000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0},
|
|
||||||
&SegmentHeader{LcSegment64, 0x1d8, "__TEXT", 0x100000000, 0x1000, 0x0, 0x1000, 0x7, 0x5, 0x5, 0x0, 0},
|
|
||||||
&SegmentHeader{LcSegment64, 0x138, "__DATA", 0x100001000, 0x1000, 0x1000, 0x1000, 0x7, 0x3, 0x3, 0x0, 5},
|
|
||||||
&SegmentHeader{LcSegment64, 0x48, "__LINKEDIT", 0x100002000, 0x1000, 0x2000, 0x140, 0x7, 0x1, 0x0, 0x0, 8},
|
|
||||||
nil, // LC_SYMTAB
|
|
||||||
nil, // LC_DYSYMTAB
|
|
||||||
nil, // LC_LOAD_DYLINKER
|
|
||||||
nil, // LC_UUID
|
|
||||||
nil, // LC_UNIXTHREAD
|
|
||||||
&Dylib{DylibCmd{}, "/usr/lib/libgcc_s.1.dylib", 0x2, 0x10000, 0x10000},
|
|
||||||
&Dylib{DylibCmd{}, "/usr/lib/libSystem.B.dylib", 0x2, 0x6f0104, 0x10000},
|
|
||||||
},
|
|
||||||
[]*SectionHeader{
|
|
||||||
{"__text", "__TEXT", 0x100000f14, 0x6d, 0xf14, 0x2, 0x0, 0x0, 0x80000400, 0, 0, 0},
|
|
||||||
{"__symbol_stub1", "__TEXT", 0x100000f81, 0xc, 0xf81, 0x0, 0x0, 0x0, 0x80000408, 0, 6, 0},
|
|
||||||
{"__stub_helper", "__TEXT", 0x100000f90, 0x18, 0xf90, 0x2, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__cstring", "__TEXT", 0x100000fa8, 0xd, 0xfa8, 0x0, 0x0, 0x0, 0x2, 0, 0, 0},
|
|
||||||
{"__eh_frame", "__TEXT", 0x100000fb8, 0x48, 0xfb8, 0x3, 0x0, 0x0, 0x6000000b, 0, 0, 0},
|
|
||||||
{"__data", "__DATA", 0x100001000, 0x1c, 0x1000, 0x3, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__dyld", "__DATA", 0x100001020, 0x38, 0x1020, 0x3, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__la_symbol_ptr", "__DATA", 0x100001058, 0x10, 0x1058, 0x2, 0x0, 0x0, 0x7, 2, 0, 0},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"testdata/gcc-amd64-darwin-exec-debug",
|
|
||||||
FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0xa, 0x4, 0x5a0, 0},
|
|
||||||
[]interface{}{
|
|
||||||
nil, // LC_UUID
|
|
||||||
&SegmentHeader{LcSegment64, 0x1d8, "__TEXT", 0x100000000, 0x1000, 0x0, 0x0, 0x7, 0x5, 0x5, 0x0, 0},
|
|
||||||
&SegmentHeader{LcSegment64, 0x138, "__DATA", 0x100001000, 0x1000, 0x0, 0x0, 0x7, 0x3, 0x3, 0x0, 5},
|
|
||||||
&SegmentHeader{LcSegment64, 0x278, "__DWARF", 0x100002000, 0x1000, 0x1000, 0x1bc, 0x7, 0x3, 0x7, 0x0, 8},
|
|
||||||
},
|
|
||||||
[]*SectionHeader{
|
|
||||||
{"__text", "__TEXT", 0x100000f14, 0x0, 0x0, 0x2, 0x0, 0x0, 0x80000400, 0, 0, 0},
|
|
||||||
{"__symbol_stub1", "__TEXT", 0x100000f81, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000408, 0, 6, 0},
|
|
||||||
{"__stub_helper", "__TEXT", 0x100000f90, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__cstring", "__TEXT", 0x100000fa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0, 0, 0},
|
|
||||||
{"__eh_frame", "__TEXT", 0x100000fb8, 0x0, 0x0, 0x3, 0x0, 0x0, 0x6000000b, 0, 0, 0},
|
|
||||||
{"__data", "__DATA", 0x100001000, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__dyld", "__DATA", 0x100001020, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__la_symbol_ptr", "__DATA", 0x100001058, 0x0, 0x0, 0x2, 0x0, 0x0, 0x7, 2, 0, 0},
|
|
||||||
{"__debug_abbrev", "__DWARF", 0x100002000, 0x36, 0x1000, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__debug_aranges", "__DWARF", 0x100002036, 0x30, 0x1036, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__debug_frame", "__DWARF", 0x100002066, 0x40, 0x1066, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__debug_info", "__DWARF", 0x1000020a6, 0x54, 0x10a6, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__debug_line", "__DWARF", 0x1000020fa, 0x47, 0x10fa, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__debug_pubnames", "__DWARF", 0x100002141, 0x1b, 0x1141, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
{"__debug_str", "__DWARF", 0x10000215c, 0x60, 0x115c, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"testdata/clang-386-darwin-exec-with-rpath",
|
|
||||||
FileHeader{0xfeedface, Cpu386, 0x3, 0x2, 0x10, 0x42c, 0x1200085},
|
|
||||||
[]interface{}{
|
|
||||||
nil, // LC_SEGMENT
|
|
||||||
nil, // LC_SEGMENT
|
|
||||||
nil, // LC_SEGMENT
|
|
||||||
nil, // LC_SEGMENT
|
|
||||||
nil, // LC_DYLD_INFO_ONLY
|
|
||||||
nil, // LC_SYMTAB
|
|
||||||
nil, // LC_DYSYMTAB
|
|
||||||
nil, // LC_LOAD_DYLINKER
|
|
||||||
nil, // LC_UUID
|
|
||||||
nil, // LC_VERSION_MIN_MACOSX
|
|
||||||
nil, // LC_SOURCE_VERSION
|
|
||||||
nil, // LC_MAIN
|
|
||||||
nil, // LC_LOAD_DYLIB
|
|
||||||
&Rpath{LcRpath, "/my/rpath"},
|
|
||||||
nil, // LC_FUNCTION_STARTS
|
|
||||||
nil, // LC_DATA_IN_CODE
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"testdata/clang-amd64-darwin-exec-with-rpath",
|
|
||||||
FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0x2, 0x10, 0x4c8, 0x200085},
|
|
||||||
[]interface{}{
|
|
||||||
nil, // LC_SEGMENT
|
|
||||||
nil, // LC_SEGMENT
|
|
||||||
nil, // LC_SEGMENT
|
|
||||||
nil, // LC_SEGMENT
|
|
||||||
nil, // LC_DYLD_INFO_ONLY
|
|
||||||
nil, // LC_SYMTAB
|
|
||||||
nil, // LC_DYSYMTAB
|
|
||||||
nil, // LC_LOAD_DYLINKER
|
|
||||||
nil, // LC_UUID
|
|
||||||
nil, // LC_VERSION_MIN_MACOSX
|
|
||||||
nil, // LC_SOURCE_VERSION
|
|
||||||
nil, // LC_MAIN
|
|
||||||
nil, // LC_LOAD_DYLIB
|
|
||||||
&Rpath{LcRpath, "/my/rpath"},
|
|
||||||
nil, // LC_FUNCTION_STARTS
|
|
||||||
nil, // LC_DATA_IN_CODE
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"testdata/clang-386-darwin.obj",
|
|
||||||
FileHeader{0xfeedface, Cpu386, 0x3, 0x1, 0x4, 0x138, 0x2000},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
map[string][]Reloc{
|
|
||||||
"__text": []Reloc{
|
|
||||||
{
|
|
||||||
Addr: 0x1d,
|
|
||||||
Type: uint8(GENERIC_RELOC_VANILLA),
|
|
||||||
Len: 2,
|
|
||||||
Pcrel: true,
|
|
||||||
Extern: true,
|
|
||||||
Value: 1,
|
|
||||||
Scattered: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Addr: 0xe,
|
|
||||||
Type: uint8(GENERIC_RELOC_LOCAL_SECTDIFF),
|
|
||||||
Len: 2,
|
|
||||||
Pcrel: false,
|
|
||||||
Value: 0x2d,
|
|
||||||
Scattered: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Addr: 0x0,
|
|
||||||
Type: uint8(GENERIC_RELOC_PAIR),
|
|
||||||
Len: 2,
|
|
||||||
Pcrel: false,
|
|
||||||
Value: 0xb,
|
|
||||||
Scattered: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"testdata/clang-amd64-darwin.obj",
|
|
||||||
FileHeader{0xfeedfacf, CpuAmd64, 0x3, 0x1, 0x4, 0x200, 0x2000},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
map[string][]Reloc{
|
|
||||||
"__text": []Reloc{
|
|
||||||
{
|
|
||||||
Addr: 0x19,
|
|
||||||
Type: uint8(X86_64_RELOC_BRANCH),
|
|
||||||
Len: 2,
|
|
||||||
Pcrel: true,
|
|
||||||
Extern: true,
|
|
||||||
Value: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Addr: 0xb,
|
|
||||||
Type: uint8(X86_64_RELOC_SIGNED),
|
|
||||||
Len: 2,
|
|
||||||
Pcrel: true,
|
|
||||||
Extern: false,
|
|
||||||
Value: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"__compact_unwind": []Reloc{
|
|
||||||
{
|
|
||||||
Addr: 0x0,
|
|
||||||
Type: uint8(X86_64_RELOC_UNSIGNED),
|
|
||||||
Len: 3,
|
|
||||||
Pcrel: false,
|
|
||||||
Extern: false,
|
|
||||||
Value: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOpen(t *testing.T) {
|
|
||||||
for i := range fileTests {
|
|
||||||
tt := &fileTests[i]
|
|
||||||
|
|
||||||
f, err := Open(tt.file)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(f.FileHeader, tt.hdr) {
|
|
||||||
t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// for i, l := range f.Loads {
|
|
||||||
// if len(l.Raw()) < 8 {
|
|
||||||
// t.Errorf("open %s, command %d:\n\tload command %T don't have enough data\n", tt.file, i, l)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if tt.loads != nil {
|
|
||||||
for i, l := range f.Loads {
|
|
||||||
if i >= len(tt.loads) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
want := tt.loads[i]
|
|
||||||
if want == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch l := l.(type) {
|
|
||||||
case *Segment:
|
|
||||||
have := &l.SegmentHeader
|
|
||||||
if !reflect.DeepEqual(have, want) {
|
|
||||||
t.Errorf("open %s, command %d:\n\thave %s\n\twant %s\n", tt.file, i, have.String(), want.(*SegmentHeader).String())
|
|
||||||
}
|
|
||||||
case *Dylib:
|
|
||||||
// have := l
|
|
||||||
// have.LoadBytes = nil
|
|
||||||
// if !reflect.DeepEqual(have, want) {
|
|
||||||
// t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
|
|
||||||
// }
|
|
||||||
case *Rpath:
|
|
||||||
// have := l
|
|
||||||
// have.LoadBytes = nil
|
|
||||||
// if !reflect.DeepEqual(have, want) {
|
|
||||||
// t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
|
|
||||||
// }
|
|
||||||
default:
|
|
||||||
t.Errorf("open %s, command %d: unknown load command\n\thave %#v\n\twant %#v\n", tt.file, i, l, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tn := len(tt.loads)
|
|
||||||
fn := len(f.Loads)
|
|
||||||
if tn != fn {
|
|
||||||
t.Errorf("open %s: len(Loads) = %d, want %d", tt.file, fn, tn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.sections != nil {
|
|
||||||
for i, sh := range f.Sections {
|
|
||||||
if i >= len(tt.sections) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
have := &sh.SectionHeader
|
|
||||||
want := tt.sections[i]
|
|
||||||
if !reflect.DeepEqual(have, want) {
|
|
||||||
t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tn := len(tt.sections)
|
|
||||||
fn := len(f.Sections)
|
|
||||||
if tn != fn {
|
|
||||||
t.Errorf("open %s: len(Sections) = %d, want %d", tt.file, fn, tn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.relocations != nil {
|
|
||||||
for i, sh := range f.Sections {
|
|
||||||
have := sh.Relocs
|
|
||||||
want := tt.relocations[sh.Name]
|
|
||||||
if !reflect.DeepEqual(have, want) {
|
|
||||||
t.Errorf("open %s, relocations in section %d (%s):\n\thave %#v\n\twant %#v\n", tt.file, i, sh.Name, have, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOpenFailure(t *testing.T) {
|
|
||||||
filename := "file.go" // not a Mach-O file
|
|
||||||
_, err := Open(filename) // don't crash
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("open %s: succeeded unexpectedly", filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOpenFat(t *testing.T) {
|
|
||||||
ff, err := OpenFat("testdata/fat-gcc-386-amd64-darwin-exec")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ff.Magic != MagicFat {
|
|
||||||
t.Errorf("OpenFat: got magic number %#x, want %#x", ff.Magic, MagicFat)
|
|
||||||
}
|
|
||||||
if len(ff.Arches) != 2 {
|
|
||||||
t.Errorf("OpenFat: got %d architectures, want 2", len(ff.Arches))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range ff.Arches {
|
|
||||||
arch := &ff.Arches[i]
|
|
||||||
ftArch := &fileTests[i]
|
|
||||||
|
|
||||||
if arch.Cpu != ftArch.hdr.Cpu || arch.SubCpu != ftArch.hdr.SubCpu {
|
|
||||||
t.Errorf("OpenFat: architecture #%d got cpu=%#x subtype=%#x, expected cpu=%#x, subtype=%#x", i, arch.Cpu, arch.SubCpu, ftArch.hdr.Cpu, ftArch.hdr.SubCpu)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(arch.FileHeader, ftArch.hdr) {
|
|
||||||
t.Errorf("OpenFat header:\n\tgot %#v\n\twant %#v\n", arch.FileHeader, ftArch.hdr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOpenFatFailure(t *testing.T) {
|
|
||||||
filename := "file.go" // not a Mach-O file
|
|
||||||
if _, err := OpenFat(filename); err == nil {
|
|
||||||
t.Errorf("OpenFat %s: succeeded unexpectedly", filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = "testdata/gcc-386-darwin-exec" // not a fat Mach-O
|
|
||||||
ff, err := OpenFat(filename)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("OpenFat %s: expected error, got nil", filename)
|
|
||||||
}
|
|
||||||
if _, ok := err.(*FormatError); !ok {
|
|
||||||
t.Errorf("OpenFat %s: expected FormatError, got %v", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ferr := err.(*FormatError)
|
|
||||||
if !strings.Contains(ferr.String(), "not a fat") {
|
|
||||||
t.Errorf("OpenFat %s: expected error containing 'not a fat', got %s", filename, ferr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if ff != nil {
|
|
||||||
t.Errorf("OpenFat %s: got %v, want nil", filename, ff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRelocTypeString(t *testing.T) {
|
|
||||||
if X86_64_RELOC_BRANCH.String() != "X86_64_RELOC_BRANCH" {
|
|
||||||
t.Errorf("got %v, want %v", X86_64_RELOC_BRANCH.String(), "X86_64_RELOC_BRANCH")
|
|
||||||
}
|
|
||||||
if X86_64_RELOC_BRANCH.GoString() != "macho.X86_64_RELOC_BRANCH" {
|
|
||||||
t.Errorf("got %v, want %v", X86_64_RELOC_BRANCH.GoString(), "macho.X86_64_RELOC_BRANCH")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTypeString(t *testing.T) {
|
|
||||||
if MhExecute.String() != "Exec" {
|
|
||||||
t.Errorf("got %v, want %v", MhExecute.String(), "Exec")
|
|
||||||
}
|
|
||||||
if MhExecute.GoString() != "macho.Exec" {
|
|
||||||
t.Errorf("got %v, want %v", MhExecute.GoString(), "macho.Exec")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,468 +0,0 @@
|
||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Mach-O header data structures
|
|
||||||
// http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
|
|
||||||
|
|
||||||
package macho
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A FileHeader represents a Mach-O file header.
|
|
||||||
type FileHeader struct {
|
|
||||||
Magic uint32
|
|
||||||
Cpu Cpu
|
|
||||||
SubCpu uint32
|
|
||||||
Type HdrType
|
|
||||||
NCommands uint32 // number of load commands
|
|
||||||
SizeCommands uint32 // size of all the load commands, not including this header.
|
|
||||||
Flags HdrFlags
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *FileHeader) Put(b []byte, o binary.ByteOrder) int {
|
|
||||||
o.PutUint32(b[0:], h.Magic)
|
|
||||||
o.PutUint32(b[4:], uint32(h.Cpu))
|
|
||||||
o.PutUint32(b[8:], h.SubCpu)
|
|
||||||
o.PutUint32(b[12:], uint32(h.Type))
|
|
||||||
o.PutUint32(b[16:], h.NCommands)
|
|
||||||
o.PutUint32(b[20:], h.SizeCommands)
|
|
||||||
o.PutUint32(b[24:], uint32(h.Flags))
|
|
||||||
if h.Magic == Magic32 {
|
|
||||||
return 28
|
|
||||||
}
|
|
||||||
o.PutUint32(b[28:], 0)
|
|
||||||
return 32
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
fileHeaderSize32 = 7 * 4
|
|
||||||
fileHeaderSize64 = 8 * 4
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Magic32 uint32 = 0xfeedface
|
|
||||||
Magic64 uint32 = 0xfeedfacf
|
|
||||||
MagicFat uint32 = 0xcafebabe
|
|
||||||
)
|
|
||||||
|
|
||||||
type HdrFlags uint32
|
|
||||||
type SegFlags uint32
|
|
||||||
type SecFlags uint32
|
|
||||||
|
|
||||||
// A HdrType is the Mach-O file type, e.g. an object file, executable, or dynamic library.
|
|
||||||
type HdrType uint32
|
|
||||||
|
|
||||||
const ( // SNAKE_CASE to CamelCase translation from C names
|
|
||||||
MhObject HdrType = 1
|
|
||||||
MhExecute HdrType = 2
|
|
||||||
MhCore HdrType = 4
|
|
||||||
MhDylib HdrType = 6
|
|
||||||
MhBundle HdrType = 8
|
|
||||||
MhDsym HdrType = 0xa
|
|
||||||
)
|
|
||||||
|
|
||||||
var typeStrings = []intName{
|
|
||||||
{uint32(MhObject), "Obj"},
|
|
||||||
{uint32(MhExecute), "Exec"},
|
|
||||||
{uint32(MhDylib), "Dylib"},
|
|
||||||
{uint32(MhBundle), "Bundle"},
|
|
||||||
{uint32(MhDsym), "Dsym"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t HdrType) String() string { return stringName(uint32(t), typeStrings, false) }
|
|
||||||
func (t HdrType) GoString() string { return stringName(uint32(t), typeStrings, true) }
|
|
||||||
|
|
||||||
// A Cpu is a Mach-O cpu type.
|
|
||||||
type Cpu uint32
|
|
||||||
|
|
||||||
const cpuArch64 = 0x01000000
|
|
||||||
|
|
||||||
const (
|
|
||||||
Cpu386 Cpu = 7
|
|
||||||
CpuAmd64 Cpu = Cpu386 | cpuArch64
|
|
||||||
CpuArm Cpu = 12
|
|
||||||
CpuArm64 Cpu = CpuArm | cpuArch64
|
|
||||||
CpuPpc Cpu = 18
|
|
||||||
CpuPpc64 Cpu = CpuPpc | cpuArch64
|
|
||||||
)
|
|
||||||
|
|
||||||
var cpuStrings = []intName{
|
|
||||||
{uint32(Cpu386), "Cpu386"},
|
|
||||||
{uint32(CpuAmd64), "CpuAmd64"},
|
|
||||||
{uint32(CpuArm), "CpuArm"},
|
|
||||||
{uint32(CpuArm64), "CpuArm64"},
|
|
||||||
{uint32(CpuPpc), "CpuPpc"},
|
|
||||||
{uint32(CpuPpc64), "CpuPpc64"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Cpu) String() string { return stringName(uint32(i), cpuStrings, false) }
|
|
||||||
func (i Cpu) GoString() string { return stringName(uint32(i), cpuStrings, true) }
|
|
||||||
|
|
||||||
// A LoadCmd is a Mach-O load command.
|
|
||||||
type LoadCmd uint32
|
|
||||||
|
|
||||||
func (c LoadCmd) Command() LoadCmd { return c }
|
|
||||||
|
|
||||||
const ( // SNAKE_CASE to CamelCase translation from C names
|
|
||||||
// Note 3 and 8 are obsolete
|
|
||||||
LcSegment LoadCmd = 0x1
|
|
||||||
LcSymtab LoadCmd = 0x2
|
|
||||||
LcThread LoadCmd = 0x4
|
|
||||||
LcUnixthread LoadCmd = 0x5 // thread+stack
|
|
||||||
LcDysymtab LoadCmd = 0xb
|
|
||||||
LcDylib LoadCmd = 0xc // load dylib command
|
|
||||||
LcIdDylib LoadCmd = 0xd // dynamically linked shared lib ident
|
|
||||||
LcLoadDylinker LoadCmd = 0xe // load a dynamic linker
|
|
||||||
LcIdDylinker LoadCmd = 0xf // id dylinker command (not load dylinker command)
|
|
||||||
LcSegment64 LoadCmd = 0x19
|
|
||||||
LcUuid LoadCmd = 0x1b
|
|
||||||
LcCodeSignature LoadCmd = 0x1d
|
|
||||||
LcSegmentSplitInfo LoadCmd = 0x1e
|
|
||||||
LcRpath LoadCmd = 0x8000001c
|
|
||||||
LcEncryptionInfo LoadCmd = 0x21
|
|
||||||
LcDyldInfo LoadCmd = 0x22
|
|
||||||
LcDyldInfoOnly LoadCmd = 0x80000022
|
|
||||||
LcVersionMinMacosx LoadCmd = 0x24
|
|
||||||
LcVersionMinIphoneos LoadCmd = 0x25
|
|
||||||
LcFunctionStarts LoadCmd = 0x26
|
|
||||||
LcDyldEnvironment LoadCmd = 0x27
|
|
||||||
LcMain LoadCmd = 0x80000028 // replacement for UnixThread
|
|
||||||
LcDataInCode LoadCmd = 0x29 // There are non-instructions in text
|
|
||||||
LcSourceVersion LoadCmd = 0x2a // Source version used to build binary
|
|
||||||
LcDylibCodeSignDrs LoadCmd = 0x2b
|
|
||||||
LcEncryptionInfo64 LoadCmd = 0x2c
|
|
||||||
LcVersionMinTvos LoadCmd = 0x2f
|
|
||||||
LcVersionMinWatchos LoadCmd = 0x30
|
|
||||||
)
|
|
||||||
|
|
||||||
var cmdStrings = []intName{
|
|
||||||
{uint32(LcSegment), "LoadCmdSegment"},
|
|
||||||
{uint32(LcThread), "LoadCmdThread"},
|
|
||||||
{uint32(LcUnixthread), "LoadCmdUnixThread"},
|
|
||||||
{uint32(LcDylib), "LoadCmdDylib"},
|
|
||||||
{uint32(LcIdDylib), "LoadCmdIdDylib"},
|
|
||||||
{uint32(LcLoadDylinker), "LoadCmdLoadDylinker"},
|
|
||||||
{uint32(LcIdDylinker), "LoadCmdIdDylinker"},
|
|
||||||
{uint32(LcSegment64), "LoadCmdSegment64"},
|
|
||||||
{uint32(LcUuid), "LoadCmdUuid"},
|
|
||||||
{uint32(LcRpath), "LoadCmdRpath"},
|
|
||||||
{uint32(LcDyldEnvironment), "LoadCmdDyldEnv"},
|
|
||||||
{uint32(LcMain), "LoadCmdMain"},
|
|
||||||
{uint32(LcDataInCode), "LoadCmdDataInCode"},
|
|
||||||
{uint32(LcSourceVersion), "LoadCmdSourceVersion"},
|
|
||||||
{uint32(LcDyldInfo), "LoadCmdDyldInfo"},
|
|
||||||
{uint32(LcDyldInfoOnly), "LoadCmdDyldInfoOnly"},
|
|
||||||
{uint32(LcVersionMinMacosx), "LoadCmdMinOsx"},
|
|
||||||
{uint32(LcFunctionStarts), "LoadCmdFunctionStarts"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i LoadCmd) String() string { return stringName(uint32(i), cmdStrings, false) }
|
|
||||||
func (i LoadCmd) GoString() string { return stringName(uint32(i), cmdStrings, true) }
|
|
||||||
|
|
||||||
type (
|
|
||||||
// A Segment32 is a 32-bit Mach-O segment load command.
|
|
||||||
Segment32 struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Name [16]byte
|
|
||||||
Addr uint32
|
|
||||||
Memsz uint32
|
|
||||||
Offset uint32
|
|
||||||
Filesz uint32
|
|
||||||
Maxprot uint32
|
|
||||||
Prot uint32
|
|
||||||
Nsect uint32
|
|
||||||
Flag SegFlags
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Segment64 is a 64-bit Mach-O segment load command.
|
|
||||||
Segment64 struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Name [16]byte
|
|
||||||
Addr uint64
|
|
||||||
Memsz uint64
|
|
||||||
Offset uint64
|
|
||||||
Filesz uint64
|
|
||||||
Maxprot uint32
|
|
||||||
Prot uint32
|
|
||||||
Nsect uint32
|
|
||||||
Flag SegFlags
|
|
||||||
}
|
|
||||||
|
|
||||||
// A SymtabCmd is a Mach-O symbol table command.
|
|
||||||
SymtabCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Symoff uint32
|
|
||||||
Nsyms uint32
|
|
||||||
Stroff uint32
|
|
||||||
Strsize uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// A DysymtabCmd is a Mach-O dynamic symbol table command.
|
|
||||||
DysymtabCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Ilocalsym uint32
|
|
||||||
Nlocalsym uint32
|
|
||||||
Iextdefsym uint32
|
|
||||||
Nextdefsym uint32
|
|
||||||
Iundefsym uint32
|
|
||||||
Nundefsym uint32
|
|
||||||
Tocoffset uint32
|
|
||||||
Ntoc uint32
|
|
||||||
Modtaboff uint32
|
|
||||||
Nmodtab uint32
|
|
||||||
Extrefsymoff uint32
|
|
||||||
Nextrefsyms uint32
|
|
||||||
Indirectsymoff uint32
|
|
||||||
Nindirectsyms uint32
|
|
||||||
Extreloff uint32
|
|
||||||
Nextrel uint32
|
|
||||||
Locreloff uint32
|
|
||||||
Nlocrel uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// A DylibCmd is a Mach-O load dynamic library command.
|
|
||||||
DylibCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Name uint32
|
|
||||||
Time uint32
|
|
||||||
CurrentVersion uint32
|
|
||||||
CompatVersion uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// A DylinkerCmd is a Mach-O load dynamic linker or environment command.
|
|
||||||
DylinkerCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Name uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// A RpathCmd is a Mach-O rpath command.
|
|
||||||
RpathCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Path uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Thread is a Mach-O thread state command.
|
|
||||||
Thread struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Type uint32
|
|
||||||
Data []uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// LC_DYLD_INFO, LC_DYLD_INFO_ONLY
|
|
||||||
DyldInfoCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
RebaseOff, RebaseLen uint32 // file offset and length; data contains segment indices
|
|
||||||
BindOff, BindLen uint32 // file offset and length; data contains segment indices
|
|
||||||
WeakBindOff, WeakBindLen uint32 // file offset and length
|
|
||||||
LazyBindOff, LazyBindLen uint32 // file offset and length
|
|
||||||
ExportOff, ExportLen uint32 // file offset and length
|
|
||||||
}
|
|
||||||
|
|
||||||
// LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS
|
|
||||||
LinkEditDataCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
DataOff, DataLen uint32 // file offset and length
|
|
||||||
}
|
|
||||||
|
|
||||||
// LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64
|
|
||||||
EncryptionInfoCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
CryptOff, CryptLen uint32 // file offset and length
|
|
||||||
CryptId uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
UuidCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Id [16]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Commands below not fully supported yet.
|
|
||||||
|
|
||||||
EntryPointCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
EntryOff uint64 // file offset
|
|
||||||
StackSize uint64 // if not zero, initial stack size
|
|
||||||
}
|
|
||||||
|
|
||||||
NoteCmd struct {
|
|
||||||
LoadCmd
|
|
||||||
Len uint32
|
|
||||||
Name [16]byte
|
|
||||||
Offset, Filesz uint64 // file offset and length
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
FlagNoUndefs HdrFlags = 0x1
|
|
||||||
FlagIncrLink HdrFlags = 0x2
|
|
||||||
FlagDyldLink HdrFlags = 0x4
|
|
||||||
FlagBindAtLoad HdrFlags = 0x8
|
|
||||||
FlagPrebound HdrFlags = 0x10
|
|
||||||
FlagSplitSegs HdrFlags = 0x20
|
|
||||||
FlagLazyInit HdrFlags = 0x40
|
|
||||||
FlagTwoLevel HdrFlags = 0x80
|
|
||||||
FlagForceFlat HdrFlags = 0x100
|
|
||||||
FlagNoMultiDefs HdrFlags = 0x200
|
|
||||||
FlagNoFixPrebinding HdrFlags = 0x400
|
|
||||||
FlagPrebindable HdrFlags = 0x800
|
|
||||||
FlagAllModsBound HdrFlags = 0x1000
|
|
||||||
FlagSubsectionsViaSymbols HdrFlags = 0x2000
|
|
||||||
FlagCanonical HdrFlags = 0x4000
|
|
||||||
FlagWeakDefines HdrFlags = 0x8000
|
|
||||||
FlagBindsToWeak HdrFlags = 0x10000
|
|
||||||
FlagAllowStackExecution HdrFlags = 0x20000
|
|
||||||
FlagRootSafe HdrFlags = 0x40000
|
|
||||||
FlagSetuidSafe HdrFlags = 0x80000
|
|
||||||
FlagNoReexportedDylibs HdrFlags = 0x100000
|
|
||||||
FlagPIE HdrFlags = 0x200000
|
|
||||||
FlagDeadStrippableDylib HdrFlags = 0x400000
|
|
||||||
FlagHasTLVDescriptors HdrFlags = 0x800000
|
|
||||||
FlagNoHeapExecution HdrFlags = 0x1000000
|
|
||||||
FlagAppExtensionSafe HdrFlags = 0x2000000
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Section32 is a 32-bit Mach-O section header.
|
|
||||||
type Section32 struct {
|
|
||||||
Name [16]byte
|
|
||||||
Seg [16]byte
|
|
||||||
Addr uint32
|
|
||||||
Size uint32
|
|
||||||
Offset uint32
|
|
||||||
Align uint32
|
|
||||||
Reloff uint32
|
|
||||||
Nreloc uint32
|
|
||||||
Flags SecFlags
|
|
||||||
Reserve1 uint32
|
|
||||||
Reserve2 uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Section64 is a 64-bit Mach-O section header.
|
|
||||||
type Section64 struct {
|
|
||||||
Name [16]byte
|
|
||||||
Seg [16]byte
|
|
||||||
Addr uint64
|
|
||||||
Size uint64
|
|
||||||
Offset uint32
|
|
||||||
Align uint32
|
|
||||||
Reloff uint32
|
|
||||||
Nreloc uint32
|
|
||||||
Flags SecFlags
|
|
||||||
Reserve1 uint32
|
|
||||||
Reserve2 uint32
|
|
||||||
Reserve3 uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Nlist32 is a Mach-O 32-bit symbol table entry.
|
|
||||||
type Nlist32 struct {
|
|
||||||
Name uint32
|
|
||||||
Type uint8
|
|
||||||
Sect uint8
|
|
||||||
Desc uint16
|
|
||||||
Value uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Nlist64 is a Mach-O 64-bit symbol table entry.
|
|
||||||
type Nlist64 struct {
|
|
||||||
Name uint32
|
|
||||||
Type uint8
|
|
||||||
Sect uint8
|
|
||||||
Desc uint16
|
|
||||||
Value uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Nlist64) Put64(b []byte, o binary.ByteOrder) uint32 {
|
|
||||||
o.PutUint32(b[0:], n.Name)
|
|
||||||
b[4] = byte(n.Type)
|
|
||||||
b[5] = byte(n.Sect)
|
|
||||||
o.PutUint16(b[6:], n.Desc)
|
|
||||||
o.PutUint64(b[8:], n.Value)
|
|
||||||
return 8 + 8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Nlist64) Put32(b []byte, o binary.ByteOrder) uint32 {
|
|
||||||
o.PutUint32(b[0:], n.Name)
|
|
||||||
b[4] = byte(n.Type)
|
|
||||||
b[5] = byte(n.Sect)
|
|
||||||
o.PutUint16(b[6:], n.Desc)
|
|
||||||
o.PutUint32(b[8:], uint32(n.Value))
|
|
||||||
return 8 + 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regs386 is the Mach-O 386 register structure.
|
|
||||||
type Regs386 struct {
|
|
||||||
AX uint32
|
|
||||||
BX uint32
|
|
||||||
CX uint32
|
|
||||||
DX uint32
|
|
||||||
DI uint32
|
|
||||||
SI uint32
|
|
||||||
BP uint32
|
|
||||||
SP uint32
|
|
||||||
SS uint32
|
|
||||||
FLAGS uint32
|
|
||||||
IP uint32
|
|
||||||
CS uint32
|
|
||||||
DS uint32
|
|
||||||
ES uint32
|
|
||||||
FS uint32
|
|
||||||
GS uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegsAMD64 is the Mach-O AMD64 register structure.
|
|
||||||
type RegsAMD64 struct {
|
|
||||||
AX uint64
|
|
||||||
BX uint64
|
|
||||||
CX uint64
|
|
||||||
DX uint64
|
|
||||||
DI uint64
|
|
||||||
SI uint64
|
|
||||||
BP uint64
|
|
||||||
SP uint64
|
|
||||||
R8 uint64
|
|
||||||
R9 uint64
|
|
||||||
R10 uint64
|
|
||||||
R11 uint64
|
|
||||||
R12 uint64
|
|
||||||
R13 uint64
|
|
||||||
R14 uint64
|
|
||||||
R15 uint64
|
|
||||||
IP uint64
|
|
||||||
FLAGS uint64
|
|
||||||
CS uint64
|
|
||||||
FS uint64
|
|
||||||
GS uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type intName struct {
|
|
||||||
i uint32
|
|
||||||
s string
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringName(i uint32, names []intName, goSyntax bool) string {
|
|
||||||
for _, n := range names {
|
|
||||||
if n.i == i {
|
|
||||||
if goSyntax {
|
|
||||||
return "macho." + n.s
|
|
||||||
}
|
|
||||||
return n.s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "0x" + strconv.FormatUint(uint64(i), 16)
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package macho
|
|
||||||
|
|
||||||
//go:generate stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go
|
|
||||||
|
|
||||||
type RelocTypeGeneric int
|
|
||||||
|
|
||||||
const (
|
|
||||||
GENERIC_RELOC_VANILLA RelocTypeGeneric = 0
|
|
||||||
GENERIC_RELOC_PAIR RelocTypeGeneric = 1
|
|
||||||
GENERIC_RELOC_SECTDIFF RelocTypeGeneric = 2
|
|
||||||
GENERIC_RELOC_PB_LA_PTR RelocTypeGeneric = 3
|
|
||||||
GENERIC_RELOC_LOCAL_SECTDIFF RelocTypeGeneric = 4
|
|
||||||
GENERIC_RELOC_TLV RelocTypeGeneric = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r RelocTypeGeneric) GoString() string { return "macho." + r.String() }
|
|
||||||
|
|
||||||
type RelocTypeX86_64 int
|
|
||||||
|
|
||||||
const (
|
|
||||||
X86_64_RELOC_UNSIGNED RelocTypeX86_64 = 0
|
|
||||||
X86_64_RELOC_SIGNED RelocTypeX86_64 = 1
|
|
||||||
X86_64_RELOC_BRANCH RelocTypeX86_64 = 2
|
|
||||||
X86_64_RELOC_GOT_LOAD RelocTypeX86_64 = 3
|
|
||||||
X86_64_RELOC_GOT RelocTypeX86_64 = 4
|
|
||||||
X86_64_RELOC_SUBTRACTOR RelocTypeX86_64 = 5
|
|
||||||
X86_64_RELOC_SIGNED_1 RelocTypeX86_64 = 6
|
|
||||||
X86_64_RELOC_SIGNED_2 RelocTypeX86_64 = 7
|
|
||||||
X86_64_RELOC_SIGNED_4 RelocTypeX86_64 = 8
|
|
||||||
X86_64_RELOC_TLV RelocTypeX86_64 = 9
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r RelocTypeX86_64) GoString() string { return "macho." + r.String() }
|
|
||||||
|
|
||||||
type RelocTypeARM int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ARM_RELOC_VANILLA RelocTypeARM = 0
|
|
||||||
ARM_RELOC_PAIR RelocTypeARM = 1
|
|
||||||
ARM_RELOC_SECTDIFF RelocTypeARM = 2
|
|
||||||
ARM_RELOC_LOCAL_SECTDIFF RelocTypeARM = 3
|
|
||||||
ARM_RELOC_PB_LA_PTR RelocTypeARM = 4
|
|
||||||
ARM_RELOC_BR24 RelocTypeARM = 5
|
|
||||||
ARM_THUMB_RELOC_BR22 RelocTypeARM = 6
|
|
||||||
ARM_THUMB_32BIT_BRANCH RelocTypeARM = 7
|
|
||||||
ARM_RELOC_HALF RelocTypeARM = 8
|
|
||||||
ARM_RELOC_HALF_SECTDIFF RelocTypeARM = 9
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r RelocTypeARM) GoString() string { return "macho." + r.String() }
|
|
||||||
|
|
||||||
type RelocTypeARM64 int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ARM64_RELOC_UNSIGNED RelocTypeARM64 = 0
|
|
||||||
ARM64_RELOC_SUBTRACTOR RelocTypeARM64 = 1
|
|
||||||
ARM64_RELOC_BRANCH26 RelocTypeARM64 = 2
|
|
||||||
ARM64_RELOC_PAGE21 RelocTypeARM64 = 3
|
|
||||||
ARM64_RELOC_PAGEOFF12 RelocTypeARM64 = 4
|
|
||||||
ARM64_RELOC_GOT_LOAD_PAGE21 RelocTypeARM64 = 5
|
|
||||||
ARM64_RELOC_GOT_LOAD_PAGEOFF12 RelocTypeARM64 = 6
|
|
||||||
ARM64_RELOC_POINTER_TO_GOT RelocTypeARM64 = 7
|
|
||||||
ARM64_RELOC_TLVP_LOAD_PAGE21 RelocTypeARM64 = 8
|
|
||||||
ARM64_RELOC_TLVP_LOAD_PAGEOFF12 RelocTypeARM64 = 9
|
|
||||||
ARM64_RELOC_ADDEND RelocTypeARM64 = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r RelocTypeARM64) GoString() string { return "macho." + r.String() }
|
|
|
@ -1,49 +0,0 @@
|
||||||
// Code generated by "stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package macho
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
const _RelocTypeGeneric_name = "GENERIC_RELOC_VANILLAGENERIC_RELOC_PAIRGENERIC_RELOC_SECTDIFFGENERIC_RELOC_PB_LA_PTRGENERIC_RELOC_LOCAL_SECTDIFFGENERIC_RELOC_TLV"
|
|
||||||
|
|
||||||
var _RelocTypeGeneric_index = [...]uint8{0, 21, 39, 61, 84, 112, 129}
|
|
||||||
|
|
||||||
func (i RelocTypeGeneric) String() string {
|
|
||||||
if i < 0 || i >= RelocTypeGeneric(len(_RelocTypeGeneric_index)-1) {
|
|
||||||
return "RelocTypeGeneric(" + strconv.FormatInt(int64(i), 10) + ")"
|
|
||||||
}
|
|
||||||
return _RelocTypeGeneric_name[_RelocTypeGeneric_index[i]:_RelocTypeGeneric_index[i+1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
const _RelocTypeX86_64_name = "X86_64_RELOC_UNSIGNEDX86_64_RELOC_SIGNEDX86_64_RELOC_BRANCHX86_64_RELOC_GOT_LOADX86_64_RELOC_GOTX86_64_RELOC_SUBTRACTORX86_64_RELOC_SIGNED_1X86_64_RELOC_SIGNED_2X86_64_RELOC_SIGNED_4X86_64_RELOC_TLV"
|
|
||||||
|
|
||||||
var _RelocTypeX86_64_index = [...]uint8{0, 21, 40, 59, 80, 96, 119, 140, 161, 182, 198}
|
|
||||||
|
|
||||||
func (i RelocTypeX86_64) String() string {
|
|
||||||
if i < 0 || i >= RelocTypeX86_64(len(_RelocTypeX86_64_index)-1) {
|
|
||||||
return "RelocTypeX86_64(" + strconv.FormatInt(int64(i), 10) + ")"
|
|
||||||
}
|
|
||||||
return _RelocTypeX86_64_name[_RelocTypeX86_64_index[i]:_RelocTypeX86_64_index[i+1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
const _RelocTypeARM_name = "ARM_RELOC_VANILLAARM_RELOC_PAIRARM_RELOC_SECTDIFFARM_RELOC_LOCAL_SECTDIFFARM_RELOC_PB_LA_PTRARM_RELOC_BR24ARM_THUMB_RELOC_BR22ARM_THUMB_32BIT_BRANCHARM_RELOC_HALFARM_RELOC_HALF_SECTDIFF"
|
|
||||||
|
|
||||||
var _RelocTypeARM_index = [...]uint8{0, 17, 31, 49, 73, 92, 106, 126, 148, 162, 185}
|
|
||||||
|
|
||||||
func (i RelocTypeARM) String() string {
|
|
||||||
if i < 0 || i >= RelocTypeARM(len(_RelocTypeARM_index)-1) {
|
|
||||||
return "RelocTypeARM(" + strconv.FormatInt(int64(i), 10) + ")"
|
|
||||||
}
|
|
||||||
return _RelocTypeARM_name[_RelocTypeARM_index[i]:_RelocTypeARM_index[i+1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
const _RelocTypeARM64_name = "ARM64_RELOC_UNSIGNEDARM64_RELOC_SUBTRACTORARM64_RELOC_BRANCH26ARM64_RELOC_PAGE21ARM64_RELOC_PAGEOFF12ARM64_RELOC_GOT_LOAD_PAGE21ARM64_RELOC_GOT_LOAD_PAGEOFF12ARM64_RELOC_POINTER_TO_GOTARM64_RELOC_TLVP_LOAD_PAGE21ARM64_RELOC_TLVP_LOAD_PAGEOFF12ARM64_RELOC_ADDEND"
|
|
||||||
|
|
||||||
var _RelocTypeARM64_index = [...]uint16{0, 20, 42, 62, 80, 101, 128, 158, 184, 212, 243, 261}
|
|
||||||
|
|
||||||
func (i RelocTypeARM64) String() string {
|
|
||||||
if i < 0 || i >= RelocTypeARM64(len(_RelocTypeARM64_index)-1) {
|
|
||||||
return "RelocTypeARM64(" + strconv.FormatInt(int64(i), 10) + ")"
|
|
||||||
}
|
|
||||||
return _RelocTypeARM64_name[_RelocTypeARM64_index[i]:_RelocTypeARM64_index[i+1]]
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue