properly added tooling to vendor
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
5711b46a8b
commit
ab65c60cb0
10
Makefile
10
Makefile
|
@ -19,12 +19,12 @@ RELEASE_NAME := Konfi-Castle-Kasino
|
|||
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
|
||||
GOFMT ?= gofmt -s
|
||||
|
||||
GOFLAGS := -i -v
|
||||
GOFLAGS := -v -mod=vendor
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
LDFLAGS := -X "git.kolaente.de/konrad/Konfi-Castle-Kasino/pkg/models.Version=$(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')" -X "main.Tags=$(TAGS)"
|
||||
|
||||
PACKAGES ?= $(filter-out git.mowie.cc/konrad/Konfi-Castle-Kasino/integrations,$(shell go list ./... | grep -v /vendor/))
|
||||
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||
|
||||
TAGS ?=
|
||||
|
@ -92,7 +92,7 @@ release-dirs:
|
|||
.PHONY: release-windows
|
||||
release-windows:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out $(RELEASE_NAME)-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
|
@ -102,7 +102,7 @@ endif
|
|||
.PHONY: release-linux
|
||||
release-linux:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out $(RELEASE_NAME)-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
|
@ -112,7 +112,7 @@ endif
|
|||
.PHONY: release-darwin
|
||||
release-darwin:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out $(RELEASE_NAME)-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
|
|
2
go.mod
2
go.mod
|
@ -15,7 +15,9 @@ require (
|
|||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 // indirect
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
|
||||
gopkg.in/ini.v1 v1.46.0 // indirect
|
||||
src.techknowlogick.com/xgo v0.0.0-20190904182048-b4fba6551e4a
|
||||
xorm.io/core v0.7.0
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -168,6 +168,7 @@ golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f h1:hX65Cu3JDlGH3uEdK7I99Ii+9kjD6mvnnpfLdEAH0x4=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -218,6 +219,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190610214847-0945d3616f18 h1:zHtiOwI/XPjMtdN/qnztM+l9nQRDJhvLztmrkFw/wn0=
|
||||
golang.org/x/tools v0.0.0-20190610214847-0945d3616f18/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
|
@ -244,6 +246,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
src.techknowlogick.com/xgo v0.0.0-20190904182048-b4fba6551e4a h1:EprwDBbbJvK7QYYjjYoL1NAb6l8E6f31HziFMqmLFzU=
|
||||
src.techknowlogick.com/xgo v0.0.0-20190904182048-b4fba6551e4a/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
|
||||
xorm.io/builder v0.3.5 h1:EilU39fvWDxjb1cDaELpYhsF+zziRBhew8xk4pngO+A=
|
||||
xorm.io/builder v0.3.5/go.mod h1:ZFbByS/KxZI1FKRjL05PyJ4YrK2bcxlUaAxdum5aTR8=
|
||||
xorm.io/core v0.7.0 h1:hKxuOKWZNeiFQsSuGet/KV8HZ788hclvAl+7azx3tkM=
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
// +build tools
|
||||
|
||||
package tools
|
||||
|
||||
// This file is needed for go mod to recognize the tools we use.
|
||||
|
||||
import (
|
||||
_ "golang.org/x/lint/golint"
|
||||
_ "src.techknowlogick.com/xgo"
|
||||
)
|
|
@ -1,9 +0,0 @@
|
|||
# Build and Release Folders
|
||||
bin-debug/
|
||||
bin-release/
|
||||
|
||||
# Project property files
|
||||
.actionScriptProperties
|
||||
.flexProperties
|
||||
.settings/
|
||||
.project
|
|
@ -1,3 +0,0 @@
|
|||
this repo will not maintain
|
||||
|
||||
move to [beego/session](https://github.com/astaxie/beego/tree/master/session)
|
|
@ -1,111 +0,0 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"github.com/astaxie/session"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var pder = &Provider{list: list.New()}
|
||||
|
||||
type SessionStore struct {
|
||||
sid string //session id唯一标示
|
||||
timeAccessed time.Time //最后访问时间
|
||||
value map[interface{}]interface{} //session里面存储的值
|
||||
}
|
||||
|
||||
func (st *SessionStore) Set(key, value interface{}) error {
|
||||
st.value[key] = value
|
||||
pder.SessionUpdate(st.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) Get(key interface{}) interface{} {
|
||||
pder.SessionUpdate(st.sid)
|
||||
if v, ok := st.value[key]; ok {
|
||||
return v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) Delete(key interface{}) error {
|
||||
delete(st.value, key)
|
||||
pder.SessionUpdate(st.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) SessionID() string {
|
||||
return st.sid
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
lock sync.Mutex //用来锁
|
||||
sessions map[string]*list.Element //用来存储在内存
|
||||
list *list.List //用来做gc
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionInit(sid string) (session.Session, error) {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
v := make(map[interface{}]interface{}, 0)
|
||||
newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
|
||||
element := pder.list.PushBack(newsess)
|
||||
pder.sessions[sid] = element
|
||||
return newsess, nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionRead(sid string) (session.Session, error) {
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
return element.Value.(*SessionStore), nil
|
||||
} else {
|
||||
sess, err := pder.SessionInit(sid)
|
||||
return sess, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionDestroy(sid string) error {
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
delete(pder.sessions, sid)
|
||||
pder.list.Remove(element)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionGC(maxlifetime int64) {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
|
||||
for {
|
||||
element := pder.list.Back()
|
||||
if element == nil {
|
||||
break
|
||||
}
|
||||
if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
|
||||
pder.list.Remove(element)
|
||||
delete(pder.sessions, element.Value.(*SessionStore).sid)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionUpdate(sid string) error {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
element.Value.(*SessionStore).timeAccessed = time.Now()
|
||||
pder.list.MoveToFront(element)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
pder.sessions = make(map[string]*list.Element, 0)
|
||||
session.Register("memory", pder)
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Session interface {
|
||||
Set(key, value interface{}) error //set session value
|
||||
Get(key interface{}) interface{} //get session value
|
||||
Delete(key interface{}) error //delete session value
|
||||
SessionID() string //back current sessionID
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
SessionInit(sid string) (Session, error)
|
||||
SessionRead(sid string) (Session, error)
|
||||
SessionDestroy(sid string) error
|
||||
SessionGC(maxlifetime int64)
|
||||
}
|
||||
|
||||
var provides = make(map[string]Provider)
|
||||
|
||||
// Register makes a session provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, provide Provider) {
|
||||
if provide == nil {
|
||||
panic("session: Register provide is nil")
|
||||
}
|
||||
if _, dup := provides[name]; dup {
|
||||
panic("session: Register called twice for provide " + name)
|
||||
}
|
||||
provides[name] = provide
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
cookieName string //private cookiename
|
||||
lock sync.Mutex // protects session
|
||||
provider Provider
|
||||
maxlifetime int64
|
||||
}
|
||||
|
||||
func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
|
||||
provider, ok := provides[provideName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
|
||||
}
|
||||
return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
|
||||
}
|
||||
|
||||
//get Session
|
||||
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
cookie, err := r.Cookie(manager.cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
sid := manager.sessionId()
|
||||
session, _ = manager.provider.SessionInit(sid)
|
||||
cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)}
|
||||
http.SetCookie(w, &cookie)
|
||||
} else {
|
||||
sid, _ := url.QueryUnescape(cookie.Value)
|
||||
session, _ = manager.provider.SessionRead(sid)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//Destroy sessionid
|
||||
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie(manager.cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
return
|
||||
} else {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
manager.provider.SessionDestroy(cookie.Value)
|
||||
expiration := time.Now()
|
||||
cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *Manager) GC() {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
manager.provider.SessionGC(manager.maxlifetime)
|
||||
time.AfterFunc(time.Duration(manager.maxlifetime)*time.Second, func() { manager.GC() })
|
||||
}
|
||||
|
||||
func (manager *Manager) sessionId() string {
|
||||
b := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return ""
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(b)
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2014, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
# browser
|
||||
import "github.com/pkg/browser"
|
||||
|
||||
Package browser provides helpers to open files, readers, and urls in a browser window.
|
||||
|
||||
The choice of which browser is started is entirely client dependant.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Variables
|
||||
``` go
|
||||
var Stderr io.Writer = os.Stderr
|
||||
```
|
||||
Stderr is the io.Writer to which executed commands write standard error.
|
||||
|
||||
``` go
|
||||
var Stdout io.Writer = os.Stdout
|
||||
```
|
||||
Stdout is the io.Writer to which executed commands write standard output.
|
||||
|
||||
|
||||
## func OpenFile
|
||||
``` go
|
||||
func OpenFile(path string) error
|
||||
```
|
||||
OpenFile opens new browser window for the file path.
|
||||
|
||||
|
||||
## func OpenReader
|
||||
``` go
|
||||
func OpenReader(r io.Reader) error
|
||||
```
|
||||
OpenReader consumes the contents of r and presents the
|
||||
results in a new browser window.
|
||||
|
||||
|
||||
## func OpenURL
|
||||
``` go
|
||||
func OpenURL(url string) error
|
||||
```
|
||||
OpenURL opens a new browser window pointing to url.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- - -
|
||||
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
|
|
@ -0,0 +1,63 @@
|
|||
// Package browser provides helpers to open files, readers, and urls in a browser window.
|
||||
//
|
||||
// The choice of which browser is started is entirely client dependant.
|
||||
package browser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Stdout is the io.Writer to which executed commands write standard output.
|
||||
var Stdout io.Writer = os.Stdout
|
||||
|
||||
// Stderr is the io.Writer to which executed commands write standard error.
|
||||
var Stderr io.Writer = os.Stderr
|
||||
|
||||
// OpenFile opens new browser window for the file path.
|
||||
func OpenFile(path string) error {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return OpenURL("file://" + path)
|
||||
}
|
||||
|
||||
// OpenReader consumes the contents of r and presents the
|
||||
// results in a new browser window.
|
||||
func OpenReader(r io.Reader) error {
|
||||
f, err := ioutil.TempFile("", "browser")
|
||||
if err != nil {
|
||||
return fmt.Errorf("browser: could not create temporary file: %v", err)
|
||||
}
|
||||
if _, err := io.Copy(f, r); err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("browser: caching temporary file failed: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("browser: caching temporary file failed: %v", err)
|
||||
}
|
||||
oldname := f.Name()
|
||||
newname := oldname + ".html"
|
||||
if err := os.Rename(oldname, newname); err != nil {
|
||||
return fmt.Errorf("browser: renaming temporary file failed: %v", err)
|
||||
}
|
||||
return OpenFile(newname)
|
||||
}
|
||||
|
||||
// OpenURL opens a new browser window pointing to url.
|
||||
func OpenURL(url string) error {
|
||||
return openBrowser(url)
|
||||
}
|
||||
|
||||
func runCmd(prog string, args ...string) error {
|
||||
cmd := exec.Command(prog, args...)
|
||||
cmd.Stdout = Stdout
|
||||
cmd.Stderr = Stderr
|
||||
setFlags(cmd)
|
||||
return cmd.Run()
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package browser
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func openBrowser(url string) error {
|
||||
return runCmd("open", url)
|
||||
}
|
||||
|
||||
func setFlags(cmd *exec.Cmd) {}
|
|
@ -0,0 +1,9 @@
|
|||
package browser
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func openBrowser(url string) error {
|
||||
return runCmd("xdg-open", url)
|
||||
}
|
||||
|
||||
func setFlags(cmd *exec.Cmd) {}
|
|
@ -0,0 +1,16 @@
|
|||
package browser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func openBrowser(url string) error {
|
||||
err := runCmd("xdg-open", url)
|
||||
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
|
||||
return errors.New("xdg-open: command not found - install xdg-utils from ports(8)")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func setFlags(cmd *exec.Cmd) {}
|
|
@ -0,0 +1,15 @@
|
|||
// +build !linux,!windows,!darwin,!openbsd
|
||||
|
||||
package browser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func openBrowser(url string) error {
|
||||
return fmt.Errorf("openBrowser: unsupported operating system: %v", runtime.GOOS)
|
||||
}
|
||||
|
||||
func setFlags(cmd *exec.Cmd) {}
|
|
@ -0,0 +1,16 @@
|
|||
package browser
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func openBrowser(url string) error {
|
||||
r := strings.NewReplacer("&", "^&")
|
||||
return runCmd("cmd", "/c", "start", r.Replace(url))
|
||||
}
|
||||
|
||||
func setFlags(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- master
|
||||
|
||||
go_import_path: golang.org/x/lint
|
||||
|
||||
install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go test -v -race ./...
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
fast_finish: true
|
|
@ -0,0 +1,15 @@
|
|||
# Contributing to Golint
|
||||
|
||||
## Before filing an issue:
|
||||
|
||||
### Are you having trouble building golint?
|
||||
|
||||
Check you have the latest version of its dependencies. Run
|
||||
```
|
||||
go get -u golang.org/x/lint/golint
|
||||
```
|
||||
If you still have problems, consider searching for existing issues before filing a new issue.
|
||||
|
||||
## Before sending a pull request:
|
||||
|
||||
Have you understood the purpose of golint? Make sure to carefully read `README`.
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2013 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,86 @@
|
|||
Golint is a linter for Go source code.
|
||||
|
||||
[![Build Status](https://travis-ci.org/golang/lint.svg?branch=master)](https://travis-ci.org/golang/lint)
|
||||
|
||||
## Installation
|
||||
|
||||
Golint requires a
|
||||
[supported release of Go](https://golang.org/doc/devel/release.html#policy).
|
||||
|
||||
go get -u golang.org/x/lint/golint
|
||||
|
||||
## Usage
|
||||
|
||||
Invoke `golint` with one or more filenames, directories, or packages named
|
||||
by its import path. Golint uses the same
|
||||
[import path syntax](https://golang.org/cmd/go/#hdr-Import_path_syntax) as
|
||||
the `go` command and therefore
|
||||
also supports relative import paths like `./...`. Additionally the `...`
|
||||
wildcard can be used as suffix on relative and absolute file paths to recurse
|
||||
into them.
|
||||
|
||||
The output of this tool is a list of suggestions in Vim quickfix format,
|
||||
which is accepted by lots of different editors.
|
||||
|
||||
## Purpose
|
||||
|
||||
Golint differs from gofmt. Gofmt reformats Go source code, whereas
|
||||
golint prints out style mistakes.
|
||||
|
||||
Golint differs from govet. Govet is concerned with correctness, whereas
|
||||
golint is concerned with coding style. Golint is in use at Google, and it
|
||||
seeks to match the accepted style of the open source Go project.
|
||||
|
||||
The suggestions made by golint are exactly that: suggestions.
|
||||
Golint is not perfect, and has both false positives and false negatives.
|
||||
Do not treat its output as a gold standard. We will not be adding pragmas
|
||||
or other knobs to suppress specific warnings, so do not expect or require
|
||||
code to be completely "lint-free".
|
||||
In short, this tool is not, and will never be, trustworthy enough for its
|
||||
suggestions to be enforced automatically, for example as part of a build process.
|
||||
Golint makes suggestions for many of the mechanically checkable items listed in
|
||||
[Effective Go](https://golang.org/doc/effective_go.html) and the
|
||||
[CodeReviewComments wiki page](https://golang.org/wiki/CodeReviewComments).
|
||||
|
||||
## Scope
|
||||
|
||||
Golint is meant to carry out the stylistic conventions put forth in
|
||||
[Effective Go](https://golang.org/doc/effective_go.html) and
|
||||
[CodeReviewComments](https://golang.org/wiki/CodeReviewComments).
|
||||
Changes that are not aligned with those documents will not be considered.
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions to this project are welcome provided they are [in scope](#scope),
|
||||
though please send mail before starting work on anything major.
|
||||
Contributors retain their copyright, so we need you to fill out
|
||||
[a short form](https://developers.google.com/open-source/cla/individual)
|
||||
before we can accept your contribution.
|
||||
|
||||
## Vim
|
||||
|
||||
Add this to your ~/.vimrc:
|
||||
|
||||
set rtp+=$GOPATH/src/golang.org/x/lint/misc/vim
|
||||
|
||||
If you have multiple entries in your GOPATH, replace `$GOPATH` with the right value.
|
||||
|
||||
Running `:Lint` will run golint on the current file and populate the quickfix list.
|
||||
|
||||
Optionally, add this to your `~/.vimrc` to automatically run `golint` on `:w`
|
||||
|
||||
autocmd BufWritePost,FileWritePost *.go execute 'Lint' | cwindow
|
||||
|
||||
|
||||
## Emacs
|
||||
|
||||
Add this to your `.emacs` file:
|
||||
|
||||
(add-to-list 'load-path (concat (getenv "GOPATH") "/src/github.com/golang/lint/misc/emacs"))
|
||||
(require 'golint)
|
||||
|
||||
If you have multiple entries in your GOPATH, replace `$GOPATH` with the right value.
|
||||
|
||||
Running M-x golint will run golint on the current file.
|
||||
|
||||
For more usage, see [Compilation-Mode](http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html).
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) 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 or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// golint lints the Go source files named on its command line.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/lint"
|
||||
)
|
||||
|
||||
var (
|
||||
minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it")
|
||||
setExitStatus = flag.Bool("set_exit_status", false, "set exit status to 1 if any issues are found")
|
||||
suggestions int
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "\tgolint [flags] # runs on package in current directory\n")
|
||||
fmt.Fprintf(os.Stderr, "\tgolint [flags] [packages]\n")
|
||||
fmt.Fprintf(os.Stderr, "\tgolint [flags] [directories] # where a '/...' suffix includes all sub-directories\n")
|
||||
fmt.Fprintf(os.Stderr, "\tgolint [flags] [files] # all must belong to a single package\n")
|
||||
fmt.Fprintf(os.Stderr, "Flags:\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
lintDir(".")
|
||||
} else {
|
||||
// dirsRun, filesRun, and pkgsRun indicate whether golint is applied to
|
||||
// directory, file or package targets. The distinction affects which
|
||||
// checks are run. It is no valid to mix target types.
|
||||
var dirsRun, filesRun, pkgsRun int
|
||||
var args []string
|
||||
for _, arg := range flag.Args() {
|
||||
if strings.HasSuffix(arg, "/...") && isDir(arg[:len(arg)-len("/...")]) {
|
||||
dirsRun = 1
|
||||
for _, dirname := range allPackagesInFS(arg) {
|
||||
args = append(args, dirname)
|
||||
}
|
||||
} else if isDir(arg) {
|
||||
dirsRun = 1
|
||||
args = append(args, arg)
|
||||
} else if exists(arg) {
|
||||
filesRun = 1
|
||||
args = append(args, arg)
|
||||
} else {
|
||||
pkgsRun = 1
|
||||
args = append(args, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if dirsRun+filesRun+pkgsRun != 1 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
switch {
|
||||
case dirsRun == 1:
|
||||
for _, dir := range args {
|
||||
lintDir(dir)
|
||||
}
|
||||
case filesRun == 1:
|
||||
lintFiles(args...)
|
||||
case pkgsRun == 1:
|
||||
for _, pkg := range importPaths(args) {
|
||||
lintPackage(pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *setExitStatus && suggestions > 0 {
|
||||
fmt.Fprintf(os.Stderr, "Found %d lint suggestions; failing.\n", suggestions)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func isDir(filename string) bool {
|
||||
fi, err := os.Stat(filename)
|
||||
return err == nil && fi.IsDir()
|
||||
}
|
||||
|
||||
func exists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func lintFiles(filenames ...string) {
|
||||
files := make(map[string][]byte)
|
||||
for _, filename := range filenames {
|
||||
src, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
files[filename] = src
|
||||
}
|
||||
|
||||
l := new(lint.Linter)
|
||||
ps, err := l.LintFiles(files)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return
|
||||
}
|
||||
for _, p := range ps {
|
||||
if p.Confidence >= *minConfidence {
|
||||
fmt.Printf("%v: %s\n", p.Position, p.Text)
|
||||
suggestions++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lintDir(dirname string) {
|
||||
pkg, err := build.ImportDir(dirname, 0)
|
||||
lintImportedPackage(pkg, err)
|
||||
}
|
||||
|
||||
func lintPackage(pkgname string) {
|
||||
pkg, err := build.Import(pkgname, ".", 0)
|
||||
lintImportedPackage(pkg, err)
|
||||
}
|
||||
|
||||
func lintImportedPackage(pkg *build.Package, err error) {
|
||||
if err != nil {
|
||||
if _, nogo := err.(*build.NoGoError); nogo {
|
||||
// Don't complain if the failure is due to no Go source files.
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
|
||||
var files []string
|
||||
files = append(files, pkg.GoFiles...)
|
||||
files = append(files, pkg.CgoFiles...)
|
||||
files = append(files, pkg.TestGoFiles...)
|
||||
if pkg.Dir != "." {
|
||||
for i, f := range files {
|
||||
files[i] = filepath.Join(pkg.Dir, f)
|
||||
}
|
||||
}
|
||||
// TODO(dsymonds): Do foo_test too (pkg.XTestGoFiles)
|
||||
|
||||
lintFiles(files...)
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
package main
|
||||
|
||||
/*
|
||||
|
||||
This file holds a direct copy of the import path matching code of
|
||||
https://github.com/golang/go/blob/master/src/cmd/go/main.go. It can be
|
||||
replaced when https://golang.org/issue/8768 is resolved.
|
||||
|
||||
It has been updated to follow upstream changes in a few ways.
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
buildContext = build.Default
|
||||
goroot = filepath.Clean(runtime.GOROOT())
|
||||
gorootSrc = filepath.Join(goroot, "src")
|
||||
)
|
||||
|
||||
// importPathsNoDotExpansion returns the import paths to use for the given
|
||||
// command line, but it does no ... expansion.
|
||||
func importPathsNoDotExpansion(args []string) []string {
|
||||
if len(args) == 0 {
|
||||
return []string{"."}
|
||||
}
|
||||
var out []string
|
||||
for _, a := range args {
|
||||
// Arguments are supposed to be import paths, but
|
||||
// as a courtesy to Windows developers, rewrite \ to /
|
||||
// in command-line arguments. Handles .\... and so on.
|
||||
if filepath.Separator == '\\' {
|
||||
a = strings.Replace(a, `\`, `/`, -1)
|
||||
}
|
||||
|
||||
// Put argument in canonical form, but preserve leading ./.
|
||||
if strings.HasPrefix(a, "./") {
|
||||
a = "./" + path.Clean(a)
|
||||
if a == "./." {
|
||||
a = "."
|
||||
}
|
||||
} else {
|
||||
a = path.Clean(a)
|
||||
}
|
||||
if a == "all" || a == "std" {
|
||||
out = append(out, allPackages(a)...)
|
||||
continue
|
||||
}
|
||||
out = append(out, a)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// importPaths returns the import paths to use for the given command line.
|
||||
func importPaths(args []string) []string {
|
||||
args = importPathsNoDotExpansion(args)
|
||||
var out []string
|
||||
for _, a := range args {
|
||||
if strings.Contains(a, "...") {
|
||||
if build.IsLocalImport(a) {
|
||||
out = append(out, allPackagesInFS(a)...)
|
||||
} else {
|
||||
out = append(out, allPackages(a)...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
out = append(out, a)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// matchPattern(pattern)(name) reports whether
|
||||
// name matches pattern. Pattern is a limited glob
|
||||
// pattern in which '...' means 'any string' and there
|
||||
// is no other special syntax.
|
||||
func matchPattern(pattern string) func(name string) bool {
|
||||
re := regexp.QuoteMeta(pattern)
|
||||
re = strings.Replace(re, `\.\.\.`, `.*`, -1)
|
||||
// Special case: foo/... matches foo too.
|
||||
if strings.HasSuffix(re, `/.*`) {
|
||||
re = re[:len(re)-len(`/.*`)] + `(/.*)?`
|
||||
}
|
||||
reg := regexp.MustCompile(`^` + re + `$`)
|
||||
return func(name string) bool {
|
||||
return reg.MatchString(name)
|
||||
}
|
||||
}
|
||||
|
||||
// hasPathPrefix reports whether the path s begins with the
|
||||
// elements in prefix.
|
||||
func hasPathPrefix(s, prefix string) bool {
|
||||
switch {
|
||||
default:
|
||||
return false
|
||||
case len(s) == len(prefix):
|
||||
return s == prefix
|
||||
case len(s) > len(prefix):
|
||||
if prefix != "" && prefix[len(prefix)-1] == '/' {
|
||||
return strings.HasPrefix(s, prefix)
|
||||
}
|
||||
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
|
||||
}
|
||||
}
|
||||
|
||||
// treeCanMatchPattern(pattern)(name) reports whether
|
||||
// name or children of name can possibly match pattern.
|
||||
// Pattern is the same limited glob accepted by matchPattern.
|
||||
func treeCanMatchPattern(pattern string) func(name string) bool {
|
||||
wildCard := false
|
||||
if i := strings.Index(pattern, "..."); i >= 0 {
|
||||
wildCard = true
|
||||
pattern = pattern[:i]
|
||||
}
|
||||
return func(name string) bool {
|
||||
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
|
||||
wildCard && strings.HasPrefix(name, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// allPackages returns all the packages that can be found
|
||||
// under the $GOPATH directories and $GOROOT matching pattern.
|
||||
// The pattern is either "all" (all packages), "std" (standard packages)
|
||||
// or a path including "...".
|
||||
func allPackages(pattern string) []string {
|
||||
pkgs := matchPackages(pattern)
|
||||
if len(pkgs) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func matchPackages(pattern string) []string {
|
||||
match := func(string) bool { return true }
|
||||
treeCanMatch := func(string) bool { return true }
|
||||
if pattern != "all" && pattern != "std" {
|
||||
match = matchPattern(pattern)
|
||||
treeCanMatch = treeCanMatchPattern(pattern)
|
||||
}
|
||||
|
||||
have := map[string]bool{
|
||||
"builtin": true, // ignore pseudo-package that exists only for documentation
|
||||
}
|
||||
if !buildContext.CgoEnabled {
|
||||
have["runtime/cgo"] = true // ignore during walk
|
||||
}
|
||||
var pkgs []string
|
||||
|
||||
// Commands
|
||||
cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
|
||||
filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil || !fi.IsDir() || path == cmd {
|
||||
return nil
|
||||
}
|
||||
name := path[len(cmd):]
|
||||
if !treeCanMatch(name) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
// Commands are all in cmd/, not in subdirectories.
|
||||
if strings.Contains(name, string(filepath.Separator)) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
|
||||
name = "cmd/" + name
|
||||
if have[name] {
|
||||
return nil
|
||||
}
|
||||
have[name] = true
|
||||
if !match(name) {
|
||||
return nil
|
||||
}
|
||||
_, err = buildContext.ImportDir(path, 0)
|
||||
if err != nil {
|
||||
if _, noGo := err.(*build.NoGoError); !noGo {
|
||||
log.Print(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
pkgs = append(pkgs, name)
|
||||
return nil
|
||||
})
|
||||
|
||||
for _, src := range buildContext.SrcDirs() {
|
||||
if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
|
||||
continue
|
||||
}
|
||||
src = filepath.Clean(src) + string(filepath.Separator)
|
||||
root := src
|
||||
if pattern == "cmd" {
|
||||
root += "cmd" + string(filepath.Separator)
|
||||
}
|
||||
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil || !fi.IsDir() || path == src {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Avoid .foo, _foo, and testdata directory trees.
|
||||
_, elem := filepath.Split(path)
|
||||
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
name := filepath.ToSlash(path[len(src):])
|
||||
if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") {
|
||||
// The name "std" is only the standard library.
|
||||
// If the name is cmd, it's the root of the command tree.
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !treeCanMatch(name) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if have[name] {
|
||||
return nil
|
||||
}
|
||||
have[name] = true
|
||||
if !match(name) {
|
||||
return nil
|
||||
}
|
||||
_, err = buildContext.ImportDir(path, 0)
|
||||
if err != nil {
|
||||
if _, noGo := err.(*build.NoGoError); noGo {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
pkgs = append(pkgs, name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// allPackagesInFS is like allPackages but is passed a pattern
|
||||
// beginning ./ or ../, meaning it should scan the tree rooted
|
||||
// at the given directory. There are ... in the pattern too.
|
||||
func allPackagesInFS(pattern string) []string {
|
||||
pkgs := matchPackagesInFS(pattern)
|
||||
if len(pkgs) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func matchPackagesInFS(pattern string) []string {
|
||||
// Find directory to begin the scan.
|
||||
// Could be smarter but this one optimization
|
||||
// is enough for now, since ... is usually at the
|
||||
// end of a path.
|
||||
i := strings.Index(pattern, "...")
|
||||
dir, _ := path.Split(pattern[:i])
|
||||
|
||||
// pattern begins with ./ or ../.
|
||||
// path.Clean will discard the ./ but not the ../.
|
||||
// We need to preserve the ./ for pattern matching
|
||||
// and in the returned import paths.
|
||||
prefix := ""
|
||||
if strings.HasPrefix(pattern, "./") {
|
||||
prefix = "./"
|
||||
}
|
||||
match := matchPattern(pattern)
|
||||
|
||||
var pkgs []string
|
||||
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil || !fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if path == dir {
|
||||
// filepath.Walk starts at dir and recurses. For the recursive case,
|
||||
// the path is the result of filepath.Join, which calls filepath.Clean.
|
||||
// The initial case is not Cleaned, though, so we do this explicitly.
|
||||
//
|
||||
// This converts a path like "./io/" to "io". Without this step, running
|
||||
// "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io
|
||||
// package, because prepending the prefix "./" to the unclean path would
|
||||
// result in "././io", and match("././io") returns false.
|
||||
path = filepath.Clean(path)
|
||||
}
|
||||
|
||||
// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
|
||||
_, elem := filepath.Split(path)
|
||||
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
|
||||
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
name := prefix + filepath.ToSlash(path)
|
||||
if !match(name) {
|
||||
return nil
|
||||
}
|
||||
if _, err = build.ImportDir(path, 0); err != nil {
|
||||
if _, noGo := err.(*build.NoGoError); !noGo {
|
||||
log.Print(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
pkgs = append(pkgs, name)
|
||||
return nil
|
||||
})
|
||||
return pkgs
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) 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 or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// +build go1.12
|
||||
|
||||
// Require use of the correct import path only for Go 1.12+ users, so
|
||||
// any breakages coincide with people updating their CI configs or
|
||||
// whatnot.
|
||||
|
||||
package main // import "golang.org/x/lint/golint"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
|||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
|
@ -0,0 +1,3 @@
|
|||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
|
@ -0,0 +1,627 @@
|
|||
// 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.
|
||||
|
||||
package astutil
|
||||
|
||||
// This file defines utilities for working with source positions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// PathEnclosingInterval returns the node that encloses the source
|
||||
// interval [start, end), and all its ancestors up to the AST root.
|
||||
//
|
||||
// The definition of "enclosing" used by this function considers
|
||||
// additional whitespace abutting a node to be enclosed by it.
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <-A->
|
||||
// <----B----->
|
||||
//
|
||||
// the ast.BinaryExpr(+) node is considered to enclose interval B
|
||||
// even though its [Pos()..End()) is actually only interval A.
|
||||
// This behaviour makes user interfaces more tolerant of imperfect
|
||||
// input.
|
||||
//
|
||||
// This function treats tokens as nodes, though they are not included
|
||||
// in the result. e.g. PathEnclosingInterval("+") returns the
|
||||
// enclosing ast.BinaryExpr("x + y").
|
||||
//
|
||||
// If start==end, the 1-char interval following start is used instead.
|
||||
//
|
||||
// The 'exact' result is true if the interval contains only path[0]
|
||||
// and perhaps some adjacent whitespace. It is false if the interval
|
||||
// overlaps multiple children of path[0], or if it contains only
|
||||
// interior whitespace of path[0].
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <--C--> <---E-->
|
||||
// ^
|
||||
// D
|
||||
//
|
||||
// intervals C, D and E are inexact. C is contained by the
|
||||
// z-assignment statement, because it spans three of its children (:=,
|
||||
// x, +). So too is the 1-char interval D, because it contains only
|
||||
// interior whitespace of the assignment. E is considered interior
|
||||
// whitespace of the BlockStmt containing the assignment.
|
||||
//
|
||||
// Precondition: [start, end) both lie within the same file as root.
|
||||
// TODO(adonovan): return (nil, false) in this case and remove precond.
|
||||
// Requires FileSet; see loader.tokenFileContainsPos.
|
||||
//
|
||||
// Postcondition: path is never nil; it always contains at least 'root'.
|
||||
//
|
||||
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
|
||||
// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
|
||||
|
||||
// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
|
||||
var visit func(node ast.Node) bool
|
||||
visit = func(node ast.Node) bool {
|
||||
path = append(path, node)
|
||||
|
||||
nodePos := node.Pos()
|
||||
nodeEnd := node.End()
|
||||
|
||||
// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
|
||||
|
||||
// Intersect [start, end) with interval of node.
|
||||
if start < nodePos {
|
||||
start = nodePos
|
||||
}
|
||||
if end > nodeEnd {
|
||||
end = nodeEnd
|
||||
}
|
||||
|
||||
// Find sole child that contains [start, end).
|
||||
children := childrenOf(node)
|
||||
l := len(children)
|
||||
for i, child := range children {
|
||||
// [childPos, childEnd) is unaugmented interval of child.
|
||||
childPos := child.Pos()
|
||||
childEnd := child.End()
|
||||
|
||||
// [augPos, augEnd) is whitespace-augmented interval of child.
|
||||
augPos := childPos
|
||||
augEnd := childEnd
|
||||
if i > 0 {
|
||||
augPos = children[i-1].End() // start of preceding whitespace
|
||||
}
|
||||
if i < l-1 {
|
||||
nextChildPos := children[i+1].Pos()
|
||||
// Does [start, end) lie between child and next child?
|
||||
if start >= augEnd && end <= nextChildPos {
|
||||
return false // inexact match
|
||||
}
|
||||
augEnd = nextChildPos // end of following whitespace
|
||||
}
|
||||
|
||||
// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
|
||||
// i, augPos, augEnd, start, end) // debugging
|
||||
|
||||
// Does augmented child strictly contain [start, end)?
|
||||
if augPos <= start && end <= augEnd {
|
||||
_, isToken := child.(tokenNode)
|
||||
return isToken || visit(child)
|
||||
}
|
||||
|
||||
// Does [start, end) overlap multiple children?
|
||||
// i.e. left-augmented child contains start
|
||||
// but LR-augmented child does not contain end.
|
||||
if start < childEnd && end > augEnd {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// No single child contained [start, end),
|
||||
// so node is the result. Is it exact?
|
||||
|
||||
// (It's tempting to put this condition before the
|
||||
// child loop, but it gives the wrong result in the
|
||||
// case where a node (e.g. ExprStmt) and its sole
|
||||
// child have equal intervals.)
|
||||
if start == nodePos && end == nodeEnd {
|
||||
return true // exact match
|
||||
}
|
||||
|
||||
return false // inexact: overlaps multiple children
|
||||
}
|
||||
|
||||
if start > end {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
if start < root.End() && end > root.Pos() {
|
||||
if start == end {
|
||||
end = start + 1 // empty interval => interval of size 1
|
||||
}
|
||||
exact = visit(root)
|
||||
|
||||
// Reverse the path:
|
||||
for i, l := 0, len(path); i < l/2; i++ {
|
||||
path[i], path[l-1-i] = path[l-1-i], path[i]
|
||||
}
|
||||
} else {
|
||||
// Selection lies within whitespace preceding the
|
||||
// first (or following the last) declaration in the file.
|
||||
// The result nonetheless always includes the ast.File.
|
||||
path = append(path, root)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// tokenNode is a dummy implementation of ast.Node for a single token.
|
||||
// They are used transiently by PathEnclosingInterval but never escape
|
||||
// this package.
|
||||
//
|
||||
type tokenNode struct {
|
||||
pos token.Pos
|
||||
end token.Pos
|
||||
}
|
||||
|
||||
func (n tokenNode) Pos() token.Pos {
|
||||
return n.pos
|
||||
}
|
||||
|
||||
func (n tokenNode) End() token.Pos {
|
||||
return n.end
|
||||
}
|
||||
|
||||
func tok(pos token.Pos, len int) ast.Node {
|
||||
return tokenNode{pos, pos + token.Pos(len)}
|
||||
}
|
||||
|
||||
// childrenOf returns the direct non-nil children of ast.Node n.
|
||||
// It may include fake ast.Node implementations for bare tokens.
|
||||
// it is not safe to call (e.g.) ast.Walk on such nodes.
|
||||
//
|
||||
func childrenOf(n ast.Node) []ast.Node {
|
||||
var children []ast.Node
|
||||
|
||||
// First add nodes for all true subtrees.
|
||||
ast.Inspect(n, func(node ast.Node) bool {
|
||||
if node == n { // push n
|
||||
return true // recur
|
||||
}
|
||||
if node != nil { // push child
|
||||
children = append(children, node)
|
||||
}
|
||||
return false // no recursion
|
||||
})
|
||||
|
||||
// Then add fake Nodes for bare tokens.
|
||||
switch n := n.(type) {
|
||||
case *ast.ArrayType:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Elt.End(), len("]")))
|
||||
|
||||
case *ast.AssignStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.BasicLit:
|
||||
children = append(children,
|
||||
tok(n.ValuePos, len(n.Value)))
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
children = append(children, tok(n.OpPos, len(n.Op.String())))
|
||||
|
||||
case *ast.BlockStmt:
|
||||
children = append(children,
|
||||
tok(n.Lbrace, len("{")),
|
||||
tok(n.Rbrace, len("}")))
|
||||
|
||||
case *ast.BranchStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.CallExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
if n.Ellipsis != 0 {
|
||||
children = append(children, tok(n.Ellipsis, len("...")))
|
||||
}
|
||||
|
||||
case *ast.CaseClause:
|
||||
if n.List == nil {
|
||||
children = append(children,
|
||||
tok(n.Case, len("default")))
|
||||
} else {
|
||||
children = append(children,
|
||||
tok(n.Case, len("case")))
|
||||
}
|
||||
children = append(children, tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.ChanType:
|
||||
switch n.Dir {
|
||||
case ast.RECV:
|
||||
children = append(children, tok(n.Begin, len("<-chan")))
|
||||
case ast.SEND:
|
||||
children = append(children, tok(n.Begin, len("chan<-")))
|
||||
case ast.RECV | ast.SEND:
|
||||
children = append(children, tok(n.Begin, len("chan")))
|
||||
}
|
||||
|
||||
case *ast.CommClause:
|
||||
if n.Comm == nil {
|
||||
children = append(children,
|
||||
tok(n.Case, len("default")))
|
||||
} else {
|
||||
children = append(children,
|
||||
tok(n.Case, len("case")))
|
||||
}
|
||||
children = append(children, tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.Comment:
|
||||
// nop
|
||||
|
||||
case *ast.CommentGroup:
|
||||
// nop
|
||||
|
||||
case *ast.CompositeLit:
|
||||
children = append(children,
|
||||
tok(n.Lbrace, len("{")),
|
||||
tok(n.Rbrace, len("{")))
|
||||
|
||||
case *ast.DeclStmt:
|
||||
// nop
|
||||
|
||||
case *ast.DeferStmt:
|
||||
children = append(children,
|
||||
tok(n.Defer, len("defer")))
|
||||
|
||||
case *ast.Ellipsis:
|
||||
children = append(children,
|
||||
tok(n.Ellipsis, len("...")))
|
||||
|
||||
case *ast.EmptyStmt:
|
||||
// nop
|
||||
|
||||
case *ast.ExprStmt:
|
||||
// nop
|
||||
|
||||
case *ast.Field:
|
||||
// TODO(adonovan): Field.{Doc,Comment,Tag}?
|
||||
|
||||
case *ast.FieldList:
|
||||
children = append(children,
|
||||
tok(n.Opening, len("(")),
|
||||
tok(n.Closing, len(")")))
|
||||
|
||||
case *ast.File:
|
||||
// TODO test: Doc
|
||||
children = append(children,
|
||||
tok(n.Package, len("package")))
|
||||
|
||||
case *ast.ForStmt:
|
||||
children = append(children,
|
||||
tok(n.For, len("for")))
|
||||
|
||||
case *ast.FuncDecl:
|
||||
// TODO(adonovan): FuncDecl.Comment?
|
||||
|
||||
// Uniquely, FuncDecl breaks the invariant that
|
||||
// preorder traversal yields tokens in lexical order:
|
||||
// in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
|
||||
//
|
||||
// As a workaround, we inline the case for FuncType
|
||||
// here and order things correctly.
|
||||
//
|
||||
children = nil // discard ast.Walk(FuncDecl) info subtrees
|
||||
children = append(children, tok(n.Type.Func, len("func")))
|
||||
if n.Recv != nil {
|
||||
children = append(children, n.Recv)
|
||||
}
|
||||
children = append(children, n.Name)
|
||||
if n.Type.Params != nil {
|
||||
children = append(children, n.Type.Params)
|
||||
}
|
||||
if n.Type.Results != nil {
|
||||
children = append(children, n.Type.Results)
|
||||
}
|
||||
if n.Body != nil {
|
||||
children = append(children, n.Body)
|
||||
}
|
||||
|
||||
case *ast.FuncLit:
|
||||
// nop
|
||||
|
||||
case *ast.FuncType:
|
||||
if n.Func != 0 {
|
||||
children = append(children,
|
||||
tok(n.Func, len("func")))
|
||||
}
|
||||
|
||||
case *ast.GenDecl:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
if n.Lparen != 0 {
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
}
|
||||
|
||||
case *ast.GoStmt:
|
||||
children = append(children,
|
||||
tok(n.Go, len("go")))
|
||||
|
||||
case *ast.Ident:
|
||||
children = append(children,
|
||||
tok(n.NamePos, len(n.Name)))
|
||||
|
||||
case *ast.IfStmt:
|
||||
children = append(children,
|
||||
tok(n.If, len("if")))
|
||||
|
||||
case *ast.ImportSpec:
|
||||
// TODO(adonovan): ImportSpec.{Doc,EndPos}?
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.IndexExpr:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("{")),
|
||||
tok(n.Rbrack, len("}")))
|
||||
|
||||
case *ast.InterfaceType:
|
||||
children = append(children,
|
||||
tok(n.Interface, len("interface")))
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
children = append(children,
|
||||
tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
children = append(children,
|
||||
tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.MapType:
|
||||
children = append(children,
|
||||
tok(n.Map, len("map")))
|
||||
|
||||
case *ast.ParenExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
|
||||
case *ast.RangeStmt:
|
||||
children = append(children,
|
||||
tok(n.For, len("for")),
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
children = append(children,
|
||||
tok(n.Return, len("return")))
|
||||
|
||||
case *ast.SelectStmt:
|
||||
children = append(children,
|
||||
tok(n.Select, len("select")))
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
// nop
|
||||
|
||||
case *ast.SendStmt:
|
||||
children = append(children,
|
||||
tok(n.Arrow, len("<-")))
|
||||
|
||||
case *ast.SliceExpr:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Rbrack, len("]")))
|
||||
|
||||
case *ast.StarExpr:
|
||||
children = append(children, tok(n.Star, len("*")))
|
||||
|
||||
case *ast.StructType:
|
||||
children = append(children, tok(n.Struct, len("struct")))
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
children = append(children, tok(n.Switch, len("switch")))
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen-1, len(".")),
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
|
||||
case *ast.TypeSpec:
|
||||
// TODO(adonovan): TypeSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
children = append(children, tok(n.Switch, len("switch")))
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
children = append(children, tok(n.OpPos, len(n.Op.String())))
|
||||
|
||||
case *ast.ValueSpec:
|
||||
// TODO(adonovan): ValueSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
|
||||
// nop
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: merge the logic of ast.Inspect() into
|
||||
// the switch above so we can make interleaved callbacks for
|
||||
// both Nodes and Tokens in the right order and avoid the need
|
||||
// to sort.
|
||||
sort.Sort(byPos(children))
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
type byPos []ast.Node
|
||||
|
||||
func (sl byPos) Len() int {
|
||||
return len(sl)
|
||||
}
|
||||
func (sl byPos) Less(i, j int) bool {
|
||||
return sl[i].Pos() < sl[j].Pos()
|
||||
}
|
||||
func (sl byPos) Swap(i, j int) {
|
||||
sl[i], sl[j] = sl[j], sl[i]
|
||||
}
|
||||
|
||||
// NodeDescription returns a description of the concrete type of n suitable
|
||||
// for a user interface.
|
||||
//
|
||||
// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
|
||||
// StarExpr) we could be much more specific given the path to the AST
|
||||
// root. Perhaps we should do that.
|
||||
//
|
||||
func NodeDescription(n ast.Node) string {
|
||||
switch n := n.(type) {
|
||||
case *ast.ArrayType:
|
||||
return "array type"
|
||||
case *ast.AssignStmt:
|
||||
return "assignment"
|
||||
case *ast.BadDecl:
|
||||
return "bad declaration"
|
||||
case *ast.BadExpr:
|
||||
return "bad expression"
|
||||
case *ast.BadStmt:
|
||||
return "bad statement"
|
||||
case *ast.BasicLit:
|
||||
return "basic literal"
|
||||
case *ast.BinaryExpr:
|
||||
return fmt.Sprintf("binary %s operation", n.Op)
|
||||
case *ast.BlockStmt:
|
||||
return "block"
|
||||
case *ast.BranchStmt:
|
||||
switch n.Tok {
|
||||
case token.BREAK:
|
||||
return "break statement"
|
||||
case token.CONTINUE:
|
||||
return "continue statement"
|
||||
case token.GOTO:
|
||||
return "goto statement"
|
||||
case token.FALLTHROUGH:
|
||||
return "fall-through statement"
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
if len(n.Args) == 1 && !n.Ellipsis.IsValid() {
|
||||
return "function call (or conversion)"
|
||||
}
|
||||
return "function call"
|
||||
case *ast.CaseClause:
|
||||
return "case clause"
|
||||
case *ast.ChanType:
|
||||
return "channel type"
|
||||
case *ast.CommClause:
|
||||
return "communication clause"
|
||||
case *ast.Comment:
|
||||
return "comment"
|
||||
case *ast.CommentGroup:
|
||||
return "comment group"
|
||||
case *ast.CompositeLit:
|
||||
return "composite literal"
|
||||
case *ast.DeclStmt:
|
||||
return NodeDescription(n.Decl) + " statement"
|
||||
case *ast.DeferStmt:
|
||||
return "defer statement"
|
||||
case *ast.Ellipsis:
|
||||
return "ellipsis"
|
||||
case *ast.EmptyStmt:
|
||||
return "empty statement"
|
||||
case *ast.ExprStmt:
|
||||
return "expression statement"
|
||||
case *ast.Field:
|
||||
// Can be any of these:
|
||||
// struct {x, y int} -- struct field(s)
|
||||
// struct {T} -- anon struct field
|
||||
// interface {I} -- interface embedding
|
||||
// interface {f()} -- interface method
|
||||
// func (A) func(B) C -- receiver, param(s), result(s)
|
||||
return "field/method/parameter"
|
||||
case *ast.FieldList:
|
||||
return "field/method/parameter list"
|
||||
case *ast.File:
|
||||
return "source file"
|
||||
case *ast.ForStmt:
|
||||
return "for loop"
|
||||
case *ast.FuncDecl:
|
||||
return "function declaration"
|
||||
case *ast.FuncLit:
|
||||
return "function literal"
|
||||
case *ast.FuncType:
|
||||
return "function type"
|
||||
case *ast.GenDecl:
|
||||
switch n.Tok {
|
||||
case token.IMPORT:
|
||||
return "import declaration"
|
||||
case token.CONST:
|
||||
return "constant declaration"
|
||||
case token.TYPE:
|
||||
return "type declaration"
|
||||
case token.VAR:
|
||||
return "variable declaration"
|
||||
}
|
||||
case *ast.GoStmt:
|
||||
return "go statement"
|
||||
case *ast.Ident:
|
||||
return "identifier"
|
||||
case *ast.IfStmt:
|
||||
return "if statement"
|
||||
case *ast.ImportSpec:
|
||||
return "import specification"
|
||||
case *ast.IncDecStmt:
|
||||
if n.Tok == token.INC {
|
||||
return "increment statement"
|
||||
}
|
||||
return "decrement statement"
|
||||
case *ast.IndexExpr:
|
||||
return "index expression"
|
||||
case *ast.InterfaceType:
|
||||
return "interface type"
|
||||
case *ast.KeyValueExpr:
|
||||
return "key/value association"
|
||||
case *ast.LabeledStmt:
|
||||
return "statement label"
|
||||
case *ast.MapType:
|
||||
return "map type"
|
||||
case *ast.Package:
|
||||
return "package"
|
||||
case *ast.ParenExpr:
|
||||
return "parenthesized " + NodeDescription(n.X)
|
||||
case *ast.RangeStmt:
|
||||
return "range loop"
|
||||
case *ast.ReturnStmt:
|
||||
return "return statement"
|
||||
case *ast.SelectStmt:
|
||||
return "select statement"
|
||||
case *ast.SelectorExpr:
|
||||
return "selector"
|
||||
case *ast.SendStmt:
|
||||
return "channel send"
|
||||
case *ast.SliceExpr:
|
||||
return "slice expression"
|
||||
case *ast.StarExpr:
|
||||
return "*-operation" // load/store expr or pointer type
|
||||
case *ast.StructType:
|
||||
return "struct type"
|
||||
case *ast.SwitchStmt:
|
||||
return "switch statement"
|
||||
case *ast.TypeAssertExpr:
|
||||
return "type assertion"
|
||||
case *ast.TypeSpec:
|
||||
return "type specification"
|
||||
case *ast.TypeSwitchStmt:
|
||||
return "type switch"
|
||||
case *ast.UnaryExpr:
|
||||
return fmt.Sprintf("unary %s operation", n.Op)
|
||||
case *ast.ValueSpec:
|
||||
return "value specification"
|
||||
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected node type: %T", n))
|
||||
}
|
|
@ -0,0 +1,481 @@
|
|||
// 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.
|
||||
|
||||
// Package astutil contains common utilities for working with the Go AST.
|
||||
package astutil // import "golang.org/x/tools/go/ast/astutil"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AddImport adds the import path to the file f, if absent.
|
||||
func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) {
|
||||
return AddNamedImport(fset, f, "", path)
|
||||
}
|
||||
|
||||
// AddNamedImport adds the import with the given name and path to the file f, if absent.
|
||||
// If name is not empty, it is used to rename the import.
|
||||
//
|
||||
// For example, calling
|
||||
// AddNamedImport(fset, f, "pathpkg", "path")
|
||||
// adds
|
||||
// import pathpkg "path"
|
||||
func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) {
|
||||
if imports(f, name, path) {
|
||||
return false
|
||||
}
|
||||
|
||||
newImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: strconv.Quote(path),
|
||||
},
|
||||
}
|
||||
if name != "" {
|
||||
newImport.Name = &ast.Ident{Name: name}
|
||||
}
|
||||
|
||||
// Find an import decl to add to.
|
||||
// The goal is to find an existing import
|
||||
// whose import path has the longest shared
|
||||
// prefix with path.
|
||||
var (
|
||||
bestMatch = -1 // length of longest shared prefix
|
||||
lastImport = -1 // index in f.Decls of the file's final import decl
|
||||
impDecl *ast.GenDecl // import decl containing the best match
|
||||
impIndex = -1 // spec index in impDecl containing the best match
|
||||
|
||||
isThirdPartyPath = isThirdParty(path)
|
||||
)
|
||||
for i, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
lastImport = i
|
||||
// Do not add to import "C", to avoid disrupting the
|
||||
// association with its doc comment, breaking cgo.
|
||||
if declImports(gen, "C") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Match an empty import decl if that's all that is available.
|
||||
if len(gen.Specs) == 0 && bestMatch == -1 {
|
||||
impDecl = gen
|
||||
}
|
||||
|
||||
// Compute longest shared prefix with imports in this group and find best
|
||||
// matched import spec.
|
||||
// 1. Always prefer import spec with longest shared prefix.
|
||||
// 2. While match length is 0,
|
||||
// - for stdlib package: prefer first import spec.
|
||||
// - for third party package: prefer first third party import spec.
|
||||
// We cannot use last import spec as best match for third party package
|
||||
// because grouped imports are usually placed last by goimports -local
|
||||
// flag.
|
||||
// See issue #19190.
|
||||
seenAnyThirdParty := false
|
||||
for j, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
p := importPath(impspec)
|
||||
n := matchLen(p, path)
|
||||
if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) {
|
||||
bestMatch = n
|
||||
impDecl = gen
|
||||
impIndex = j
|
||||
}
|
||||
seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no import decl found, add one after the last import.
|
||||
if impDecl == nil {
|
||||
impDecl = &ast.GenDecl{
|
||||
Tok: token.IMPORT,
|
||||
}
|
||||
if lastImport >= 0 {
|
||||
impDecl.TokPos = f.Decls[lastImport].End()
|
||||
} else {
|
||||
// There are no existing imports.
|
||||
// Our new import, preceded by a blank line, goes after the package declaration
|
||||
// and after the comment, if any, that starts on the same line as the
|
||||
// package declaration.
|
||||
impDecl.TokPos = f.Package
|
||||
|
||||
file := fset.File(f.Package)
|
||||
pkgLine := file.Line(f.Package)
|
||||
for _, c := range f.Comments {
|
||||
if file.Line(c.Pos()) > pkgLine {
|
||||
break
|
||||
}
|
||||
// +2 for a blank line
|
||||
impDecl.TokPos = c.End() + 2
|
||||
}
|
||||
}
|
||||
f.Decls = append(f.Decls, nil)
|
||||
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
|
||||
f.Decls[lastImport+1] = impDecl
|
||||
}
|
||||
|
||||
// Insert new import at insertAt.
|
||||
insertAt := 0
|
||||
if impIndex >= 0 {
|
||||
// insert after the found import
|
||||
insertAt = impIndex + 1
|
||||
}
|
||||
impDecl.Specs = append(impDecl.Specs, nil)
|
||||
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
|
||||
impDecl.Specs[insertAt] = newImport
|
||||
pos := impDecl.Pos()
|
||||
if insertAt > 0 {
|
||||
// If there is a comment after an existing import, preserve the comment
|
||||
// position by adding the new import after the comment.
|
||||
if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil {
|
||||
pos = spec.Comment.End()
|
||||
} else {
|
||||
// Assign same position as the previous import,
|
||||
// so that the sorter sees it as being in the same block.
|
||||
pos = impDecl.Specs[insertAt-1].Pos()
|
||||
}
|
||||
}
|
||||
if newImport.Name != nil {
|
||||
newImport.Name.NamePos = pos
|
||||
}
|
||||
newImport.Path.ValuePos = pos
|
||||
newImport.EndPos = pos
|
||||
|
||||
// Clean up parens. impDecl contains at least one spec.
|
||||
if len(impDecl.Specs) == 1 {
|
||||
// Remove unneeded parens.
|
||||
impDecl.Lparen = token.NoPos
|
||||
} else if !impDecl.Lparen.IsValid() {
|
||||
// impDecl needs parens added.
|
||||
impDecl.Lparen = impDecl.Specs[0].Pos()
|
||||
}
|
||||
|
||||
f.Imports = append(f.Imports, newImport)
|
||||
|
||||
if len(f.Decls) <= 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Merge all the import declarations into the first one.
|
||||
var first *ast.GenDecl
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
|
||||
continue
|
||||
}
|
||||
if first == nil {
|
||||
first = gen
|
||||
continue // Don't touch the first one.
|
||||
}
|
||||
// We now know there is more than one package in this import
|
||||
// declaration. Ensure that it ends up parenthesized.
|
||||
first.Lparen = first.Pos()
|
||||
// Move the imports of the other import declaration to the first one.
|
||||
for _, spec := range gen.Specs {
|
||||
spec.(*ast.ImportSpec).Path.ValuePos = first.Pos()
|
||||
first.Specs = append(first.Specs, spec)
|
||||
}
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isThirdParty(importPath string) bool {
|
||||
// Third party package import path usually contains "." (".com", ".org", ...)
|
||||
// This logic is taken from golang.org/x/tools/imports package.
|
||||
return strings.Contains(importPath, ".")
|
||||
}
|
||||
|
||||
// DeleteImport deletes the import path from the file f, if present.
|
||||
// If there are duplicate import declarations, all matching ones are deleted.
|
||||
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) {
|
||||
return DeleteNamedImport(fset, f, "", path)
|
||||
}
|
||||
|
||||
// DeleteNamedImport deletes the import with the given name and path from the file f, if present.
|
||||
// If there are duplicate import declarations, all matching ones are deleted.
|
||||
func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) {
|
||||
var delspecs []*ast.ImportSpec
|
||||
var delcomments []*ast.CommentGroup
|
||||
|
||||
// Find the import nodes that import path, if any.
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.IMPORT {
|
||||
continue
|
||||
}
|
||||
for j := 0; j < len(gen.Specs); j++ {
|
||||
spec := gen.Specs[j]
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
if importName(impspec) != name || importPath(impspec) != path {
|
||||
continue
|
||||
}
|
||||
|
||||
// We found an import spec that imports path.
|
||||
// Delete it.
|
||||
delspecs = append(delspecs, impspec)
|
||||
deleted = true
|
||||
copy(gen.Specs[j:], gen.Specs[j+1:])
|
||||
gen.Specs = gen.Specs[:len(gen.Specs)-1]
|
||||
|
||||
// If this was the last import spec in this decl,
|
||||
// delete the decl, too.
|
||||
if len(gen.Specs) == 0 {
|
||||
copy(f.Decls[i:], f.Decls[i+1:])
|
||||
f.Decls = f.Decls[:len(f.Decls)-1]
|
||||
i--
|
||||
break
|
||||
} else if len(gen.Specs) == 1 {
|
||||
if impspec.Doc != nil {
|
||||
delcomments = append(delcomments, impspec.Doc)
|
||||
}
|
||||
if impspec.Comment != nil {
|
||||
delcomments = append(delcomments, impspec.Comment)
|
||||
}
|
||||
for _, cg := range f.Comments {
|
||||
// Found comment on the same line as the import spec.
|
||||
if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line {
|
||||
delcomments = append(delcomments, cg)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
spec := gen.Specs[0].(*ast.ImportSpec)
|
||||
|
||||
// Move the documentation right after the import decl.
|
||||
if spec.Doc != nil {
|
||||
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line {
|
||||
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
|
||||
}
|
||||
}
|
||||
for _, cg := range f.Comments {
|
||||
if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line {
|
||||
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line {
|
||||
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if j > 0 {
|
||||
lastImpspec := gen.Specs[j-1].(*ast.ImportSpec)
|
||||
lastLine := fset.Position(lastImpspec.Path.ValuePos).Line
|
||||
line := fset.Position(impspec.Path.ValuePos).Line
|
||||
|
||||
// We deleted an entry but now there may be
|
||||
// a blank line-sized hole where the import was.
|
||||
if line-lastLine > 1 {
|
||||
// There was a blank line immediately preceding the deleted import,
|
||||
// so there's no need to close the hole.
|
||||
// Do nothing.
|
||||
} else if line != fset.File(gen.Rparen).LineCount() {
|
||||
// There was no blank line. Close the hole.
|
||||
fset.File(gen.Rparen).MergeLine(line)
|
||||
}
|
||||
}
|
||||
j--
|
||||
}
|
||||
}
|
||||
|
||||
// Delete imports from f.Imports.
|
||||
for i := 0; i < len(f.Imports); i++ {
|
||||
imp := f.Imports[i]
|
||||
for j, del := range delspecs {
|
||||
if imp == del {
|
||||
copy(f.Imports[i:], f.Imports[i+1:])
|
||||
f.Imports = f.Imports[:len(f.Imports)-1]
|
||||
copy(delspecs[j:], delspecs[j+1:])
|
||||
delspecs = delspecs[:len(delspecs)-1]
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete comments from f.Comments.
|
||||
for i := 0; i < len(f.Comments); i++ {
|
||||
cg := f.Comments[i]
|
||||
for j, del := range delcomments {
|
||||
if cg == del {
|
||||
copy(f.Comments[i:], f.Comments[i+1:])
|
||||
f.Comments = f.Comments[:len(f.Comments)-1]
|
||||
copy(delcomments[j:], delcomments[j+1:])
|
||||
delcomments = delcomments[:len(delcomments)-1]
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(delspecs) > 0 {
|
||||
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RewriteImport rewrites any import of path oldPath to path newPath.
|
||||
func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) {
|
||||
for _, imp := range f.Imports {
|
||||
if importPath(imp) == oldPath {
|
||||
rewrote = true
|
||||
// record old End, because the default is to compute
|
||||
// it using the length of imp.Path.Value.
|
||||
imp.EndPos = imp.End()
|
||||
imp.Path.Value = strconv.Quote(newPath)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UsesImport reports whether a given import is used.
|
||||
func UsesImport(f *ast.File, path string) (used bool) {
|
||||
spec := importSpec(f, path)
|
||||
if spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := spec.Name.String()
|
||||
switch name {
|
||||
case "<nil>":
|
||||
// If the package name is not explicitly specified,
|
||||
// make an educated guess. This is not guaranteed to be correct.
|
||||
lastSlash := strings.LastIndex(path, "/")
|
||||
if lastSlash == -1 {
|
||||
name = path
|
||||
} else {
|
||||
name = path[lastSlash+1:]
|
||||
}
|
||||
case "_", ".":
|
||||
// Not sure if this import is used - err on the side of caution.
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Walk(visitFn(func(n ast.Node) {
|
||||
sel, ok := n.(*ast.SelectorExpr)
|
||||
if ok && isTopName(sel.X, name) {
|
||||
used = true
|
||||
}
|
||||
}), f)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node)
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
fn(node)
|
||||
return fn
|
||||
}
|
||||
|
||||
// imports reports whether f has an import with the specified name and path.
|
||||
func imports(f *ast.File, name, path string) bool {
|
||||
for _, s := range f.Imports {
|
||||
if importName(s) == name && importPath(s) == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// importSpec returns the import spec if f imports path,
|
||||
// or nil otherwise.
|
||||
func importSpec(f *ast.File, path string) *ast.ImportSpec {
|
||||
for _, s := range f.Imports {
|
||||
if importPath(s) == path {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// importName returns the name of s,
|
||||
// or "" if the import is not named.
|
||||
func importName(s *ast.ImportSpec) string {
|
||||
if s.Name == nil {
|
||||
return ""
|
||||
}
|
||||
return s.Name.Name
|
||||
}
|
||||
|
||||
// importPath returns the unquoted import path of s,
|
||||
// or "" if the path is not properly quoted.
|
||||
func importPath(s *ast.ImportSpec) string {
|
||||
t, err := strconv.Unquote(s.Path.Value)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// declImports reports whether gen contains an import of path.
|
||||
func declImports(gen *ast.GenDecl, path string) bool {
|
||||
if gen.Tok != token.IMPORT {
|
||||
return false
|
||||
}
|
||||
for _, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
if importPath(impspec) == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchLen returns the length of the longest path segment prefix shared by x and y.
|
||||
func matchLen(x, y string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
|
||||
if x[i] == '/' {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// isTopName returns true if n is a top-level unresolved identifier with the given name.
|
||||
func isTopName(n ast.Expr, name string) bool {
|
||||
id, ok := n.(*ast.Ident)
|
||||
return ok && id.Name == name && id.Obj == nil
|
||||
}
|
||||
|
||||
// Imports returns the file imports grouped by paragraph.
|
||||
func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
|
||||
var groups [][]*ast.ImportSpec
|
||||
|
||||
for _, decl := range f.Decls {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok || genDecl.Tok != token.IMPORT {
|
||||
break
|
||||
}
|
||||
|
||||
group := []*ast.ImportSpec{}
|
||||
|
||||
var lastLine int
|
||||
for _, spec := range genDecl.Specs {
|
||||
importSpec := spec.(*ast.ImportSpec)
|
||||
pos := importSpec.Path.ValuePos
|
||||
line := fset.Position(pos).Line
|
||||
if lastLine > 0 && pos > 0 && line-lastLine > 1 {
|
||||
groups = append(groups, group)
|
||||
group = []*ast.ImportSpec{}
|
||||
}
|
||||
group = append(group, importSpec)
|
||||
lastLine = line
|
||||
}
|
||||
groups = append(groups, group)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
// 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 astutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// An ApplyFunc is invoked by Apply for each node n, even if n is nil,
|
||||
// before and/or after the node's children, using a Cursor describing
|
||||
// the current node and providing operations on it.
|
||||
//
|
||||
// The return value of ApplyFunc controls the syntax tree traversal.
|
||||
// See Apply for details.
|
||||
type ApplyFunc func(*Cursor) bool
|
||||
|
||||
// Apply traverses a syntax tree recursively, starting with root,
|
||||
// and calling pre and post for each node as described below.
|
||||
// Apply returns the syntax tree, possibly modified.
|
||||
//
|
||||
// If pre is not nil, it is called for each node before the node's
|
||||
// children are traversed (pre-order). If pre returns false, no
|
||||
// children are traversed, and post is not called for that node.
|
||||
//
|
||||
// If post is not nil, and a prior call of pre didn't return false,
|
||||
// post is called for each node after its children are traversed
|
||||
// (post-order). If post returns false, traversal is terminated and
|
||||
// Apply returns immediately.
|
||||
//
|
||||
// Only fields that refer to AST nodes are considered children;
|
||||
// i.e., token.Pos, Scopes, Objects, and fields of basic types
|
||||
// (strings, etc.) are ignored.
|
||||
//
|
||||
// Children are traversed in the order in which they appear in the
|
||||
// respective node's struct definition. A package's files are
|
||||
// traversed in the filenames' alphabetical order.
|
||||
//
|
||||
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
|
||||
parent := &struct{ ast.Node }{root}
|
||||
defer func() {
|
||||
if r := recover(); r != nil && r != abort {
|
||||
panic(r)
|
||||
}
|
||||
result = parent.Node
|
||||
}()
|
||||
a := &application{pre: pre, post: post}
|
||||
a.apply(parent, "Node", nil, root)
|
||||
return
|
||||
}
|
||||
|
||||
var abort = new(int) // singleton, to signal termination of Apply
|
||||
|
||||
// A Cursor describes a node encountered during Apply.
|
||||
// Information about the node and its parent is available
|
||||
// from the Node, Parent, Name, and Index methods.
|
||||
//
|
||||
// If p is a variable of type and value of the current parent node
|
||||
// c.Parent(), and f is the field identifier with name c.Name(),
|
||||
// the following invariants hold:
|
||||
//
|
||||
// p.f == c.Node() if c.Index() < 0
|
||||
// p.f[c.Index()] == c.Node() if c.Index() >= 0
|
||||
//
|
||||
// The methods Replace, Delete, InsertBefore, and InsertAfter
|
||||
// can be used to change the AST without disrupting Apply.
|
||||
type Cursor struct {
|
||||
parent ast.Node
|
||||
name string
|
||||
iter *iterator // valid if non-nil
|
||||
node ast.Node
|
||||
}
|
||||
|
||||
// Node returns the current Node.
|
||||
func (c *Cursor) Node() ast.Node { return c.node }
|
||||
|
||||
// Parent returns the parent of the current Node.
|
||||
func (c *Cursor) Parent() ast.Node { return c.parent }
|
||||
|
||||
// Name returns the name of the parent Node field that contains the current Node.
|
||||
// If the parent is a *ast.Package and the current Node is a *ast.File, Name returns
|
||||
// the filename for the current Node.
|
||||
func (c *Cursor) Name() string { return c.name }
|
||||
|
||||
// Index reports the index >= 0 of the current Node in the slice of Nodes that
|
||||
// contains it, or a value < 0 if the current Node is not part of a slice.
|
||||
// The index of the current node changes if InsertBefore is called while
|
||||
// processing the current node.
|
||||
func (c *Cursor) Index() int {
|
||||
if c.iter != nil {
|
||||
return c.iter.index
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// field returns the current node's parent field value.
|
||||
func (c *Cursor) field() reflect.Value {
|
||||
return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name)
|
||||
}
|
||||
|
||||
// Replace replaces the current Node with n.
|
||||
// The replacement node is not walked by Apply.
|
||||
func (c *Cursor) Replace(n ast.Node) {
|
||||
if _, ok := c.node.(*ast.File); ok {
|
||||
file, ok := n.(*ast.File)
|
||||
if !ok {
|
||||
panic("attempt to replace *ast.File with non-*ast.File")
|
||||
}
|
||||
c.parent.(*ast.Package).Files[c.name] = file
|
||||
return
|
||||
}
|
||||
|
||||
v := c.field()
|
||||
if i := c.Index(); i >= 0 {
|
||||
v = v.Index(i)
|
||||
}
|
||||
v.Set(reflect.ValueOf(n))
|
||||
}
|
||||
|
||||
// Delete deletes the current Node from its containing slice.
|
||||
// If the current Node is not part of a slice, Delete panics.
|
||||
// As a special case, if the current node is a package file,
|
||||
// Delete removes it from the package's Files map.
|
||||
func (c *Cursor) Delete() {
|
||||
if _, ok := c.node.(*ast.File); ok {
|
||||
delete(c.parent.(*ast.Package).Files, c.name)
|
||||
return
|
||||
}
|
||||
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("Delete node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i, l), v.Slice(i+1, l))
|
||||
v.Index(l - 1).Set(reflect.Zero(v.Type().Elem()))
|
||||
v.SetLen(l - 1)
|
||||
c.iter.step--
|
||||
}
|
||||
|
||||
// InsertAfter inserts n after the current Node in its containing slice.
|
||||
// If the current Node is not part of a slice, InsertAfter panics.
|
||||
// Apply does not walk n.
|
||||
func (c *Cursor) InsertAfter(n ast.Node) {
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("InsertAfter node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l))
|
||||
v.Index(i + 1).Set(reflect.ValueOf(n))
|
||||
c.iter.step++
|
||||
}
|
||||
|
||||
// InsertBefore inserts n before the current Node in its containing slice.
|
||||
// If the current Node is not part of a slice, InsertBefore panics.
|
||||
// Apply will not walk n.
|
||||
func (c *Cursor) InsertBefore(n ast.Node) {
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("InsertBefore node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i+1, l), v.Slice(i, l))
|
||||
v.Index(i).Set(reflect.ValueOf(n))
|
||||
c.iter.index++
|
||||
}
|
||||
|
||||
// application carries all the shared data so we can pass it around cheaply.
|
||||
type application struct {
|
||||
pre, post ApplyFunc
|
||||
cursor Cursor
|
||||
iter iterator
|
||||
}
|
||||
|
||||
func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast.Node) {
|
||||
// convert typed nil into untyped nil
|
||||
if v := reflect.ValueOf(n); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
n = nil
|
||||
}
|
||||
|
||||
// avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead
|
||||
saved := a.cursor
|
||||
a.cursor.parent = parent
|
||||
a.cursor.name = name
|
||||
a.cursor.iter = iter
|
||||
a.cursor.node = n
|
||||
|
||||
if a.pre != nil && !a.pre(&a.cursor) {
|
||||
a.cursor = saved
|
||||
return
|
||||
}
|
||||
|
||||
// walk children
|
||||
// (the order of the cases matches the order of the corresponding node types in go/ast)
|
||||
switch n := n.(type) {
|
||||
case nil:
|
||||
// nothing to do
|
||||
|
||||
// Comments and fields
|
||||
case *ast.Comment:
|
||||
// nothing to do
|
||||
|
||||
case *ast.CommentGroup:
|
||||
if n != nil {
|
||||
a.applyList(n, "List")
|
||||
}
|
||||
|
||||
case *ast.Field:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Names")
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Tag", nil, n.Tag)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.FieldList:
|
||||
a.applyList(n, "List")
|
||||
|
||||
// Expressions
|
||||
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
|
||||
// nothing to do
|
||||
|
||||
case *ast.Ellipsis:
|
||||
a.apply(n, "Elt", nil, n.Elt)
|
||||
|
||||
case *ast.FuncLit:
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.CompositeLit:
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.applyList(n, "Elts")
|
||||
|
||||
case *ast.ParenExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Sel", nil, n.Sel)
|
||||
|
||||
case *ast.IndexExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Index", nil, n.Index)
|
||||
|
||||
case *ast.SliceExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Low", nil, n.Low)
|
||||
a.apply(n, "High", nil, n.High)
|
||||
a.apply(n, "Max", nil, n.Max)
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
|
||||
case *ast.CallExpr:
|
||||
a.apply(n, "Fun", nil, n.Fun)
|
||||
a.applyList(n, "Args")
|
||||
|
||||
case *ast.StarExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Y", nil, n.Y)
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
// Types
|
||||
case *ast.ArrayType:
|
||||
a.apply(n, "Len", nil, n.Len)
|
||||
a.apply(n, "Elt", nil, n.Elt)
|
||||
|
||||
case *ast.StructType:
|
||||
a.apply(n, "Fields", nil, n.Fields)
|
||||
|
||||
case *ast.FuncType:
|
||||
a.apply(n, "Params", nil, n.Params)
|
||||
a.apply(n, "Results", nil, n.Results)
|
||||
|
||||
case *ast.InterfaceType:
|
||||
a.apply(n, "Methods", nil, n.Methods)
|
||||
|
||||
case *ast.MapType:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
case *ast.ChanType:
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
// Statements
|
||||
case *ast.BadStmt:
|
||||
// nothing to do
|
||||
|
||||
case *ast.DeclStmt:
|
||||
a.apply(n, "Decl", nil, n.Decl)
|
||||
|
||||
case *ast.EmptyStmt:
|
||||
// nothing to do
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
a.apply(n, "Label", nil, n.Label)
|
||||
a.apply(n, "Stmt", nil, n.Stmt)
|
||||
|
||||
case *ast.ExprStmt:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.SendStmt:
|
||||
a.apply(n, "Chan", nil, n.Chan)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.AssignStmt:
|
||||
a.applyList(n, "Lhs")
|
||||
a.applyList(n, "Rhs")
|
||||
|
||||
case *ast.GoStmt:
|
||||
a.apply(n, "Call", nil, n.Call)
|
||||
|
||||
case *ast.DeferStmt:
|
||||
a.apply(n, "Call", nil, n.Call)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
a.applyList(n, "Results")
|
||||
|
||||
case *ast.BranchStmt:
|
||||
a.apply(n, "Label", nil, n.Label)
|
||||
|
||||
case *ast.BlockStmt:
|
||||
a.applyList(n, "List")
|
||||
|
||||
case *ast.IfStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Cond", nil, n.Cond)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
a.apply(n, "Else", nil, n.Else)
|
||||
|
||||
case *ast.CaseClause:
|
||||
a.applyList(n, "List")
|
||||
a.applyList(n, "Body")
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Tag", nil, n.Tag)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Assign", nil, n.Assign)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.CommClause:
|
||||
a.apply(n, "Comm", nil, n.Comm)
|
||||
a.applyList(n, "Body")
|
||||
|
||||
case *ast.SelectStmt:
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.ForStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Cond", nil, n.Cond)
|
||||
a.apply(n, "Post", nil, n.Post)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
// Declarations
|
||||
case *ast.ImportSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.apply(n, "Path", nil, n.Path)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.ValueSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Names")
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.applyList(n, "Values")
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.TypeSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.BadDecl:
|
||||
// nothing to do
|
||||
|
||||
case *ast.GenDecl:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Specs")
|
||||
|
||||
case *ast.FuncDecl:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Recv", nil, n.Recv)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
// Files and packages
|
||||
case *ast.File:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.applyList(n, "Decls")
|
||||
// Don't walk n.Comments; they have either been walked already if
|
||||
// they are Doc comments, or they can be easily walked explicitly.
|
||||
|
||||
case *ast.Package:
|
||||
// collect and sort names for reproducible behavior
|
||||
var names []string
|
||||
for name := range n.Files {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
a.apply(n, name, nil, n.Files[name])
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Apply: unexpected node type %T", n))
|
||||
}
|
||||
|
||||
if a.post != nil && !a.post(&a.cursor) {
|
||||
panic(abort)
|
||||
}
|
||||
|
||||
a.cursor = saved
|
||||
}
|
||||
|
||||
// An iterator controls iteration over a slice of nodes.
|
||||
type iterator struct {
|
||||
index, step int
|
||||
}
|
||||
|
||||
func (a *application) applyList(parent ast.Node, name string) {
|
||||
// avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead
|
||||
saved := a.iter
|
||||
a.iter.index = 0
|
||||
for {
|
||||
// must reload parent.name each time, since cursor modifications might change it
|
||||
v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name)
|
||||
if a.iter.index >= v.Len() {
|
||||
break
|
||||
}
|
||||
|
||||
// element x may be nil in a bad AST - be cautious
|
||||
var x ast.Node
|
||||
if e := v.Index(a.iter.index); e.IsValid() {
|
||||
x = e.Interface().(ast.Node)
|
||||
}
|
||||
|
||||
a.iter.step = 1
|
||||
a.apply(parent, name, &a.iter, x)
|
||||
a.iter.index += a.iter.step
|
||||
}
|
||||
a.iter = saved
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package astutil
|
||||
|
||||
import "go/ast"
|
||||
|
||||
// Unparen returns e with any enclosing parentheses stripped.
|
||||
func Unparen(e ast.Expr) ast.Expr {
|
||||
for {
|
||||
p, ok := e.(*ast.ParenExpr)
|
||||
if !ok {
|
||||
return e
|
||||
}
|
||||
e = p.X
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
// 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 gcexportdata provides functions for locating, reading, and
|
||||
// writing export data files containing type information produced by the
|
||||
// gc compiler. This package supports go1.7 export data format and all
|
||||
// later versions.
|
||||
//
|
||||
// Although it might seem convenient for this package to live alongside
|
||||
// go/types in the standard library, this would cause version skew
|
||||
// problems for developer tools that use it, since they must be able to
|
||||
// consume the outputs of the gc compiler both before and after a Go
|
||||
// update such as from Go 1.7 to Go 1.8. Because this package lives in
|
||||
// golang.org/x/tools, sites can update their version of this repo some
|
||||
// time before the Go 1.8 release and rebuild and redeploy their
|
||||
// developer tools, which will then be able to consume both Go 1.7 and
|
||||
// Go 1.8 export data files, so they will work before and after the
|
||||
// Go update. (See discussion at https://golang.org/issue/15651.)
|
||||
//
|
||||
package gcexportdata // import "golang.org/x/tools/go/gcexportdata"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"golang.org/x/tools/go/internal/gcimporter"
|
||||
)
|
||||
|
||||
// Find returns the name of an object (.o) or archive (.a) file
|
||||
// containing type information for the specified import path,
|
||||
// using the workspace layout conventions of go/build.
|
||||
// If no file was found, an empty filename is returned.
|
||||
//
|
||||
// A relative srcDir is interpreted relative to the current working directory.
|
||||
//
|
||||
// Find also returns the package's resolved (canonical) import path,
|
||||
// reflecting the effects of srcDir and vendoring on importPath.
|
||||
func Find(importPath, srcDir string) (filename, path string) {
|
||||
return gcimporter.FindPkg(importPath, srcDir)
|
||||
}
|
||||
|
||||
// NewReader returns a reader for the export data section of an object
|
||||
// (.o) or archive (.a) file read from r. The new reader may provide
|
||||
// additional trailing data beyond the end of the export data.
|
||||
func NewReader(r io.Reader) (io.Reader, error) {
|
||||
buf := bufio.NewReader(r)
|
||||
_, err := gcimporter.FindExportData(buf)
|
||||
// If we ever switch to a zip-like archive format with the ToC
|
||||
// at the end, we can return the correct portion of export data,
|
||||
// but for now we must return the entire rest of the file.
|
||||
return buf, err
|
||||
}
|
||||
|
||||
// Read reads export data from in, decodes it, and returns type
|
||||
// information for the package.
|
||||
// The package name is specified by path.
|
||||
// File position information is added to fset.
|
||||
//
|
||||
// Read may inspect and add to the imports map to ensure that references
|
||||
// within the export data to other packages are consistent. The caller
|
||||
// must ensure that imports[path] does not exist, or exists but is
|
||||
// incomplete (see types.Package.Complete), and Read inserts the
|
||||
// resulting package into this map entry.
|
||||
//
|
||||
// On return, the state of the reader is undefined.
|
||||
func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
|
||||
data, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading export data for %q: %v", path, err)
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(data, []byte("!<arch>")) {
|
||||
return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path)
|
||||
}
|
||||
|
||||
// The App Engine Go runtime v1.6 uses the old export data format.
|
||||
// TODO(adonovan): delete once v1.7 has been around for a while.
|
||||
if bytes.HasPrefix(data, []byte("package ")) {
|
||||
return gcimporter.ImportData(imports, path, path, bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// The indexed export format starts with an 'i'; the older
|
||||
// binary export format starts with a 'c', 'd', or 'v'
|
||||
// (from "version"). Select appropriate importer.
|
||||
if len(data) > 0 && data[0] == 'i' {
|
||||
_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
|
||||
return pkg, err
|
||||
}
|
||||
|
||||
_, pkg, err := gcimporter.BImportData(fset, imports, data, path)
|
||||
return pkg, err
|
||||
}
|
||||
|
||||
// Write writes encoded type information for the specified package to out.
|
||||
// The FileSet provides file position information for named objects.
|
||||
func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
|
||||
b, err := gcimporter.BExportData(fset, pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = out.Write(b)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// 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 gcexportdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
// NewImporter returns a new instance of the types.Importer interface
|
||||
// that reads type information from export data files written by gc.
|
||||
// The Importer also satisfies types.ImporterFrom.
|
||||
//
|
||||
// Export data files are located using "go build" workspace conventions
|
||||
// and the build.Default context.
|
||||
//
|
||||
// Use this importer instead of go/importer.For("gc", ...) to avoid the
|
||||
// version-skew problems described in the documentation of this package,
|
||||
// or to control the FileSet or access the imports map populated during
|
||||
// package loading.
|
||||
//
|
||||
func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom {
|
||||
return importer{fset, imports}
|
||||
}
|
||||
|
||||
type importer struct {
|
||||
fset *token.FileSet
|
||||
imports map[string]*types.Package
|
||||
}
|
||||
|
||||
func (imp importer) Import(importPath string) (*types.Package, error) {
|
||||
return imp.ImportFrom(importPath, "", 0)
|
||||
}
|
||||
|
||||
func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) {
|
||||
filename, path := Find(importPath, srcDir)
|
||||
if filename == "" {
|
||||
if importPath == "unsafe" {
|
||||
// Even for unsafe, call Find first in case
|
||||
// the package was vendored.
|
||||
return types.Unsafe, nil
|
||||
}
|
||||
return nil, fmt.Errorf("can't find import: %s", importPath)
|
||||
}
|
||||
|
||||
if pkg, ok := imp.imports[path]; ok && pkg.Complete() {
|
||||
return pkg, nil // cache hit
|
||||
}
|
||||
|
||||
// open file
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
if err != nil {
|
||||
// add file name to error
|
||||
err = fmt.Errorf("reading export data: %s: %v", filename, err)
|
||||
}
|
||||
}()
|
||||
|
||||
r, err := NewReader(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Read(r, imp.fset, imp.imports, path)
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// 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 ignore
|
||||
|
||||
// The gcexportdata command is a diagnostic tool that displays the
|
||||
// contents of gc export data files.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/go/gcexportdata"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
var packageFlag = flag.String("package", "", "alternative package to print")
|
||||
|
||||
func main() {
|
||||
log.SetPrefix("gcexportdata: ")
|
||||
log.SetFlags(0)
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, "usage: gcexportdata [-package path] file.a")
|
||||
}
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
filename := flag.Args()[0]
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
r, err := gcexportdata.NewReader(f)
|
||||
if err != nil {
|
||||
log.Fatalf("%s: %s", filename, err)
|
||||
}
|
||||
|
||||
// Decode the package.
|
||||
const primary = "<primary>"
|
||||
imports := make(map[string]*types.Package)
|
||||
fset := token.NewFileSet()
|
||||
pkg, err := gcexportdata.Read(r, fset, imports, primary)
|
||||
if err != nil {
|
||||
log.Fatalf("%s: %s", filename, err)
|
||||
}
|
||||
|
||||
// Optionally select an indirectly mentioned package.
|
||||
if *packageFlag != "" {
|
||||
pkg = imports[*packageFlag]
|
||||
if pkg == nil {
|
||||
fmt.Fprintf(os.Stderr, "export data file %s does not mention %s; has:\n",
|
||||
filename, *packageFlag)
|
||||
for p := range imports {
|
||||
if p != primary {
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n", p)
|
||||
}
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Print all package-level declarations, including non-exported ones.
|
||||
fmt.Printf("package %s\n", pkg.Name())
|
||||
for _, imp := range pkg.Imports() {
|
||||
fmt.Printf("import %q\n", imp.Path())
|
||||
}
|
||||
qual := func(p *types.Package) string {
|
||||
if pkg == p {
|
||||
return ""
|
||||
}
|
||||
return p.Name()
|
||||
}
|
||||
scope := pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
fmt.Printf("%s: %s\n",
|
||||
fset.Position(obj.Pos()),
|
||||
types.ObjectString(obj, qual))
|
||||
|
||||
// For types, print each method.
|
||||
if _, ok := obj.(*types.TypeName); ok {
|
||||
for _, method := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
|
||||
fmt.Printf("%s: %s\n",
|
||||
fset.Position(method.Obj().Pos()),
|
||||
types.SelectionString(method, qual))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,852 @@
|
|||
// 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.
|
||||
|
||||
// Binary package export.
|
||||
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go;
|
||||
// see that file for specification of the format.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// If debugFormat is set, each integer and string value is preceded by a marker
|
||||
// and position information in the encoding. This mechanism permits an importer
|
||||
// to recognize immediately when it is out of sync. The importer recognizes this
|
||||
// mode automatically (i.e., it can import export data produced with debugging
|
||||
// support even if debugFormat is not set at the time of import). This mode will
|
||||
// lead to massively larger export data (by a factor of 2 to 3) and should only
|
||||
// be enabled during development and debugging.
|
||||
//
|
||||
// NOTE: This flag is the first flag to enable if importing dies because of
|
||||
// (suspected) format errors, and whenever a change is made to the format.
|
||||
const debugFormat = false // default: false
|
||||
|
||||
// If trace is set, debugging output is printed to std out.
|
||||
const trace = false // default: false
|
||||
|
||||
// Current export format version. Increase with each format change.
|
||||
// Note: The latest binary (non-indexed) export format is at version 6.
|
||||
// This exporter is still at level 4, but it doesn't matter since
|
||||
// the binary importer can handle older versions just fine.
|
||||
// 6: package height (CL 105038) -- NOT IMPLEMENTED HERE
|
||||
// 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMEMTED HERE
|
||||
// 4: type name objects support type aliases, uses aliasTag
|
||||
// 3: Go1.8 encoding (same as version 2, aliasTag defined but never used)
|
||||
// 2: removed unused bool in ODCL export (compiler only)
|
||||
// 1: header format change (more regular), export package for _ struct fields
|
||||
// 0: Go1.7 encoding
|
||||
const exportVersion = 4
|
||||
|
||||
// trackAllTypes enables cycle tracking for all types, not just named
|
||||
// types. The existing compiler invariants assume that unnamed types
|
||||
// that are not completely set up are not used, or else there are spurious
|
||||
// errors.
|
||||
// If disabled, only named types are tracked, possibly leading to slightly
|
||||
// less efficient encoding in rare cases. It also prevents the export of
|
||||
// some corner-case type declarations (but those are not handled correctly
|
||||
// with with the textual export format either).
|
||||
// TODO(gri) enable and remove once issues caused by it are fixed
|
||||
const trackAllTypes = false
|
||||
|
||||
type exporter struct {
|
||||
fset *token.FileSet
|
||||
out bytes.Buffer
|
||||
|
||||
// object -> index maps, indexed in order of serialization
|
||||
strIndex map[string]int
|
||||
pkgIndex map[*types.Package]int
|
||||
typIndex map[types.Type]int
|
||||
|
||||
// position encoding
|
||||
posInfoFormat bool
|
||||
prevFile string
|
||||
prevLine int
|
||||
|
||||
// debugging support
|
||||
written int // bytes written
|
||||
indent int // for trace
|
||||
}
|
||||
|
||||
// internalError represents an error generated inside this package.
|
||||
type internalError string
|
||||
|
||||
func (e internalError) Error() string { return "gcimporter: " + string(e) }
|
||||
|
||||
func internalErrorf(format string, args ...interface{}) error {
|
||||
return internalError(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// BExportData returns binary export data for pkg.
|
||||
// If no file set is provided, position info will be missing.
|
||||
func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if ierr, ok := e.(internalError); ok {
|
||||
err = ierr
|
||||
return
|
||||
}
|
||||
// Not an internal error; panic again.
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
|
||||
p := exporter{
|
||||
fset: fset,
|
||||
strIndex: map[string]int{"": 0}, // empty string is mapped to 0
|
||||
pkgIndex: make(map[*types.Package]int),
|
||||
typIndex: make(map[types.Type]int),
|
||||
posInfoFormat: true, // TODO(gri) might become a flag, eventually
|
||||
}
|
||||
|
||||
// write version info
|
||||
// The version string must start with "version %d" where %d is the version
|
||||
// number. Additional debugging information may follow after a blank; that
|
||||
// text is ignored by the importer.
|
||||
p.rawStringln(fmt.Sprintf("version %d", exportVersion))
|
||||
var debug string
|
||||
if debugFormat {
|
||||
debug = "debug"
|
||||
}
|
||||
p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly
|
||||
p.bool(trackAllTypes)
|
||||
p.bool(p.posInfoFormat)
|
||||
|
||||
// --- generic export data ---
|
||||
|
||||
// populate type map with predeclared "known" types
|
||||
for index, typ := range predeclared() {
|
||||
p.typIndex[typ] = index
|
||||
}
|
||||
if len(p.typIndex) != len(predeclared()) {
|
||||
return nil, internalError("duplicate entries in type map?")
|
||||
}
|
||||
|
||||
// write package data
|
||||
p.pkg(pkg, true)
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
// write objects
|
||||
objcount := 0
|
||||
scope := pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
if !ast.IsExported(name) {
|
||||
continue
|
||||
}
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.obj(scope.Lookup(name))
|
||||
objcount++
|
||||
}
|
||||
|
||||
// indicate end of list
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.tag(endTag)
|
||||
|
||||
// for self-verification only (redundant)
|
||||
p.int(objcount)
|
||||
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
// --- end of export data ---
|
||||
|
||||
return p.out.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *exporter) pkg(pkg *types.Package, emptypath bool) {
|
||||
if pkg == nil {
|
||||
panic(internalError("unexpected nil pkg"))
|
||||
}
|
||||
|
||||
// if we saw the package before, write its index (>= 0)
|
||||
if i, ok := p.pkgIndex[pkg]; ok {
|
||||
p.index('P', i)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, remember the package, write the package tag (< 0) and package data
|
||||
if trace {
|
||||
p.tracef("P%d = { ", len(p.pkgIndex))
|
||||
defer p.tracef("} ")
|
||||
}
|
||||
p.pkgIndex[pkg] = len(p.pkgIndex)
|
||||
|
||||
p.tag(packageTag)
|
||||
p.string(pkg.Name())
|
||||
if emptypath {
|
||||
p.string("")
|
||||
} else {
|
||||
p.string(pkg.Path())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) obj(obj types.Object) {
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
p.tag(constTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
p.typ(obj.Type())
|
||||
p.value(obj.Val())
|
||||
|
||||
case *types.TypeName:
|
||||
if obj.IsAlias() {
|
||||
p.tag(aliasTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
} else {
|
||||
p.tag(typeTag)
|
||||
}
|
||||
p.typ(obj.Type())
|
||||
|
||||
case *types.Var:
|
||||
p.tag(varTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
p.typ(obj.Type())
|
||||
|
||||
case *types.Func:
|
||||
p.tag(funcTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
sig := obj.Type().(*types.Signature)
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected object %v (%T)", obj, obj))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) pos(obj types.Object) {
|
||||
if !p.posInfoFormat {
|
||||
return
|
||||
}
|
||||
|
||||
file, line := p.fileLine(obj)
|
||||
if file == p.prevFile {
|
||||
// common case: write line delta
|
||||
// delta == 0 means different file or no line change
|
||||
delta := line - p.prevLine
|
||||
p.int(delta)
|
||||
if delta == 0 {
|
||||
p.int(-1) // -1 means no file change
|
||||
}
|
||||
} else {
|
||||
// different file
|
||||
p.int(0)
|
||||
// Encode filename as length of common prefix with previous
|
||||
// filename, followed by (possibly empty) suffix. Filenames
|
||||
// frequently share path prefixes, so this can save a lot
|
||||
// of space and make export data size less dependent on file
|
||||
// path length. The suffix is unlikely to be empty because
|
||||
// file names tend to end in ".go".
|
||||
n := commonPrefixLen(p.prevFile, file)
|
||||
p.int(n) // n >= 0
|
||||
p.string(file[n:]) // write suffix only
|
||||
p.prevFile = file
|
||||
p.int(line)
|
||||
}
|
||||
p.prevLine = line
|
||||
}
|
||||
|
||||
func (p *exporter) fileLine(obj types.Object) (file string, line int) {
|
||||
if p.fset != nil {
|
||||
pos := p.fset.Position(obj.Pos())
|
||||
file = pos.Filename
|
||||
line = pos.Line
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func commonPrefixLen(a, b string) int {
|
||||
if len(a) > len(b) {
|
||||
a, b = b, a
|
||||
}
|
||||
// len(a) <= len(b)
|
||||
i := 0
|
||||
for i < len(a) && a[i] == b[i] {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (p *exporter) qualifiedName(obj types.Object) {
|
||||
p.string(obj.Name())
|
||||
p.pkg(obj.Pkg(), false)
|
||||
}
|
||||
|
||||
func (p *exporter) typ(t types.Type) {
|
||||
if t == nil {
|
||||
panic(internalError("nil type"))
|
||||
}
|
||||
|
||||
// Possible optimization: Anonymous pointer types *T where
|
||||
// T is a named type are common. We could canonicalize all
|
||||
// such types *T to a single type PT = *T. This would lead
|
||||
// to at most one *T entry in typIndex, and all future *T's
|
||||
// would be encoded as the respective index directly. Would
|
||||
// save 1 byte (pointerTag) per *T and reduce the typIndex
|
||||
// size (at the cost of a canonicalization map). We can do
|
||||
// this later, without encoding format change.
|
||||
|
||||
// if we saw the type before, write its index (>= 0)
|
||||
if i, ok := p.typIndex[t]; ok {
|
||||
p.index('T', i)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, remember the type, write the type tag (< 0) and type data
|
||||
if trackAllTypes {
|
||||
if trace {
|
||||
p.tracef("T%d = {>\n", len(p.typIndex))
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
p.typIndex[t] = len(p.typIndex)
|
||||
}
|
||||
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
if !trackAllTypes {
|
||||
// if we don't track all types, track named types now
|
||||
p.typIndex[t] = len(p.typIndex)
|
||||
}
|
||||
|
||||
p.tag(namedTag)
|
||||
p.pos(t.Obj())
|
||||
p.qualifiedName(t.Obj())
|
||||
p.typ(t.Underlying())
|
||||
if !types.IsInterface(t) {
|
||||
p.assocMethods(t)
|
||||
}
|
||||
|
||||
case *types.Array:
|
||||
p.tag(arrayTag)
|
||||
p.int64(t.Len())
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Slice:
|
||||
p.tag(sliceTag)
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *dddSlice:
|
||||
p.tag(dddTag)
|
||||
p.typ(t.elem)
|
||||
|
||||
case *types.Struct:
|
||||
p.tag(structTag)
|
||||
p.fieldList(t)
|
||||
|
||||
case *types.Pointer:
|
||||
p.tag(pointerTag)
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Signature:
|
||||
p.tag(signatureTag)
|
||||
p.paramList(t.Params(), t.Variadic())
|
||||
p.paramList(t.Results(), false)
|
||||
|
||||
case *types.Interface:
|
||||
p.tag(interfaceTag)
|
||||
p.iface(t)
|
||||
|
||||
case *types.Map:
|
||||
p.tag(mapTag)
|
||||
p.typ(t.Key())
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Chan:
|
||||
p.tag(chanTag)
|
||||
p.int(int(3 - t.Dir())) // hack
|
||||
p.typ(t.Elem())
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected type %T: %s", t, t))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) assocMethods(named *types.Named) {
|
||||
// Sort methods (for determinism).
|
||||
var methods []*types.Func
|
||||
for i := 0; i < named.NumMethods(); i++ {
|
||||
methods = append(methods, named.Method(i))
|
||||
}
|
||||
sort.Sort(methodsByName(methods))
|
||||
|
||||
p.int(len(methods))
|
||||
|
||||
if trace && methods != nil {
|
||||
p.tracef("associated methods {>\n")
|
||||
}
|
||||
|
||||
for i, m := range methods {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
p.pos(m)
|
||||
name := m.Name()
|
||||
p.string(name)
|
||||
if !exported(name) {
|
||||
p.pkg(m.Pkg(), false)
|
||||
}
|
||||
|
||||
sig := m.Type().(*types.Signature)
|
||||
p.paramList(types.NewTuple(sig.Recv()), false)
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
p.int(0) // dummy value for go:nointerface pragma - ignored by importer
|
||||
}
|
||||
|
||||
if trace && methods != nil {
|
||||
p.tracef("<\n} ")
|
||||
}
|
||||
}
|
||||
|
||||
type methodsByName []*types.Func
|
||||
|
||||
func (x methodsByName) Len() int { return len(x) }
|
||||
func (x methodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() }
|
||||
|
||||
func (p *exporter) fieldList(t *types.Struct) {
|
||||
if trace && t.NumFields() > 0 {
|
||||
p.tracef("fields {>\n")
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
|
||||
p.int(t.NumFields())
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.field(t.Field(i))
|
||||
p.string(t.Tag(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) field(f *types.Var) {
|
||||
if !f.IsField() {
|
||||
panic(internalError("field expected"))
|
||||
}
|
||||
|
||||
p.pos(f)
|
||||
p.fieldName(f)
|
||||
p.typ(f.Type())
|
||||
}
|
||||
|
||||
func (p *exporter) iface(t *types.Interface) {
|
||||
// TODO(gri): enable importer to load embedded interfaces,
|
||||
// then emit Embeddeds and ExplicitMethods separately here.
|
||||
p.int(0)
|
||||
|
||||
n := t.NumMethods()
|
||||
if trace && n > 0 {
|
||||
p.tracef("methods {>\n")
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
p.int(n)
|
||||
for i := 0; i < n; i++ {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.method(t.Method(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) method(m *types.Func) {
|
||||
sig := m.Type().(*types.Signature)
|
||||
if sig.Recv() == nil {
|
||||
panic(internalError("method expected"))
|
||||
}
|
||||
|
||||
p.pos(m)
|
||||
p.string(m.Name())
|
||||
if m.Name() != "_" && !ast.IsExported(m.Name()) {
|
||||
p.pkg(m.Pkg(), false)
|
||||
}
|
||||
|
||||
// interface method; no need to encode receiver.
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
}
|
||||
|
||||
func (p *exporter) fieldName(f *types.Var) {
|
||||
name := f.Name()
|
||||
|
||||
if f.Anonymous() {
|
||||
// anonymous field - we distinguish between 3 cases:
|
||||
// 1) field name matches base type name and is exported
|
||||
// 2) field name matches base type name and is not exported
|
||||
// 3) field name doesn't match base type name (alias name)
|
||||
bname := basetypeName(f.Type())
|
||||
if name == bname {
|
||||
if ast.IsExported(name) {
|
||||
name = "" // 1) we don't need to know the field name or package
|
||||
} else {
|
||||
name = "?" // 2) use unexported name "?" to force package export
|
||||
}
|
||||
} else {
|
||||
// 3) indicate alias and export name as is
|
||||
// (this requires an extra "@" but this is a rare case)
|
||||
p.string("@")
|
||||
}
|
||||
}
|
||||
|
||||
p.string(name)
|
||||
if name != "" && !ast.IsExported(name) {
|
||||
p.pkg(f.Pkg(), false)
|
||||
}
|
||||
}
|
||||
|
||||
func basetypeName(typ types.Type) string {
|
||||
switch typ := deref(typ).(type) {
|
||||
case *types.Basic:
|
||||
return typ.Name()
|
||||
case *types.Named:
|
||||
return typ.Obj().Name()
|
||||
default:
|
||||
return "" // unnamed type
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) paramList(params *types.Tuple, variadic bool) {
|
||||
// use negative length to indicate unnamed parameters
|
||||
// (look at the first parameter only since either all
|
||||
// names are present or all are absent)
|
||||
n := params.Len()
|
||||
if n > 0 && params.At(0).Name() == "" {
|
||||
n = -n
|
||||
}
|
||||
p.int(n)
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
q := params.At(i)
|
||||
t := q.Type()
|
||||
if variadic && i == params.Len()-1 {
|
||||
t = &dddSlice{t.(*types.Slice).Elem()}
|
||||
}
|
||||
p.typ(t)
|
||||
if n > 0 {
|
||||
name := q.Name()
|
||||
p.string(name)
|
||||
if name != "_" {
|
||||
p.pkg(q.Pkg(), false)
|
||||
}
|
||||
}
|
||||
p.string("") // no compiler-specific info
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) value(x constant.Value) {
|
||||
if trace {
|
||||
p.tracef("= ")
|
||||
}
|
||||
|
||||
switch x.Kind() {
|
||||
case constant.Bool:
|
||||
tag := falseTag
|
||||
if constant.BoolVal(x) {
|
||||
tag = trueTag
|
||||
}
|
||||
p.tag(tag)
|
||||
|
||||
case constant.Int:
|
||||
if v, exact := constant.Int64Val(x); exact {
|
||||
// common case: x fits into an int64 - use compact encoding
|
||||
p.tag(int64Tag)
|
||||
p.int64(v)
|
||||
return
|
||||
}
|
||||
// uncommon case: large x - use float encoding
|
||||
// (powers of 2 will be encoded efficiently with exponent)
|
||||
p.tag(floatTag)
|
||||
p.float(constant.ToFloat(x))
|
||||
|
||||
case constant.Float:
|
||||
p.tag(floatTag)
|
||||
p.float(x)
|
||||
|
||||
case constant.Complex:
|
||||
p.tag(complexTag)
|
||||
p.float(constant.Real(x))
|
||||
p.float(constant.Imag(x))
|
||||
|
||||
case constant.String:
|
||||
p.tag(stringTag)
|
||||
p.string(constant.StringVal(x))
|
||||
|
||||
case constant.Unknown:
|
||||
// package contains type errors
|
||||
p.tag(unknownTag)
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected value %v (%T)", x, x))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) float(x constant.Value) {
|
||||
if x.Kind() != constant.Float {
|
||||
panic(internalErrorf("unexpected constant %v, want float", x))
|
||||
}
|
||||
// extract sign (there is no -0)
|
||||
sign := constant.Sign(x)
|
||||
if sign == 0 {
|
||||
// x == 0
|
||||
p.int(0)
|
||||
return
|
||||
}
|
||||
// x != 0
|
||||
|
||||
var f big.Float
|
||||
if v, exact := constant.Float64Val(x); exact {
|
||||
// float64
|
||||
f.SetFloat64(v)
|
||||
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
|
||||
// TODO(gri): add big.Rat accessor to constant.Value.
|
||||
r := valueToRat(num)
|
||||
f.SetRat(r.Quo(r, valueToRat(denom)))
|
||||
} else {
|
||||
// Value too large to represent as a fraction => inaccessible.
|
||||
// TODO(gri): add big.Float accessor to constant.Value.
|
||||
f.SetFloat64(math.MaxFloat64) // FIXME
|
||||
}
|
||||
|
||||
// extract exponent such that 0.5 <= m < 1.0
|
||||
var m big.Float
|
||||
exp := f.MantExp(&m)
|
||||
|
||||
// extract mantissa as *big.Int
|
||||
// - set exponent large enough so mant satisfies mant.IsInt()
|
||||
// - get *big.Int from mant
|
||||
m.SetMantExp(&m, int(m.MinPrec()))
|
||||
mant, acc := m.Int(nil)
|
||||
if acc != big.Exact {
|
||||
panic(internalError("internal error"))
|
||||
}
|
||||
|
||||
p.int(sign)
|
||||
p.int(exp)
|
||||
p.string(string(mant.Bytes()))
|
||||
}
|
||||
|
||||
func valueToRat(x constant.Value) *big.Rat {
|
||||
// Convert little-endian to big-endian.
|
||||
// I can't believe this is necessary.
|
||||
bytes := constant.Bytes(x)
|
||||
for i := 0; i < len(bytes)/2; i++ {
|
||||
bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
|
||||
}
|
||||
return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
|
||||
}
|
||||
|
||||
func (p *exporter) bool(b bool) bool {
|
||||
if trace {
|
||||
p.tracef("[")
|
||||
defer p.tracef("= %v] ", b)
|
||||
}
|
||||
|
||||
x := 0
|
||||
if b {
|
||||
x = 1
|
||||
}
|
||||
p.int(x)
|
||||
return b
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Low-level encoders
|
||||
|
||||
func (p *exporter) index(marker byte, index int) {
|
||||
if index < 0 {
|
||||
panic(internalError("invalid index < 0"))
|
||||
}
|
||||
if debugFormat {
|
||||
p.marker('t')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%c%d ", marker, index)
|
||||
}
|
||||
p.rawInt64(int64(index))
|
||||
}
|
||||
|
||||
func (p *exporter) tag(tag int) {
|
||||
if tag >= 0 {
|
||||
panic(internalError("invalid tag >= 0"))
|
||||
}
|
||||
if debugFormat {
|
||||
p.marker('t')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%s ", tagString[-tag])
|
||||
}
|
||||
p.rawInt64(int64(tag))
|
||||
}
|
||||
|
||||
func (p *exporter) int(x int) {
|
||||
p.int64(int64(x))
|
||||
}
|
||||
|
||||
func (p *exporter) int64(x int64) {
|
||||
if debugFormat {
|
||||
p.marker('i')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%d ", x)
|
||||
}
|
||||
p.rawInt64(x)
|
||||
}
|
||||
|
||||
func (p *exporter) string(s string) {
|
||||
if debugFormat {
|
||||
p.marker('s')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%q ", s)
|
||||
}
|
||||
// if we saw the string before, write its index (>= 0)
|
||||
// (the empty string is mapped to 0)
|
||||
if i, ok := p.strIndex[s]; ok {
|
||||
p.rawInt64(int64(i))
|
||||
return
|
||||
}
|
||||
// otherwise, remember string and write its negative length and bytes
|
||||
p.strIndex[s] = len(p.strIndex)
|
||||
p.rawInt64(-int64(len(s)))
|
||||
for i := 0; i < len(s); i++ {
|
||||
p.rawByte(s[i])
|
||||
}
|
||||
}
|
||||
|
||||
// marker emits a marker byte and position information which makes
|
||||
// it easy for a reader to detect if it is "out of sync". Used for
|
||||
// debugFormat format only.
|
||||
func (p *exporter) marker(m byte) {
|
||||
p.rawByte(m)
|
||||
// Enable this for help tracking down the location
|
||||
// of an incorrect marker when running in debugFormat.
|
||||
if false && trace {
|
||||
p.tracef("#%d ", p.written)
|
||||
}
|
||||
p.rawInt64(int64(p.written))
|
||||
}
|
||||
|
||||
// rawInt64 should only be used by low-level encoders.
|
||||
func (p *exporter) rawInt64(x int64) {
|
||||
var tmp [binary.MaxVarintLen64]byte
|
||||
n := binary.PutVarint(tmp[:], x)
|
||||
for i := 0; i < n; i++ {
|
||||
p.rawByte(tmp[i])
|
||||
}
|
||||
}
|
||||
|
||||
// rawStringln should only be used to emit the initial version string.
|
||||
func (p *exporter) rawStringln(s string) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
p.rawByte(s[i])
|
||||
}
|
||||
p.rawByte('\n')
|
||||
}
|
||||
|
||||
// rawByte is the bottleneck interface to write to p.out.
|
||||
// rawByte escapes b as follows (any encoding does that
|
||||
// hides '$'):
|
||||
//
|
||||
// '$' => '|' 'S'
|
||||
// '|' => '|' '|'
|
||||
//
|
||||
// Necessary so other tools can find the end of the
|
||||
// export data by searching for "$$".
|
||||
// rawByte should only be used by low-level encoders.
|
||||
func (p *exporter) rawByte(b byte) {
|
||||
switch b {
|
||||
case '$':
|
||||
// write '$' as '|' 'S'
|
||||
b = 'S'
|
||||
fallthrough
|
||||
case '|':
|
||||
// write '|' as '|' '|'
|
||||
p.out.WriteByte('|')
|
||||
p.written++
|
||||
}
|
||||
p.out.WriteByte(b)
|
||||
p.written++
|
||||
}
|
||||
|
||||
// tracef is like fmt.Printf but it rewrites the format string
|
||||
// to take care of indentation.
|
||||
func (p *exporter) tracef(format string, args ...interface{}) {
|
||||
if strings.ContainsAny(format, "<>\n") {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(format); i++ {
|
||||
// no need to deal with runes
|
||||
ch := format[i]
|
||||
switch ch {
|
||||
case '>':
|
||||
p.indent++
|
||||
continue
|
||||
case '<':
|
||||
p.indent--
|
||||
continue
|
||||
}
|
||||
buf.WriteByte(ch)
|
||||
if ch == '\n' {
|
||||
for j := p.indent; j > 0; j-- {
|
||||
buf.WriteString(". ")
|
||||
}
|
||||
}
|
||||
}
|
||||
format = buf.String()
|
||||
}
|
||||
fmt.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Debugging support.
|
||||
// (tagString is only used when tracing is enabled)
|
||||
var tagString = [...]string{
|
||||
// Packages
|
||||
-packageTag: "package",
|
||||
|
||||
// Types
|
||||
-namedTag: "named type",
|
||||
-arrayTag: "array",
|
||||
-sliceTag: "slice",
|
||||
-dddTag: "ddd",
|
||||
-structTag: "struct",
|
||||
-pointerTag: "pointer",
|
||||
-signatureTag: "signature",
|
||||
-interfaceTag: "interface",
|
||||
-mapTag: "map",
|
||||
-chanTag: "chan",
|
||||
|
||||
// Values
|
||||
-falseTag: "false",
|
||||
-trueTag: "true",
|
||||
-int64Tag: "int64",
|
||||
-floatTag: "float",
|
||||
-fractionTag: "fraction",
|
||||
-complexTag: "complex",
|
||||
-stringTag: "string",
|
||||
-unknownTag: "unknown",
|
||||
|
||||
// Type aliases
|
||||
-aliasTag: "alias",
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,93 @@
|
|||
// 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 file is a copy of $GOROOT/src/go/internal/gcimporter/exportdata.go.
|
||||
|
||||
// This file implements FindExportData.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func readGopackHeader(r *bufio.Reader) (name string, size int, err error) {
|
||||
// See $GOROOT/include/ar.h.
|
||||
hdr := make([]byte, 16+12+6+6+8+10+2)
|
||||
_, err = io.ReadFull(r, hdr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// leave for debugging
|
||||
if false {
|
||||
fmt.Printf("header: %s", hdr)
|
||||
}
|
||||
s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10]))
|
||||
size, err = strconv.Atoi(s)
|
||||
if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
|
||||
err = fmt.Errorf("invalid archive header")
|
||||
return
|
||||
}
|
||||
name = strings.TrimSpace(string(hdr[:16]))
|
||||
return
|
||||
}
|
||||
|
||||
// FindExportData positions the reader r at the beginning of the
|
||||
// export data section of an underlying GC-created object/archive
|
||||
// file by reading from it. The reader must be positioned at the
|
||||
// start of the file before calling this function. The hdr result
|
||||
// is the string before the export data, either "$$" or "$$B".
|
||||
//
|
||||
func FindExportData(r *bufio.Reader) (hdr string, err error) {
|
||||
// Read first line to make sure this is an object file.
|
||||
line, err := r.ReadSlice('\n')
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can't find export data (%v)", err)
|
||||
return
|
||||
}
|
||||
|
||||
if string(line) == "!<arch>\n" {
|
||||
// Archive file. Scan to __.PKGDEF.
|
||||
var name string
|
||||
if name, _, err = readGopackHeader(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// First entry should be __.PKGDEF.
|
||||
if name != "__.PKGDEF" {
|
||||
err = fmt.Errorf("go archive is missing __.PKGDEF")
|
||||
return
|
||||
}
|
||||
|
||||
// Read first line of __.PKGDEF data, so that line
|
||||
// is once again the first line of the input.
|
||||
if line, err = r.ReadSlice('\n'); err != nil {
|
||||
err = fmt.Errorf("can't find export data (%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Now at __.PKGDEF in archive or still at beginning of file.
|
||||
// Either way, line should begin with "go object ".
|
||||
if !strings.HasPrefix(string(line), "go object ") {
|
||||
err = fmt.Errorf("not a Go object file")
|
||||
return
|
||||
}
|
||||
|
||||
// Skip over object header to export data.
|
||||
// Begins after first line starting with $$.
|
||||
for line[0] != '$' {
|
||||
if line, err = r.ReadSlice('\n'); err != nil {
|
||||
err = fmt.Errorf("can't find export data (%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
hdr = string(line)
|
||||
|
||||
return
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,723 @@
|
|||
// 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.
|
||||
|
||||
// Indexed binary package export.
|
||||
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go;
|
||||
// see that file for specification of the format.
|
||||
|
||||
// +build go1.11
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Current indexed export format version. Increase with each format change.
|
||||
// 0: Go1.11 encoding
|
||||
const iexportVersion = 0
|
||||
|
||||
// IExportData returns the binary export data for pkg.
|
||||
// If no file set is provided, position info will be missing.
|
||||
func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if ierr, ok := e.(internalError); ok {
|
||||
err = ierr
|
||||
return
|
||||
}
|
||||
// Not an internal error; panic again.
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
|
||||
p := iexporter{
|
||||
out: bytes.NewBuffer(nil),
|
||||
fset: fset,
|
||||
allPkgs: map[*types.Package]bool{},
|
||||
stringIndex: map[string]uint64{},
|
||||
declIndex: map[types.Object]uint64{},
|
||||
typIndex: map[types.Type]uint64{},
|
||||
}
|
||||
|
||||
for i, pt := range predeclared() {
|
||||
p.typIndex[pt] = uint64(i)
|
||||
}
|
||||
if len(p.typIndex) > predeclReserved {
|
||||
panic(internalErrorf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved))
|
||||
}
|
||||
|
||||
// Initialize work queue with exported declarations.
|
||||
scope := pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
if ast.IsExported(name) {
|
||||
p.pushDecl(scope.Lookup(name))
|
||||
}
|
||||
}
|
||||
|
||||
// Loop until no more work.
|
||||
for !p.declTodo.empty() {
|
||||
p.doDecl(p.declTodo.popHead())
|
||||
}
|
||||
|
||||
// Append indices to data0 section.
|
||||
dataLen := uint64(p.data0.Len())
|
||||
w := p.newWriter()
|
||||
w.writeIndex(p.declIndex, pkg)
|
||||
w.flush()
|
||||
|
||||
// Assemble header.
|
||||
var hdr intWriter
|
||||
hdr.WriteByte('i')
|
||||
hdr.uint64(iexportVersion)
|
||||
hdr.uint64(uint64(p.strings.Len()))
|
||||
hdr.uint64(dataLen)
|
||||
|
||||
// Flush output.
|
||||
io.Copy(p.out, &hdr)
|
||||
io.Copy(p.out, &p.strings)
|
||||
io.Copy(p.out, &p.data0)
|
||||
|
||||
return p.out.Bytes(), nil
|
||||
}
|
||||
|
||||
// writeIndex writes out an object index. mainIndex indicates whether
|
||||
// we're writing out the main index, which is also read by
|
||||
// non-compiler tools and includes a complete package description
|
||||
// (i.e., name and height).
|
||||
func (w *exportWriter) writeIndex(index map[types.Object]uint64, localpkg *types.Package) {
|
||||
// Build a map from packages to objects from that package.
|
||||
pkgObjs := map[*types.Package][]types.Object{}
|
||||
|
||||
// For the main index, make sure to include every package that
|
||||
// we reference, even if we're not exporting (or reexporting)
|
||||
// any symbols from it.
|
||||
pkgObjs[localpkg] = nil
|
||||
for pkg := range w.p.allPkgs {
|
||||
pkgObjs[pkg] = nil
|
||||
}
|
||||
|
||||
for obj := range index {
|
||||
pkgObjs[obj.Pkg()] = append(pkgObjs[obj.Pkg()], obj)
|
||||
}
|
||||
|
||||
var pkgs []*types.Package
|
||||
for pkg, objs := range pkgObjs {
|
||||
pkgs = append(pkgs, pkg)
|
||||
|
||||
sort.Slice(objs, func(i, j int) bool {
|
||||
return objs[i].Name() < objs[j].Name()
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(pkgs, func(i, j int) bool {
|
||||
return pkgs[i].Path() < pkgs[j].Path()
|
||||
})
|
||||
|
||||
w.uint64(uint64(len(pkgs)))
|
||||
for _, pkg := range pkgs {
|
||||
w.string(pkg.Path())
|
||||
w.string(pkg.Name())
|
||||
w.uint64(uint64(0)) // package height is not needed for go/types
|
||||
|
||||
objs := pkgObjs[pkg]
|
||||
w.uint64(uint64(len(objs)))
|
||||
for _, obj := range objs {
|
||||
w.string(obj.Name())
|
||||
w.uint64(index[obj])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type iexporter struct {
|
||||
fset *token.FileSet
|
||||
out *bytes.Buffer
|
||||
|
||||
// allPkgs tracks all packages that have been referenced by
|
||||
// the export data, so we can ensure to include them in the
|
||||
// main index.
|
||||
allPkgs map[*types.Package]bool
|
||||
|
||||
declTodo objQueue
|
||||
|
||||
strings intWriter
|
||||
stringIndex map[string]uint64
|
||||
|
||||
data0 intWriter
|
||||
declIndex map[types.Object]uint64
|
||||
typIndex map[types.Type]uint64
|
||||
}
|
||||
|
||||
// stringOff returns the offset of s within the string section.
|
||||
// If not already present, it's added to the end.
|
||||
func (p *iexporter) stringOff(s string) uint64 {
|
||||
off, ok := p.stringIndex[s]
|
||||
if !ok {
|
||||
off = uint64(p.strings.Len())
|
||||
p.stringIndex[s] = off
|
||||
|
||||
p.strings.uint64(uint64(len(s)))
|
||||
p.strings.WriteString(s)
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
// pushDecl adds n to the declaration work queue, if not already present.
|
||||
func (p *iexporter) pushDecl(obj types.Object) {
|
||||
// Package unsafe is known to the compiler and predeclared.
|
||||
assert(obj.Pkg() != types.Unsafe)
|
||||
|
||||
if _, ok := p.declIndex[obj]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
p.declIndex[obj] = ^uint64(0) // mark n present in work queue
|
||||
p.declTodo.pushTail(obj)
|
||||
}
|
||||
|
||||
// exportWriter handles writing out individual data section chunks.
|
||||
type exportWriter struct {
|
||||
p *iexporter
|
||||
|
||||
data intWriter
|
||||
currPkg *types.Package
|
||||
prevFile string
|
||||
prevLine int64
|
||||
}
|
||||
|
||||
func (p *iexporter) doDecl(obj types.Object) {
|
||||
w := p.newWriter()
|
||||
w.setPkg(obj.Pkg(), false)
|
||||
|
||||
switch obj := obj.(type) {
|
||||
case *types.Var:
|
||||
w.tag('V')
|
||||
w.pos(obj.Pos())
|
||||
w.typ(obj.Type(), obj.Pkg())
|
||||
|
||||
case *types.Func:
|
||||
sig, _ := obj.Type().(*types.Signature)
|
||||
if sig.Recv() != nil {
|
||||
panic(internalErrorf("unexpected method: %v", sig))
|
||||
}
|
||||
w.tag('F')
|
||||
w.pos(obj.Pos())
|
||||
w.signature(sig)
|
||||
|
||||
case *types.Const:
|
||||
w.tag('C')
|
||||
w.pos(obj.Pos())
|
||||
w.value(obj.Type(), obj.Val())
|
||||
|
||||
case *types.TypeName:
|
||||
if obj.IsAlias() {
|
||||
w.tag('A')
|
||||
w.pos(obj.Pos())
|
||||
w.typ(obj.Type(), obj.Pkg())
|
||||
break
|
||||
}
|
||||
|
||||
// Defined type.
|
||||
w.tag('T')
|
||||
w.pos(obj.Pos())
|
||||
|
||||
underlying := obj.Type().Underlying()
|
||||
w.typ(underlying, obj.Pkg())
|
||||
|
||||
t := obj.Type()
|
||||
if types.IsInterface(t) {
|
||||
break
|
||||
}
|
||||
|
||||
named, ok := t.(*types.Named)
|
||||
if !ok {
|
||||
panic(internalErrorf("%s is not a defined type", t))
|
||||
}
|
||||
|
||||
n := named.NumMethods()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
m := named.Method(i)
|
||||
w.pos(m.Pos())
|
||||
w.string(m.Name())
|
||||
sig, _ := m.Type().(*types.Signature)
|
||||
w.param(sig.Recv())
|
||||
w.signature(sig)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected object: %v", obj))
|
||||
}
|
||||
|
||||
p.declIndex[obj] = w.flush()
|
||||
}
|
||||
|
||||
func (w *exportWriter) tag(tag byte) {
|
||||
w.data.WriteByte(tag)
|
||||
}
|
||||
|
||||
func (w *exportWriter) pos(pos token.Pos) {
|
||||
p := w.p.fset.Position(pos)
|
||||
file := p.Filename
|
||||
line := int64(p.Line)
|
||||
|
||||
// When file is the same as the last position (common case),
|
||||
// we can save a few bytes by delta encoding just the line
|
||||
// number.
|
||||
//
|
||||
// Note: Because data objects may be read out of order (or not
|
||||
// at all), we can only apply delta encoding within a single
|
||||
// object. This is handled implicitly by tracking prevFile and
|
||||
// prevLine as fields of exportWriter.
|
||||
|
||||
if file == w.prevFile {
|
||||
delta := line - w.prevLine
|
||||
w.int64(delta)
|
||||
if delta == deltaNewFile {
|
||||
w.int64(-1)
|
||||
}
|
||||
} else {
|
||||
w.int64(deltaNewFile)
|
||||
w.int64(line) // line >= 0
|
||||
w.string(file)
|
||||
w.prevFile = file
|
||||
}
|
||||
w.prevLine = line
|
||||
}
|
||||
|
||||
func (w *exportWriter) pkg(pkg *types.Package) {
|
||||
// Ensure any referenced packages are declared in the main index.
|
||||
w.p.allPkgs[pkg] = true
|
||||
|
||||
w.string(pkg.Path())
|
||||
}
|
||||
|
||||
func (w *exportWriter) qualifiedIdent(obj types.Object) {
|
||||
// Ensure any referenced declarations are written out too.
|
||||
w.p.pushDecl(obj)
|
||||
|
||||
w.string(obj.Name())
|
||||
w.pkg(obj.Pkg())
|
||||
}
|
||||
|
||||
func (w *exportWriter) typ(t types.Type, pkg *types.Package) {
|
||||
w.data.uint64(w.p.typOff(t, pkg))
|
||||
}
|
||||
|
||||
func (p *iexporter) newWriter() *exportWriter {
|
||||
return &exportWriter{p: p}
|
||||
}
|
||||
|
||||
func (w *exportWriter) flush() uint64 {
|
||||
off := uint64(w.p.data0.Len())
|
||||
io.Copy(&w.p.data0, &w.data)
|
||||
return off
|
||||
}
|
||||
|
||||
func (p *iexporter) typOff(t types.Type, pkg *types.Package) uint64 {
|
||||
off, ok := p.typIndex[t]
|
||||
if !ok {
|
||||
w := p.newWriter()
|
||||
w.doTyp(t, pkg)
|
||||
off = predeclReserved + w.flush()
|
||||
p.typIndex[t] = off
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
func (w *exportWriter) startType(k itag) {
|
||||
w.data.uint64(uint64(k))
|
||||
}
|
||||
|
||||
func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
w.startType(definedType)
|
||||
w.qualifiedIdent(t.Obj())
|
||||
|
||||
case *types.Pointer:
|
||||
w.startType(pointerType)
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Slice:
|
||||
w.startType(sliceType)
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Array:
|
||||
w.startType(arrayType)
|
||||
w.uint64(uint64(t.Len()))
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Chan:
|
||||
w.startType(chanType)
|
||||
// 1 RecvOnly; 2 SendOnly; 3 SendRecv
|
||||
var dir uint64
|
||||
switch t.Dir() {
|
||||
case types.RecvOnly:
|
||||
dir = 1
|
||||
case types.SendOnly:
|
||||
dir = 2
|
||||
case types.SendRecv:
|
||||
dir = 3
|
||||
}
|
||||
w.uint64(dir)
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Map:
|
||||
w.startType(mapType)
|
||||
w.typ(t.Key(), pkg)
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Signature:
|
||||
w.startType(signatureType)
|
||||
w.setPkg(pkg, true)
|
||||
w.signature(t)
|
||||
|
||||
case *types.Struct:
|
||||
w.startType(structType)
|
||||
w.setPkg(pkg, true)
|
||||
|
||||
n := t.NumFields()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Field(i)
|
||||
w.pos(f.Pos())
|
||||
w.string(f.Name())
|
||||
w.typ(f.Type(), pkg)
|
||||
w.bool(f.Embedded())
|
||||
w.string(t.Tag(i)) // note (or tag)
|
||||
}
|
||||
|
||||
case *types.Interface:
|
||||
w.startType(interfaceType)
|
||||
w.setPkg(pkg, true)
|
||||
|
||||
n := t.NumEmbeddeds()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Embedded(i)
|
||||
w.pos(f.Obj().Pos())
|
||||
w.typ(f.Obj().Type(), f.Obj().Pkg())
|
||||
}
|
||||
|
||||
n = t.NumExplicitMethods()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
m := t.ExplicitMethod(i)
|
||||
w.pos(m.Pos())
|
||||
w.string(m.Name())
|
||||
sig, _ := m.Type().(*types.Signature)
|
||||
w.signature(sig)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t)))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *exportWriter) setPkg(pkg *types.Package, write bool) {
|
||||
if write {
|
||||
w.pkg(pkg)
|
||||
}
|
||||
|
||||
w.currPkg = pkg
|
||||
}
|
||||
|
||||
func (w *exportWriter) signature(sig *types.Signature) {
|
||||
w.paramList(sig.Params())
|
||||
w.paramList(sig.Results())
|
||||
if sig.Params().Len() > 0 {
|
||||
w.bool(sig.Variadic())
|
||||
}
|
||||
}
|
||||
|
||||
func (w *exportWriter) paramList(tup *types.Tuple) {
|
||||
n := tup.Len()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
w.param(tup.At(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *exportWriter) param(obj types.Object) {
|
||||
w.pos(obj.Pos())
|
||||
w.localIdent(obj)
|
||||
w.typ(obj.Type(), obj.Pkg())
|
||||
}
|
||||
|
||||
func (w *exportWriter) value(typ types.Type, v constant.Value) {
|
||||
w.typ(typ, nil)
|
||||
|
||||
switch v.Kind() {
|
||||
case constant.Bool:
|
||||
w.bool(constant.BoolVal(v))
|
||||
case constant.Int:
|
||||
var i big.Int
|
||||
if i64, exact := constant.Int64Val(v); exact {
|
||||
i.SetInt64(i64)
|
||||
} else if ui64, exact := constant.Uint64Val(v); exact {
|
||||
i.SetUint64(ui64)
|
||||
} else {
|
||||
i.SetString(v.ExactString(), 10)
|
||||
}
|
||||
w.mpint(&i, typ)
|
||||
case constant.Float:
|
||||
f := constantToFloat(v)
|
||||
w.mpfloat(f, typ)
|
||||
case constant.Complex:
|
||||
w.mpfloat(constantToFloat(constant.Real(v)), typ)
|
||||
w.mpfloat(constantToFloat(constant.Imag(v)), typ)
|
||||
case constant.String:
|
||||
w.string(constant.StringVal(v))
|
||||
case constant.Unknown:
|
||||
// package contains type errors
|
||||
default:
|
||||
panic(internalErrorf("unexpected value %v (%T)", v, v))
|
||||
}
|
||||
}
|
||||
|
||||
// constantToFloat converts a constant.Value with kind constant.Float to a
|
||||
// big.Float.
|
||||
func constantToFloat(x constant.Value) *big.Float {
|
||||
assert(x.Kind() == constant.Float)
|
||||
// Use the same floating-point precision (512) as cmd/compile
|
||||
// (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
|
||||
const mpprec = 512
|
||||
var f big.Float
|
||||
f.SetPrec(mpprec)
|
||||
if v, exact := constant.Float64Val(x); exact {
|
||||
// float64
|
||||
f.SetFloat64(v)
|
||||
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
|
||||
// TODO(gri): add big.Rat accessor to constant.Value.
|
||||
n := valueToRat(num)
|
||||
d := valueToRat(denom)
|
||||
f.SetRat(n.Quo(n, d))
|
||||
} else {
|
||||
// Value too large to represent as a fraction => inaccessible.
|
||||
// TODO(gri): add big.Float accessor to constant.Value.
|
||||
_, ok := f.SetString(x.ExactString())
|
||||
assert(ok)
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
// mpint exports a multi-precision integer.
|
||||
//
|
||||
// For unsigned types, small values are written out as a single
|
||||
// byte. Larger values are written out as a length-prefixed big-endian
|
||||
// byte string, where the length prefix is encoded as its complement.
|
||||
// For example, bytes 0, 1, and 2 directly represent the integer
|
||||
// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-,
|
||||
// 2-, and 3-byte big-endian string follow.
|
||||
//
|
||||
// Encoding for signed types use the same general approach as for
|
||||
// unsigned types, except small values use zig-zag encoding and the
|
||||
// bottom bit of length prefix byte for large values is reserved as a
|
||||
// sign bit.
|
||||
//
|
||||
// The exact boundary between small and large encodings varies
|
||||
// according to the maximum number of bytes needed to encode a value
|
||||
// of type typ. As a special case, 8-bit types are always encoded as a
|
||||
// single byte.
|
||||
//
|
||||
// TODO(mdempsky): Is this level of complexity really worthwhile?
|
||||
func (w *exportWriter) mpint(x *big.Int, typ types.Type) {
|
||||
basic, ok := typ.Underlying().(*types.Basic)
|
||||
if !ok {
|
||||
panic(internalErrorf("unexpected type %v (%T)", typ.Underlying(), typ.Underlying()))
|
||||
}
|
||||
|
||||
signed, maxBytes := intSize(basic)
|
||||
|
||||
negative := x.Sign() < 0
|
||||
if !signed && negative {
|
||||
panic(internalErrorf("negative unsigned integer; type %v, value %v", typ, x))
|
||||
}
|
||||
|
||||
b := x.Bytes()
|
||||
if len(b) > 0 && b[0] == 0 {
|
||||
panic(internalErrorf("leading zeros"))
|
||||
}
|
||||
if uint(len(b)) > maxBytes {
|
||||
panic(internalErrorf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x))
|
||||
}
|
||||
|
||||
maxSmall := 256 - maxBytes
|
||||
if signed {
|
||||
maxSmall = 256 - 2*maxBytes
|
||||
}
|
||||
if maxBytes == 1 {
|
||||
maxSmall = 256
|
||||
}
|
||||
|
||||
// Check if x can use small value encoding.
|
||||
if len(b) <= 1 {
|
||||
var ux uint
|
||||
if len(b) == 1 {
|
||||
ux = uint(b[0])
|
||||
}
|
||||
if signed {
|
||||
ux <<= 1
|
||||
if negative {
|
||||
ux--
|
||||
}
|
||||
}
|
||||
if ux < maxSmall {
|
||||
w.data.WriteByte(byte(ux))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
n := 256 - uint(len(b))
|
||||
if signed {
|
||||
n = 256 - 2*uint(len(b))
|
||||
if negative {
|
||||
n |= 1
|
||||
}
|
||||
}
|
||||
if n < maxSmall || n >= 256 {
|
||||
panic(internalErrorf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n))
|
||||
}
|
||||
|
||||
w.data.WriteByte(byte(n))
|
||||
w.data.Write(b)
|
||||
}
|
||||
|
||||
// mpfloat exports a multi-precision floating point number.
|
||||
//
|
||||
// The number's value is decomposed into mantissa × 2**exponent, where
|
||||
// mantissa is an integer. The value is written out as mantissa (as a
|
||||
// multi-precision integer) and then the exponent, except exponent is
|
||||
// omitted if mantissa is zero.
|
||||
func (w *exportWriter) mpfloat(f *big.Float, typ types.Type) {
|
||||
if f.IsInf() {
|
||||
panic("infinite constant")
|
||||
}
|
||||
|
||||
// Break into f = mant × 2**exp, with 0.5 <= mant < 1.
|
||||
var mant big.Float
|
||||
exp := int64(f.MantExp(&mant))
|
||||
|
||||
// Scale so that mant is an integer.
|
||||
prec := mant.MinPrec()
|
||||
mant.SetMantExp(&mant, int(prec))
|
||||
exp -= int64(prec)
|
||||
|
||||
manti, acc := mant.Int(nil)
|
||||
if acc != big.Exact {
|
||||
panic(internalErrorf("mantissa scaling failed for %f (%s)", f, acc))
|
||||
}
|
||||
w.mpint(manti, typ)
|
||||
if manti.Sign() != 0 {
|
||||
w.int64(exp)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *exportWriter) bool(b bool) bool {
|
||||
var x uint64
|
||||
if b {
|
||||
x = 1
|
||||
}
|
||||
w.uint64(x)
|
||||
return b
|
||||
}
|
||||
|
||||
func (w *exportWriter) int64(x int64) { w.data.int64(x) }
|
||||
func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) }
|
||||
func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) }
|
||||
|
||||
func (w *exportWriter) localIdent(obj types.Object) {
|
||||
// Anonymous parameters.
|
||||
if obj == nil {
|
||||
w.string("")
|
||||
return
|
||||
}
|
||||
|
||||
name := obj.Name()
|
||||
if name == "_" {
|
||||
w.string("_")
|
||||
return
|
||||
}
|
||||
|
||||
w.string(name)
|
||||
}
|
||||
|
||||
type intWriter struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *intWriter) int64(x int64) {
|
||||
var buf [binary.MaxVarintLen64]byte
|
||||
n := binary.PutVarint(buf[:], x)
|
||||
w.Write(buf[:n])
|
||||
}
|
||||
|
||||
func (w *intWriter) uint64(x uint64) {
|
||||
var buf [binary.MaxVarintLen64]byte
|
||||
n := binary.PutUvarint(buf[:], x)
|
||||
w.Write(buf[:n])
|
||||
}
|
||||
|
||||
func assert(cond bool) {
|
||||
if !cond {
|
||||
panic("internal error: assertion failed")
|
||||
}
|
||||
}
|
||||
|
||||
// The below is copied from go/src/cmd/compile/internal/gc/syntax.go.
|
||||
|
||||
// objQueue is a FIFO queue of types.Object. The zero value of objQueue is
|
||||
// a ready-to-use empty queue.
|
||||
type objQueue struct {
|
||||
ring []types.Object
|
||||
head, tail int
|
||||
}
|
||||
|
||||
// empty returns true if q contains no Nodes.
|
||||
func (q *objQueue) empty() bool {
|
||||
return q.head == q.tail
|
||||
}
|
||||
|
||||
// pushTail appends n to the tail of the queue.
|
||||
func (q *objQueue) pushTail(obj types.Object) {
|
||||
if len(q.ring) == 0 {
|
||||
q.ring = make([]types.Object, 16)
|
||||
} else if q.head+len(q.ring) == q.tail {
|
||||
// Grow the ring.
|
||||
nring := make([]types.Object, len(q.ring)*2)
|
||||
// Copy the old elements.
|
||||
part := q.ring[q.head%len(q.ring):]
|
||||
if q.tail-q.head <= len(part) {
|
||||
part = part[:q.tail-q.head]
|
||||
copy(nring, part)
|
||||
} else {
|
||||
pos := copy(nring, part)
|
||||
copy(nring[pos:], q.ring[:q.tail%len(q.ring)])
|
||||
}
|
||||
q.ring, q.head, q.tail = nring, 0, q.tail-q.head
|
||||
}
|
||||
|
||||
q.ring[q.tail%len(q.ring)] = obj
|
||||
q.tail++
|
||||
}
|
||||
|
||||
// popHead pops a node from the head of the queue. It panics if q is empty.
|
||||
func (q *objQueue) popHead() types.Object {
|
||||
if q.empty() {
|
||||
panic("dequeue empty")
|
||||
}
|
||||
obj := q.ring[q.head%len(q.ring)]
|
||||
q.head++
|
||||
return obj
|
||||
}
|
|
@ -0,0 +1,606 @@
|
|||
// 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.
|
||||
|
||||
// Indexed package import.
|
||||
// See cmd/compile/internal/gc/iexport.go for the export data format.
|
||||
|
||||
// This file is a copy of $GOROOT/src/go/internal/gcimporter/iimport.go.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type intReader struct {
|
||||
*bytes.Reader
|
||||
path string
|
||||
}
|
||||
|
||||
func (r *intReader) int64() int64 {
|
||||
i, err := binary.ReadVarint(r.Reader)
|
||||
if err != nil {
|
||||
errorf("import %q: read varint error: %v", r.path, err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (r *intReader) uint64() uint64 {
|
||||
i, err := binary.ReadUvarint(r.Reader)
|
||||
if err != nil {
|
||||
errorf("import %q: read varint error: %v", r.path, err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
const predeclReserved = 32
|
||||
|
||||
type itag uint64
|
||||
|
||||
const (
|
||||
// Types
|
||||
definedType itag = iota
|
||||
pointerType
|
||||
sliceType
|
||||
arrayType
|
||||
chanType
|
||||
mapType
|
||||
signatureType
|
||||
structType
|
||||
interfaceType
|
||||
)
|
||||
|
||||
// IImportData imports a package from the serialized package data
|
||||
// and returns the number of bytes consumed and a reference to the package.
|
||||
// If the export data version is not recognized or the format is otherwise
|
||||
// compromised, an error is returned.
|
||||
func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) {
|
||||
const currentVersion = 0
|
||||
version := -1
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if version > currentVersion {
|
||||
err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e)
|
||||
} else {
|
||||
err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
r := &intReader{bytes.NewReader(data), path}
|
||||
|
||||
version = int(r.uint64())
|
||||
switch version {
|
||||
case currentVersion:
|
||||
default:
|
||||
errorf("unknown iexport format version %d", version)
|
||||
}
|
||||
|
||||
sLen := int64(r.uint64())
|
||||
dLen := int64(r.uint64())
|
||||
|
||||
whence, _ := r.Seek(0, io.SeekCurrent)
|
||||
stringData := data[whence : whence+sLen]
|
||||
declData := data[whence+sLen : whence+sLen+dLen]
|
||||
r.Seek(sLen+dLen, io.SeekCurrent)
|
||||
|
||||
p := iimporter{
|
||||
ipath: path,
|
||||
|
||||
stringData: stringData,
|
||||
stringCache: make(map[uint64]string),
|
||||
pkgCache: make(map[uint64]*types.Package),
|
||||
|
||||
declData: declData,
|
||||
pkgIndex: make(map[*types.Package]map[string]uint64),
|
||||
typCache: make(map[uint64]types.Type),
|
||||
|
||||
fake: fakeFileSet{
|
||||
fset: fset,
|
||||
files: make(map[string]*token.File),
|
||||
},
|
||||
}
|
||||
|
||||
for i, pt := range predeclared() {
|
||||
p.typCache[uint64(i)] = pt
|
||||
}
|
||||
|
||||
pkgList := make([]*types.Package, r.uint64())
|
||||
for i := range pkgList {
|
||||
pkgPathOff := r.uint64()
|
||||
pkgPath := p.stringAt(pkgPathOff)
|
||||
pkgName := p.stringAt(r.uint64())
|
||||
_ = r.uint64() // package height; unused by go/types
|
||||
|
||||
if pkgPath == "" {
|
||||
pkgPath = path
|
||||
}
|
||||
pkg := imports[pkgPath]
|
||||
if pkg == nil {
|
||||
pkg = types.NewPackage(pkgPath, pkgName)
|
||||
imports[pkgPath] = pkg
|
||||
} else if pkg.Name() != pkgName {
|
||||
errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path)
|
||||
}
|
||||
|
||||
p.pkgCache[pkgPathOff] = pkg
|
||||
|
||||
nameIndex := make(map[string]uint64)
|
||||
for nSyms := r.uint64(); nSyms > 0; nSyms-- {
|
||||
name := p.stringAt(r.uint64())
|
||||
nameIndex[name] = r.uint64()
|
||||
}
|
||||
|
||||
p.pkgIndex[pkg] = nameIndex
|
||||
pkgList[i] = pkg
|
||||
}
|
||||
var localpkg *types.Package
|
||||
for _, pkg := range pkgList {
|
||||
if pkg.Path() == path {
|
||||
localpkg = pkg
|
||||
}
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(p.pkgIndex[localpkg]))
|
||||
for name := range p.pkgIndex[localpkg] {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
p.doDecl(localpkg, name)
|
||||
}
|
||||
|
||||
for _, typ := range p.interfaceList {
|
||||
typ.Complete()
|
||||
}
|
||||
|
||||
// record all referenced packages as imports
|
||||
list := append(([]*types.Package)(nil), pkgList[1:]...)
|
||||
sort.Sort(byPath(list))
|
||||
localpkg.SetImports(list)
|
||||
|
||||
// package was imported completely and without errors
|
||||
localpkg.MarkComplete()
|
||||
|
||||
consumed, _ := r.Seek(0, io.SeekCurrent)
|
||||
return int(consumed), localpkg, nil
|
||||
}
|
||||
|
||||
type iimporter struct {
|
||||
ipath string
|
||||
|
||||
stringData []byte
|
||||
stringCache map[uint64]string
|
||||
pkgCache map[uint64]*types.Package
|
||||
|
||||
declData []byte
|
||||
pkgIndex map[*types.Package]map[string]uint64
|
||||
typCache map[uint64]types.Type
|
||||
|
||||
fake fakeFileSet
|
||||
interfaceList []*types.Interface
|
||||
}
|
||||
|
||||
func (p *iimporter) doDecl(pkg *types.Package, name string) {
|
||||
// See if we've already imported this declaration.
|
||||
if obj := pkg.Scope().Lookup(name); obj != nil {
|
||||
return
|
||||
}
|
||||
|
||||
off, ok := p.pkgIndex[pkg][name]
|
||||
if !ok {
|
||||
errorf("%v.%v not in index", pkg, name)
|
||||
}
|
||||
|
||||
r := &importReader{p: p, currPkg: pkg}
|
||||
r.declReader.Reset(p.declData[off:])
|
||||
|
||||
r.obj(name)
|
||||
}
|
||||
|
||||
func (p *iimporter) stringAt(off uint64) string {
|
||||
if s, ok := p.stringCache[off]; ok {
|
||||
return s
|
||||
}
|
||||
|
||||
slen, n := binary.Uvarint(p.stringData[off:])
|
||||
if n <= 0 {
|
||||
errorf("varint failed")
|
||||
}
|
||||
spos := off + uint64(n)
|
||||
s := string(p.stringData[spos : spos+slen])
|
||||
p.stringCache[off] = s
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *iimporter) pkgAt(off uint64) *types.Package {
|
||||
if pkg, ok := p.pkgCache[off]; ok {
|
||||
return pkg
|
||||
}
|
||||
path := p.stringAt(off)
|
||||
errorf("missing package %q in %q", path, p.ipath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *iimporter) typAt(off uint64, base *types.Named) types.Type {
|
||||
if t, ok := p.typCache[off]; ok && (base == nil || !isInterface(t)) {
|
||||
return t
|
||||
}
|
||||
|
||||
if off < predeclReserved {
|
||||
errorf("predeclared type missing from cache: %v", off)
|
||||
}
|
||||
|
||||
r := &importReader{p: p}
|
||||
r.declReader.Reset(p.declData[off-predeclReserved:])
|
||||
t := r.doType(base)
|
||||
|
||||
if base == nil || !isInterface(t) {
|
||||
p.typCache[off] = t
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type importReader struct {
|
||||
p *iimporter
|
||||
declReader bytes.Reader
|
||||
currPkg *types.Package
|
||||
prevFile string
|
||||
prevLine int64
|
||||
}
|
||||
|
||||
func (r *importReader) obj(name string) {
|
||||
tag := r.byte()
|
||||
pos := r.pos()
|
||||
|
||||
switch tag {
|
||||
case 'A':
|
||||
typ := r.typ()
|
||||
|
||||
r.declare(types.NewTypeName(pos, r.currPkg, name, typ))
|
||||
|
||||
case 'C':
|
||||
typ, val := r.value()
|
||||
|
||||
r.declare(types.NewConst(pos, r.currPkg, name, typ, val))
|
||||
|
||||
case 'F':
|
||||
sig := r.signature(nil)
|
||||
|
||||
r.declare(types.NewFunc(pos, r.currPkg, name, sig))
|
||||
|
||||
case 'T':
|
||||
// Types can be recursive. We need to setup a stub
|
||||
// declaration before recursing.
|
||||
obj := types.NewTypeName(pos, r.currPkg, name, nil)
|
||||
named := types.NewNamed(obj, nil, nil)
|
||||
r.declare(obj)
|
||||
|
||||
underlying := r.p.typAt(r.uint64(), named).Underlying()
|
||||
named.SetUnderlying(underlying)
|
||||
|
||||
if !isInterface(underlying) {
|
||||
for n := r.uint64(); n > 0; n-- {
|
||||
mpos := r.pos()
|
||||
mname := r.ident()
|
||||
recv := r.param()
|
||||
msig := r.signature(recv)
|
||||
|
||||
named.AddMethod(types.NewFunc(mpos, r.currPkg, mname, msig))
|
||||
}
|
||||
}
|
||||
|
||||
case 'V':
|
||||
typ := r.typ()
|
||||
|
||||
r.declare(types.NewVar(pos, r.currPkg, name, typ))
|
||||
|
||||
default:
|
||||
errorf("unexpected tag: %v", tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *importReader) declare(obj types.Object) {
|
||||
obj.Pkg().Scope().Insert(obj)
|
||||
}
|
||||
|
||||
func (r *importReader) value() (typ types.Type, val constant.Value) {
|
||||
typ = r.typ()
|
||||
|
||||
switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType {
|
||||
case types.IsBoolean:
|
||||
val = constant.MakeBool(r.bool())
|
||||
|
||||
case types.IsString:
|
||||
val = constant.MakeString(r.string())
|
||||
|
||||
case types.IsInteger:
|
||||
val = r.mpint(b)
|
||||
|
||||
case types.IsFloat:
|
||||
val = r.mpfloat(b)
|
||||
|
||||
case types.IsComplex:
|
||||
re := r.mpfloat(b)
|
||||
im := r.mpfloat(b)
|
||||
val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im))
|
||||
|
||||
default:
|
||||
if b.Kind() == types.Invalid {
|
||||
val = constant.MakeUnknown()
|
||||
return
|
||||
}
|
||||
errorf("unexpected type %v", typ) // panics
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func intSize(b *types.Basic) (signed bool, maxBytes uint) {
|
||||
if (b.Info() & types.IsUntyped) != 0 {
|
||||
return true, 64
|
||||
}
|
||||
|
||||
switch b.Kind() {
|
||||
case types.Float32, types.Complex64:
|
||||
return true, 3
|
||||
case types.Float64, types.Complex128:
|
||||
return true, 7
|
||||
}
|
||||
|
||||
signed = (b.Info() & types.IsUnsigned) == 0
|
||||
switch b.Kind() {
|
||||
case types.Int8, types.Uint8:
|
||||
maxBytes = 1
|
||||
case types.Int16, types.Uint16:
|
||||
maxBytes = 2
|
||||
case types.Int32, types.Uint32:
|
||||
maxBytes = 4
|
||||
default:
|
||||
maxBytes = 8
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *importReader) mpint(b *types.Basic) constant.Value {
|
||||
signed, maxBytes := intSize(b)
|
||||
|
||||
maxSmall := 256 - maxBytes
|
||||
if signed {
|
||||
maxSmall = 256 - 2*maxBytes
|
||||
}
|
||||
if maxBytes == 1 {
|
||||
maxSmall = 256
|
||||
}
|
||||
|
||||
n, _ := r.declReader.ReadByte()
|
||||
if uint(n) < maxSmall {
|
||||
v := int64(n)
|
||||
if signed {
|
||||
v >>= 1
|
||||
if n&1 != 0 {
|
||||
v = ^v
|
||||
}
|
||||
}
|
||||
return constant.MakeInt64(v)
|
||||
}
|
||||
|
||||
v := -n
|
||||
if signed {
|
||||
v = -(n &^ 1) >> 1
|
||||
}
|
||||
if v < 1 || uint(v) > maxBytes {
|
||||
errorf("weird decoding: %v, %v => %v", n, signed, v)
|
||||
}
|
||||
|
||||
buf := make([]byte, v)
|
||||
io.ReadFull(&r.declReader, buf)
|
||||
|
||||
// convert to little endian
|
||||
// TODO(gri) go/constant should have a more direct conversion function
|
||||
// (e.g., once it supports a big.Float based implementation)
|
||||
for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
|
||||
buf[i], buf[j] = buf[j], buf[i]
|
||||
}
|
||||
|
||||
x := constant.MakeFromBytes(buf)
|
||||
if signed && n&1 != 0 {
|
||||
x = constant.UnaryOp(token.SUB, x, 0)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (r *importReader) mpfloat(b *types.Basic) constant.Value {
|
||||
x := r.mpint(b)
|
||||
if constant.Sign(x) == 0 {
|
||||
return x
|
||||
}
|
||||
|
||||
exp := r.int64()
|
||||
switch {
|
||||
case exp > 0:
|
||||
x = constant.Shift(x, token.SHL, uint(exp))
|
||||
case exp < 0:
|
||||
d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp))
|
||||
x = constant.BinaryOp(x, token.QUO, d)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (r *importReader) ident() string {
|
||||
return r.string()
|
||||
}
|
||||
|
||||
func (r *importReader) qualifiedIdent() (*types.Package, string) {
|
||||
name := r.string()
|
||||
pkg := r.pkg()
|
||||
return pkg, name
|
||||
}
|
||||
|
||||
func (r *importReader) pos() token.Pos {
|
||||
delta := r.int64()
|
||||
if delta != deltaNewFile {
|
||||
r.prevLine += delta
|
||||
} else if l := r.int64(); l == -1 {
|
||||
r.prevLine += deltaNewFile
|
||||
} else {
|
||||
r.prevFile = r.string()
|
||||
r.prevLine = l
|
||||
}
|
||||
|
||||
if r.prevFile == "" && r.prevLine == 0 {
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
return r.p.fake.pos(r.prevFile, int(r.prevLine))
|
||||
}
|
||||
|
||||
func (r *importReader) typ() types.Type {
|
||||
return r.p.typAt(r.uint64(), nil)
|
||||
}
|
||||
|
||||
func isInterface(t types.Type) bool {
|
||||
_, ok := t.(*types.Interface)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *importReader) pkg() *types.Package { return r.p.pkgAt(r.uint64()) }
|
||||
func (r *importReader) string() string { return r.p.stringAt(r.uint64()) }
|
||||
|
||||
func (r *importReader) doType(base *types.Named) types.Type {
|
||||
switch k := r.kind(); k {
|
||||
default:
|
||||
errorf("unexpected kind tag in %q: %v", r.p.ipath, k)
|
||||
return nil
|
||||
|
||||
case definedType:
|
||||
pkg, name := r.qualifiedIdent()
|
||||
r.p.doDecl(pkg, name)
|
||||
return pkg.Scope().Lookup(name).(*types.TypeName).Type()
|
||||
case pointerType:
|
||||
return types.NewPointer(r.typ())
|
||||
case sliceType:
|
||||
return types.NewSlice(r.typ())
|
||||
case arrayType:
|
||||
n := r.uint64()
|
||||
return types.NewArray(r.typ(), int64(n))
|
||||
case chanType:
|
||||
dir := chanDir(int(r.uint64()))
|
||||
return types.NewChan(dir, r.typ())
|
||||
case mapType:
|
||||
return types.NewMap(r.typ(), r.typ())
|
||||
case signatureType:
|
||||
r.currPkg = r.pkg()
|
||||
return r.signature(nil)
|
||||
|
||||
case structType:
|
||||
r.currPkg = r.pkg()
|
||||
|
||||
fields := make([]*types.Var, r.uint64())
|
||||
tags := make([]string, len(fields))
|
||||
for i := range fields {
|
||||
fpos := r.pos()
|
||||
fname := r.ident()
|
||||
ftyp := r.typ()
|
||||
emb := r.bool()
|
||||
tag := r.string()
|
||||
|
||||
fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb)
|
||||
tags[i] = tag
|
||||
}
|
||||
return types.NewStruct(fields, tags)
|
||||
|
||||
case interfaceType:
|
||||
r.currPkg = r.pkg()
|
||||
|
||||
embeddeds := make([]types.Type, r.uint64())
|
||||
for i := range embeddeds {
|
||||
_ = r.pos()
|
||||
embeddeds[i] = r.typ()
|
||||
}
|
||||
|
||||
methods := make([]*types.Func, r.uint64())
|
||||
for i := range methods {
|
||||
mpos := r.pos()
|
||||
mname := r.ident()
|
||||
|
||||
// TODO(mdempsky): Matches bimport.go, but I
|
||||
// don't agree with this.
|
||||
var recv *types.Var
|
||||
if base != nil {
|
||||
recv = types.NewVar(token.NoPos, r.currPkg, "", base)
|
||||
}
|
||||
|
||||
msig := r.signature(recv)
|
||||
methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig)
|
||||
}
|
||||
|
||||
typ := newInterface(methods, embeddeds)
|
||||
r.p.interfaceList = append(r.p.interfaceList, typ)
|
||||
return typ
|
||||
}
|
||||
}
|
||||
|
||||
func (r *importReader) kind() itag {
|
||||
return itag(r.uint64())
|
||||
}
|
||||
|
||||
func (r *importReader) signature(recv *types.Var) *types.Signature {
|
||||
params := r.paramList()
|
||||
results := r.paramList()
|
||||
variadic := params.Len() > 0 && r.bool()
|
||||
return types.NewSignature(recv, params, results, variadic)
|
||||
}
|
||||
|
||||
func (r *importReader) paramList() *types.Tuple {
|
||||
xs := make([]*types.Var, r.uint64())
|
||||
for i := range xs {
|
||||
xs[i] = r.param()
|
||||
}
|
||||
return types.NewTuple(xs...)
|
||||
}
|
||||
|
||||
func (r *importReader) param() *types.Var {
|
||||
pos := r.pos()
|
||||
name := r.ident()
|
||||
typ := r.typ()
|
||||
return types.NewParam(pos, r.currPkg, name, typ)
|
||||
}
|
||||
|
||||
func (r *importReader) bool() bool {
|
||||
return r.uint64() != 0
|
||||
}
|
||||
|
||||
func (r *importReader) int64() int64 {
|
||||
n, err := binary.ReadVarint(&r.declReader)
|
||||
if err != nil {
|
||||
errorf("readVarint: %v", err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (r *importReader) uint64() uint64 {
|
||||
n, err := binary.ReadUvarint(&r.declReader)
|
||||
if err != nil {
|
||||
errorf("readUvarint: %v", err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (r *importReader) byte() byte {
|
||||
x, err := r.declReader.ReadByte()
|
||||
if err != nil {
|
||||
errorf("declReader.ReadByte: %v", err)
|
||||
}
|
||||
return x
|
||||
}
|
21
vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go
generated
vendored
Normal file
21
vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// 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.
|
||||
|
||||
// +build !go1.11
|
||||
|
||||
package gcimporter
|
||||
|
||||
import "go/types"
|
||||
|
||||
func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface {
|
||||
named := make([]*types.Named, len(embeddeds))
|
||||
for i, e := range embeddeds {
|
||||
var ok bool
|
||||
named[i], ok = e.(*types.Named)
|
||||
if !ok {
|
||||
panic("embedding of non-defined interfaces in interfaces is not supported before Go 1.11")
|
||||
}
|
||||
}
|
||||
return types.NewInterface(methods, named)
|
||||
}
|
13
vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go
generated
vendored
Normal file
13
vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
// 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.
|
||||
|
||||
// +build go1.11
|
||||
|
||||
package gcimporter
|
||||
|
||||
import "go/types"
|
||||
|
||||
func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface {
|
||||
return types.NewInterfaceType(methods, embeddeds)
|
||||
}
|
|
@ -1,6 +1,3 @@
|
|||
# github.com/astaxie/session v0.0.0-20130408050157-95d7fe18579c
|
||||
github.com/astaxie/session
|
||||
github.com/astaxie/session/providers/memory
|
||||
# github.com/asticode/go-astilectron v0.8.0
|
||||
github.com/asticode/go-astilectron
|
||||
# github.com/asticode/go-astilog v1.0.0
|
||||
|
@ -46,6 +43,8 @@ github.com/mattn/go-colorable
|
|||
github.com/mattn/go-isatty
|
||||
# github.com/mattn/go-sqlite3 v1.11.0
|
||||
github.com/mattn/go-sqlite3
|
||||
# github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
|
||||
github.com/pkg/browser
|
||||
# github.com/pkg/errors v0.8.1
|
||||
github.com/pkg/errors
|
||||
# github.com/sirupsen/logrus v1.2.0
|
||||
|
@ -58,6 +57,9 @@ github.com/valyala/fasttemplate
|
|||
golang.org/x/crypto/acme
|
||||
golang.org/x/crypto/acme/autocert
|
||||
golang.org/x/crypto/ssh/terminal
|
||||
# golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f
|
||||
golang.org/x/lint/golint
|
||||
golang.org/x/lint
|
||||
# golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
|
||||
golang.org/x/net/idna
|
||||
golang.org/x/net/context/ctxhttp
|
||||
|
@ -69,6 +71,12 @@ golang.org/x/text/secure/bidirule
|
|||
golang.org/x/text/unicode/bidi
|
||||
golang.org/x/text/unicode/norm
|
||||
golang.org/x/text/transform
|
||||
# golang.org/x/tools v0.0.0-20190610214847-0945d3616f18
|
||||
golang.org/x/tools/go/ast/astutil
|
||||
golang.org/x/tools/go/gcexportdata
|
||||
golang.org/x/tools/go/internal/gcimporter
|
||||
# src.techknowlogick.com/xgo v0.0.0-20190904182048-b4fba6551e4a
|
||||
src.techknowlogick.com/xgo
|
||||
# xorm.io/builder v0.3.5
|
||||
xorm.io/builder
|
||||
# xorm.io/core v0.7.0
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
local BuildSwitchDryRun(version='go-latest', tags='latest', dry=false, depends='') = {
|
||||
name: if dry then 'dry-run-' + version else 'build-' + version,
|
||||
pull: 'always',
|
||||
image: 'plugins/docker',
|
||||
settings: {
|
||||
dockerfile: 'docker/' + version + '/Dockerfile',
|
||||
context: 'docker/' + version,
|
||||
password: {
|
||||
from_secret: 'docker_password'
|
||||
},
|
||||
username: {
|
||||
from_secret: 'docker_username'
|
||||
},
|
||||
repo: 'techknowlogick/xgo',
|
||||
tags: tags,
|
||||
dry_run: dry
|
||||
},
|
||||
[if depends != '' then 'depends_on']: [
|
||||
depends
|
||||
],
|
||||
[if !dry then 'when']: {
|
||||
branch: ['master'],
|
||||
event: {exclude: ['pull_request']}
|
||||
},
|
||||
[if dry then 'when']: {
|
||||
event: ['pull_request']
|
||||
},
|
||||
};
|
||||
|
||||
local BuildWithDiffTags(version='go-latest', tags='latest', depends='') = BuildSwitchDryRun(version, tags, false, depends);
|
||||
local BuildWithDiffTagsDry(version='go-latest', tags='latest', depends='') = BuildSwitchDryRun(version, tags, true, depends);
|
||||
local BuildStep(version='go-latest', depends='') = BuildWithDiffTags(version, version, depends);
|
||||
local BuildStepDry(version='go-latest', depends='') = BuildSwitchDryRun(version, version, true, depends);
|
||||
|
||||
{
|
||||
kind: 'pipeline',
|
||||
name: 'default',
|
||||
steps: [
|
||||
BuildStepDry('base'),
|
||||
BuildStepDry('go-1.13.0', 'dry-run-base'),
|
||||
BuildStepDry('go-1.12.9', 'dry-run-base'),
|
||||
|
||||
BuildStep('base'),
|
||||
BuildStep('go-1.13.0', 'build-base'),
|
||||
BuildStep('go-1.13.x', 'build-go-1.13.0'),
|
||||
BuildStep('go-1.12.9', 'build-base'),
|
||||
BuildStep('go-1.12.x', 'build-go-1.12.9'),
|
||||
BuildWithDiffTags('go-latest', 'latest', 'build-go-1.13.x'),
|
||||
]
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: dry-run-base
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/base
|
||||
dockerfile: docker/base/Dockerfile
|
||||
dry_run: true
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: base
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
|
||||
- name: dry-run-go-1.13.0
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/go-1.13.0
|
||||
dockerfile: docker/go-1.13.0/Dockerfile
|
||||
dry_run: true
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: go-1.13.0
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- dry-run-base
|
||||
|
||||
- name: dry-run-go-1.12.9
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/go-1.12.9
|
||||
dockerfile: docker/go-1.12.9/Dockerfile
|
||||
dry_run: true
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: go-1.12.9
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- dry-run-base
|
||||
|
||||
- name: build-base
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/base
|
||||
dockerfile: docker/base/Dockerfile
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: base
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
- name: build-go-1.13.0
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/go-1.13.0
|
||||
dockerfile: docker/go-1.13.0/Dockerfile
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: go-1.13.0
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- build-base
|
||||
|
||||
- name: build-go-1.13.x
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/go-1.13.x
|
||||
dockerfile: docker/go-1.13.x/Dockerfile
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: go-1.13.x
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- build-go-1.13.0
|
||||
|
||||
- name: build-go-1.12.9
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/go-1.12.9
|
||||
dockerfile: docker/go-1.12.9/Dockerfile
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: go-1.12.9
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- build-base
|
||||
|
||||
- name: build-go-1.12.x
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/go-1.12.x
|
||||
dockerfile: docker/go-1.12.x/Dockerfile
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: go-1.12.x
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- build-go-1.12.9
|
||||
|
||||
- name: build-go-latest
|
||||
pull: always
|
||||
image: plugins/docker
|
||||
settings:
|
||||
context: docker/go-latest
|
||||
dockerfile: docker/go-latest/Dockerfile
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: techknowlogick/xgo
|
||||
tags: latest
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- build-go-1.13.x
|
||||
|
||||
...
|
|
@ -0,0 +1,2 @@
|
|||
.idea/
|
||||
xgo
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2014 Péter Szilágyi <peterke@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,246 @@
|
|||
# xgo - Go CGO cross compiler
|
||||
|
||||
[![Build Status](https://cloud.drone.io/api/badges/techknowlogick/xgo/status.svg)](https://cloud.drone.io/techknowlogick/xgo)
|
||||
|
||||
Although Go strives to be a cross platform language, cross compilation from one
|
||||
platform to another is not as simple as it could be, as you need the Go sources
|
||||
bootstrapped to each platform and architecture.
|
||||
|
||||
The first step towards cross compiling was Dave Cheney's [golang-crosscompile](https://github.com/davecheney/golang-crosscompile)
|
||||
package, which automatically bootstrapped the necessary sources based on your
|
||||
existing Go installation. Although this was enough for a lot of cases, certain
|
||||
drawbacks became apparent where the official libraries used CGO internally: any
|
||||
dependency to third party platform code is unavailable, hence those parts don't
|
||||
cross compile nicely (native DNS resolution, system certificate access, etc).
|
||||
|
||||
A step forward in enabling cross compilation was Alan Shreve's [gonative](https://github.com/inconshreveable/gonative)
|
||||
package, which instead of bootstrapping the different platforms based on the
|
||||
existing Go installation, downloaded the official pre-compiled binaries from the
|
||||
golang website and injected those into the local toolchain. Since the pre-built
|
||||
binaries already contained the necessary platform specific code, the few missing
|
||||
dependencies were resolved, and true cross compilation could commence... of pure
|
||||
Go code.
|
||||
|
||||
However, there was still one feature missing: cross compiling Go code that used
|
||||
CGO itself, which isn't trivial since you need access to OS specific headers and
|
||||
libraries. This becomes very annoying when you need access only to some trivial
|
||||
OS specific functionality (e.g. query the CPU load), but need to configure and
|
||||
maintain separate build environments to do it.
|
||||
|
||||
## Enter xgo
|
||||
|
||||
My solution to the challenge of cross compiling Go code with embedded C/C++ snippets
|
||||
(i.e. CGO_ENABLED=1) is based on the concept of [lightweight Linux containers](http://en.wikipedia.org/wiki/LXC).
|
||||
All the necessary Go tool-chains, C cross compilers and platform headers/libraries
|
||||
have been assembled into a single Docker container, which can then be called as if
|
||||
a single command to compile a Go package to various platforms and architectures.
|
||||
|
||||
## Installation
|
||||
|
||||
Although you could build the container manually, it is available as an automatic
|
||||
trusted build from Docker's container registry (not insignificant in size):
|
||||
|
||||
docker pull techknowlogick/xgo:latest
|
||||
|
||||
To prevent having to remember a potentially complex Docker command every time,
|
||||
a lightweight Go wrapper was written on top of it.
|
||||
|
||||
go get src.techknowlogick.com/xgo
|
||||
|
||||
## Usage
|
||||
|
||||
Simply specify the import path you want to build, and xgo will do the rest:
|
||||
|
||||
$ xgo github.com/project-iris/iris
|
||||
...
|
||||
|
||||
$ ls -al
|
||||
-rwxr-xr-x 1 root root 6776500 Nov 24 16:44 iris-darwin-10.6-386
|
||||
-rwxr-xr-x 1 root root 8755532 Nov 24 16:44 iris-darwin-10.6-amd64
|
||||
-rwxr-xr-x 1 root root 10135248 Nov 24 16:44 iris-linux-386
|
||||
-rwxr-xr-x 1 root root 12598472 Nov 24 16:44 iris-linux-amd64
|
||||
-rwxr-xr-x 1 root root 10040464 Nov 24 16:44 iris-linux-arm
|
||||
-rwxr-xr-x 1 root root 7516368 Nov 24 16:44 iris-windows-4.0-386.exe
|
||||
-rwxr-xr-x 1 root root 9549416 Nov 24 16:44 iris-windows-4.0-amd64.exe
|
||||
|
||||
|
||||
If the path is not a canonical import path, but rather a local path (starts with
|
||||
a dot `.` or a dash `/`), xgo will use the local GOPATH contents for the cross
|
||||
compilation.
|
||||
|
||||
|
||||
### Build flags
|
||||
|
||||
A handful of flags can be passed to `go build`. The currently supported ones are
|
||||
|
||||
- `-v`: prints the names of packages as they are compiled
|
||||
- `-x`: prints the build commands as compilation progresses
|
||||
- `-race`: enables data race detection (supported only on amd64, rest built without)
|
||||
- `-tags='tag list'`: list of build tags to consider satisfied during the build
|
||||
- `-ldflags='flag list'`: arguments to pass on each go tool link invocation
|
||||
- `-buildmode=mode`: binary type to produce by the compiler
|
||||
|
||||
|
||||
### Go releases
|
||||
|
||||
As newer versions of the language runtime, libraries and tools get released,
|
||||
these will get incorporated into xgo too as extensions layers to the base cross
|
||||
compilation image (only Go 1.3 and above will be supported).
|
||||
|
||||
You can select which Go release to work with through the `-go` command line flag
|
||||
to xgo and if the specific release was already integrated, it will automatically
|
||||
be retrieved and installed.
|
||||
|
||||
$ xgo -go 1.6.1 github.com/project-iris/iris
|
||||
|
||||
Additionally, a few wildcard release strings are also supported:
|
||||
|
||||
- `latest` will use the latest Go release (this is the default)
|
||||
- `1.6.x` will use the latest point release of a specific Go version
|
||||
- `1.6-develop` will use the develop branch of a specific Go version
|
||||
- `develop` will use the develop branch of the entire Go repository
|
||||
|
||||
### Output prefixing
|
||||
|
||||
xgo by default uses the name of the package being cross compiled as the output
|
||||
file prefix. This can be overridden with the `-out` flag.
|
||||
|
||||
$ xgo -out iris-v0.3.2 github.com/project-iris/iris
|
||||
...
|
||||
|
||||
$ ls -al
|
||||
-rwxr-xr-x 1 root root 6776500 Nov 24 16:44 iris-v0.3.2-darwin-10.6-386
|
||||
-rwxr-xr-x 1 root root 8755532 Nov 24 16:44 iris-v0.3.2-darwin-10.6-amd64
|
||||
-rwxr-xr-x 1 root root 10135248 Nov 24 16:44 iris-v0.3.2-linux-386
|
||||
-rwxr-xr-x 1 root root 12598472 Nov 24 16:44 iris-v0.3.2-linux-amd64
|
||||
-rwxr-xr-x 1 root root 10040464 Nov 24 16:44 iris-v0.3.2-linux-arm
|
||||
-rwxr-xr-x 1 root root 7516368 Nov 24 16:44 iris-v0.3.2-windows-4.0-386.exe
|
||||
-rwxr-xr-x 1 root root 9549416 Nov 24 16:44 iris-v0.3.2-windows-4.0-amd64.exe
|
||||
|
||||
|
||||
### Branch selection
|
||||
|
||||
Similarly to `go get`, xgo also uses the `master` branch of a repository during
|
||||
source code retrieval. To switch to a different branch before compilation pass
|
||||
the desired branch name through the `--branch` argument.
|
||||
|
||||
$ xgo --branch release-branch.go1.4 golang.org/x/tools/cmd/goimports
|
||||
...
|
||||
|
||||
$ ls -al
|
||||
-rwxr-xr-x 1 root root 4139868 Nov 24 16:40 goimports-darwin-10.6-386
|
||||
-rwxr-xr-x 1 root root 5186720 Nov 24 16:40 goimports-darwin-10.6-amd64
|
||||
-rwxr-xr-x 1 root root 4189456 Nov 24 16:40 goimports-linux-386
|
||||
-rwxr-xr-x 1 root root 5264136 Nov 24 16:40 goimports-linux-amd64
|
||||
-rwxr-xr-x 1 root root 4209416 Nov 24 16:40 goimports-linux-arm
|
||||
-rwxr-xr-x 1 root root 4348416 Nov 24 16:40 goimports-windows-4.0-386.exe
|
||||
-rwxr-xr-x 1 root root 5415424 Nov 24 16:40 goimports-windows-4.0-amd64.exe
|
||||
|
||||
|
||||
### Remote selection
|
||||
|
||||
Yet again similarly to `go get`, xgo uses the repository remote corresponding to
|
||||
the import path being built. To switch to a different remote while preserving the
|
||||
original import path, use the `--remote` argument.
|
||||
|
||||
$ xgo --remote github.com/golang/tools golang.org/x/tools/cmd/goimports
|
||||
...
|
||||
|
||||
### Package selection
|
||||
|
||||
If you used the above *branch* or *remote* selection machanisms, it may happen
|
||||
that the path you are trying to build is only present in the specific branch and
|
||||
not the default repository, causing Go to fail at locating it. To circumvent this,
|
||||
you may specify only the repository root for xgo, and use an additional `--pkg`
|
||||
parameter to select the exact package within, honoring any prior *branch* and
|
||||
*remote* selections.
|
||||
|
||||
$ xgo --pkg cmd/goimports golang.org/x/tools
|
||||
...
|
||||
|
||||
$ ls -al
|
||||
-rwxr-xr-x 1 root root 4164448 Nov 24 16:38 goimports-darwin-10.6-386
|
||||
-rwxr-xr-x 1 root root 5223584 Nov 24 16:38 goimports-darwin-10.6-amd64
|
||||
-rwxr-xr-x 1 root root 4217184 Nov 24 16:38 goimports-linux-386
|
||||
-rwxr-xr-x 1 root root 5295768 Nov 24 16:38 goimports-linux-amd64
|
||||
-rwxr-xr-x 1 root root 4233120 Nov 24 16:38 goimports-linux-arm
|
||||
-rwxr-xr-x 1 root root 4373504 Nov 24 16:38 goimports-windows-4.0-386.exe
|
||||
-rwxr-xr-x 1 root root 5450240 Nov 24 16:38 goimports-windows-4.0-amd64.exe
|
||||
|
||||
This argument may at some point be integrated into the import path itself, but for
|
||||
now it exists as an independent build parameter. Also, there is not possibility
|
||||
for now to build mulitple commands in one go.
|
||||
|
||||
### Limit build targets
|
||||
|
||||
By default `xgo` will try and build the specified package to all platforms and
|
||||
architectures supported by the underlying Go runtime. If you wish to restrict
|
||||
the build to only a few target systems, use the comma separated `--targets` CLI
|
||||
argument:
|
||||
|
||||
* `--targets=linux/arm`: builds only the ARMv5 Linux binaries (`arm-6`/`arm-7` allowed)
|
||||
* `--targets=windows/*,darwin/*`: builds all Windows and OSX binaries
|
||||
* `--targets=*/arm`: builds ARM binaries for all platforms
|
||||
* `--targets=*/*`: builds all suppoted targets (default)
|
||||
|
||||
The supported targets are:
|
||||
|
||||
* Platforms: `darwin`, `linux`, `windows`
|
||||
* Achitectures: `386`, `amd64`, `arm-5`, `arm-6`, `arm-7`, `arm64`, `mips`, `mipsle`, `mips64`, `mips64le`
|
||||
|
||||
### Platform versions
|
||||
|
||||
By default `xgo` tries to cross compile to the lowest possible versions of every
|
||||
supported platform, in order to produce binaries that are portable among various
|
||||
versions of the same operating system. This however can lead to issues if a used
|
||||
dependency is only supported by more recent systems. As such, `xgo` supports the
|
||||
selection of specific platform versions by appending them to the OS target string.
|
||||
|
||||
* `--targets=darwin-10.9/*`: cross compile to Mac OS X Mavericks
|
||||
* `--targets=windows-6.0/*`: cross compile to Windows Vista
|
||||
|
||||
The supported platforms are:
|
||||
|
||||
* All Windows APIs up to Windows 8.1 limited by `mingw-w64` ([API level ids](https://en.wikipedia.org/wiki/Windows_NT#Releases))
|
||||
* OSX APIs in the range of 10.6 - 10.14
|
||||
|
||||
### CGO dependencies
|
||||
|
||||
The main differentiator of xgo versus other cross compilers is support for basic
|
||||
embedded C/C++ code and target-platform specific OS SDK availability. The current
|
||||
xgo release introduces an experimental CGO *dependency* cross compilation, enabling
|
||||
building Go programs that require external C/C++ libraries.
|
||||
|
||||
It is assumed that the dependent C/C++ library is `configure/make` based, was
|
||||
properly prepared for cross compilation and is available as a tarball download
|
||||
(`.tar`, `.tar.gz` or `.tar.bz2`). Further plans include extending this to cmake
|
||||
based projects, if need arises (please open an issue if it's important to you).
|
||||
|
||||
Such dependencies can be added via the `--deps` argument. They will be retrieved
|
||||
prior to starting the cross compilation and the packages cached to save bandwidth
|
||||
on subsequent calls.
|
||||
|
||||
A complex sample for such a scenario is building the Ethereum CLI node, which has
|
||||
the GNU Multiple Precision Arithmetic Library as it's dependency.
|
||||
|
||||
$ xgo --deps=https://gmplib.org/download/gmp/gmp-6.1.0.tar.bz2 \
|
||||
--targets=windows/* github.com/ethereum/go-ethereum/cmd/geth
|
||||
...
|
||||
|
||||
$ ls -al
|
||||
-rwxr-xr-x 1 root root 16315679 Nov 24 16:39 geth-windows-4.0-386.exe
|
||||
-rwxr-xr-x 1 root root 19452036 Nov 24 16:38 geth-windows-4.0-amd64.exe
|
||||
|
||||
Some trivial arguments may be passed to the dependencies' configure script via
|
||||
`--depsargs`.
|
||||
|
||||
$ xgo --deps=https://gmplib.org/download/gmp/gmp-6.1.0.tar.bz2 \
|
||||
--targets=ios/* --depsargs=--disable-assembly \
|
||||
github.com/ethereum/go-ethereum/cmd/geth
|
||||
...
|
||||
|
||||
$ ls -al
|
||||
-rwxr-xr-x 1 root root 14804160 Nov 24 16:32 geth-ios-5.0-arm
|
||||
|
||||
Note, that since xgo needs to cross compile the dependencies for each platform
|
||||
and architecture separately, build time can increase significantly.
|
|
@ -0,0 +1,3 @@
|
|||
module src.techknowlogick.com/xgo
|
||||
|
||||
go 1.12
|
|
@ -0,0 +1,84 @@
|
|||
// Go CGO cross compiler
|
||||
// Copyright (c) 2016 Péter Szilágyi. All rights reserved.
|
||||
//
|
||||
// Released under the MIT license.
|
||||
|
||||
// This is a manual test suite to run the cross compiler against various known
|
||||
// projects, codebases and repositories to ensure at least a baseline guarantee
|
||||
// that things work as they supposed to.
|
||||
//
|
||||
// Run as: go run testsuite.go
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// layers defines all the docker layers needed for the final xgo image. The last
|
||||
// one will be used to run the test suite against.
|
||||
var layers = []struct {
|
||||
tag string
|
||||
dir string
|
||||
}{
|
||||
{"techknowlogick/xgo:base", "base"},
|
||||
{"techknowlogick/xgo:1.12.4", "go-1.12.4"},
|
||||
{"techknowlogick/xgo:1.12.x", "go-1.12.x"},
|
||||
{"techknowlogick/xgo:latest", "go-latest"},
|
||||
}
|
||||
|
||||
// tests defaines all the input test cases and associated arguments the cross
|
||||
// compiler should be ran for and with which arguments.
|
||||
var tests = []struct {
|
||||
path string
|
||||
args []string
|
||||
}{
|
||||
// Tiny test cases to smoke test cross compilations
|
||||
{"github.com/karalabe/xgo/tests/embedded_c", nil},
|
||||
{"github.com/karalabe/xgo/tests/embedded_cpp", nil},
|
||||
|
||||
// Baseline projects to ensure minimal requirements
|
||||
{"github.com/ethereum/go-ethereum/cmd/geth", []string{"--branch", "develop"}},
|
||||
|
||||
// Third party projects using xgo, smoke test that they don't break
|
||||
{"github.com/rwcarlsen/cyan/cmd/cyan", nil},
|
||||
{"github.com/cockroachdb/cockroach", []string{"--targets", "darwin-10.6/amd64"}},
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Retrieve the current working directory to locate the dockerfiles
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to retrieve local working directory: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(pwd, "docker", "base")); err != nil {
|
||||
log.Fatalf("Failed to locate docker image: %v", err)
|
||||
}
|
||||
// Assemble the multi-layered xgo docker image
|
||||
for _, layer := range layers {
|
||||
cmd := exec.Command("docker", "build", "--tag", layer.tag, filepath.Join(pwd, "docker", layer.dir))
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatalf("Failed to build xgo layer: %v", err)
|
||||
}
|
||||
}
|
||||
// Iterate over each of the test cases and run them
|
||||
for i, test := range tests {
|
||||
cmd := exec.Command("docker", append([]string{"run", "--entrypoint", "xgo", layers[len(layers)-1].tag, "-v"}, append(test.args, test.path)...)...)
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatalf("Test #%d: cross compilation failed: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,404 @@
|
|||
// Go CGO cross compiler
|
||||
// Copyright (c) 2014 Péter Szilágyi. All rights reserved.
|
||||
//
|
||||
// Released under the MIT license.
|
||||
|
||||
// Wrapper around the GCO cross compiler docker container.
|
||||
package main // import "src.techknowlogick.com/xgo"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Path where to cache external dependencies
|
||||
var depsCache string
|
||||
|
||||
func init() {
|
||||
// Initialize the external dependency cache path to a few possible locations
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
depsCache = filepath.Join(home, ".xgo-cache")
|
||||
return
|
||||
}
|
||||
if user, err := user.Current(); user != nil && err == nil && user.HomeDir != "" {
|
||||
depsCache = filepath.Join(user.HomeDir, ".xgo-cache")
|
||||
return
|
||||
}
|
||||
depsCache = filepath.Join(os.TempDir(), "xgo-cache")
|
||||
}
|
||||
|
||||
// Cross compilation docker containers
|
||||
var dockerBase = "techknowlogick/xgo:base"
|
||||
var dockerDist = "techknowlogick/xgo:"
|
||||
|
||||
// Command line arguments to fine tune the compilation
|
||||
var (
|
||||
goVersion = flag.String("go", "latest", "Go release to use for cross compilation")
|
||||
srcPackage = flag.String("pkg", "", "Sub-package to build if not root import")
|
||||
srcRemote = flag.String("remote", "", "Version control remote repository to build")
|
||||
srcBranch = flag.String("branch", "", "Version control branch to build")
|
||||
outPrefix = flag.String("out", "", "Prefix to use for output naming (empty = package name)")
|
||||
outFolder = flag.String("dest", "", "Destination folder to put binaries in (empty = current)")
|
||||
crossDeps = flag.String("deps", "", "CGO dependencies (configure/make based archives)")
|
||||
crossArgs = flag.String("depsargs", "", "CGO dependency configure arguments")
|
||||
targets = flag.String("targets", "*/*", "Comma separated targets to build for")
|
||||
dockerImage = flag.String("image", "", "Use custom docker image instead of official distribution")
|
||||
)
|
||||
|
||||
// ConfigFlags is a simple set of flags to define the environment and dependencies.
|
||||
type ConfigFlags struct {
|
||||
Repository string // Root import path to build
|
||||
Package string // Sub-package to build if not root import
|
||||
Prefix string // Prefix to use for output naming
|
||||
Remote string // Version control remote repository to build
|
||||
Branch string // Version control branch to build
|
||||
Dependencies string // CGO dependencies (configure/make based archives)
|
||||
Arguments string // CGO dependency configure arguments
|
||||
Targets []string // Targets to build for
|
||||
}
|
||||
|
||||
// Command line arguments to pass to go build
|
||||
var (
|
||||
buildVerbose = flag.Bool("v", false, "Print the names of packages as they are compiled")
|
||||
buildSteps = flag.Bool("x", false, "Print the command as executing the builds")
|
||||
buildRace = flag.Bool("race", false, "Enable data race detection (supported only on amd64)")
|
||||
buildTags = flag.String("tags", "", "List of build tags to consider satisfied during the build")
|
||||
buildLdFlags = flag.String("ldflags", "", "Arguments to pass on each go tool link invocation")
|
||||
buildMode = flag.String("buildmode", "default", "Indicates which kind of object file to build")
|
||||
)
|
||||
|
||||
// BuildFlags is a simple collection of flags to fine tune a build.
|
||||
type BuildFlags struct {
|
||||
Verbose bool // Print the names of packages as they are compiled
|
||||
Steps bool // Print the command as executing the builds
|
||||
Race bool // Enable data race detection (supported only on amd64)
|
||||
Tags string // List of build tags to consider satisfied during the build
|
||||
LdFlags string // Arguments to pass on each go tool link invocation
|
||||
Mode string // Indicates which kind of object file to build
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Retrieve the CLI flags and the execution environment
|
||||
flag.Parse()
|
||||
|
||||
xgoInXgo := os.Getenv("XGO_IN_XGO") == "1"
|
||||
if xgoInXgo {
|
||||
depsCache = "/deps-cache"
|
||||
}
|
||||
// Only use docker images if we're not already inside out own image
|
||||
image := ""
|
||||
|
||||
if !xgoInXgo {
|
||||
// Ensure docker is available
|
||||
if err := checkDocker(); err != nil {
|
||||
log.Fatalf("Failed to check docker installation: %v.", err)
|
||||
}
|
||||
// Validate the command line arguments
|
||||
if len(flag.Args()) != 1 {
|
||||
log.Fatalf("Usage: %s [options] <go import path>", os.Args[0])
|
||||
}
|
||||
// Select the image to use, either official or custom
|
||||
image = dockerDist + *goVersion
|
||||
if *dockerImage != "" {
|
||||
image = *dockerImage
|
||||
}
|
||||
// Check that all required images are available
|
||||
found, err := checkDockerImage(image)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Fatalf("Failed to check docker image availability: %v.", err)
|
||||
case !found:
|
||||
fmt.Println("not found!")
|
||||
if err := pullDockerImage(image); err != nil {
|
||||
log.Fatalf("Failed to pull docker image from the registry: %v.", err)
|
||||
}
|
||||
default:
|
||||
fmt.Println("found.")
|
||||
}
|
||||
}
|
||||
// Cache all external dependencies to prevent always hitting the internet
|
||||
if *crossDeps != "" {
|
||||
if err := os.MkdirAll(depsCache, 0751); err != nil {
|
||||
log.Fatalf("Failed to create dependency cache: %v.", err)
|
||||
}
|
||||
// Download all missing dependencies
|
||||
for _, dep := range strings.Split(*crossDeps, " ") {
|
||||
if url := strings.TrimSpace(dep); len(url) > 0 {
|
||||
path := filepath.Join(depsCache, filepath.Base(url))
|
||||
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
fmt.Printf("Downloading new dependency: %s...\n", url)
|
||||
|
||||
out, err := os.Create(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create dependency file: %v.", err)
|
||||
}
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to retrieve dependency: %v.", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if _, err := io.Copy(out, res.Body); err != nil {
|
||||
log.Fatalf("Failed to download dependency: %v", err)
|
||||
}
|
||||
out.Close()
|
||||
|
||||
fmt.Printf("New dependency cached: %s.\n", path)
|
||||
} else {
|
||||
fmt.Printf("Dependency already cached: %s.\n", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Assemble the cross compilation environment and build options
|
||||
config := &ConfigFlags{
|
||||
Repository: flag.Args()[0],
|
||||
Package: *srcPackage,
|
||||
Remote: *srcRemote,
|
||||
Branch: *srcBranch,
|
||||
Prefix: *outPrefix,
|
||||
Dependencies: *crossDeps,
|
||||
Arguments: *crossArgs,
|
||||
Targets: strings.Split(*targets, ","),
|
||||
}
|
||||
flags := &BuildFlags{
|
||||
Verbose: *buildVerbose,
|
||||
Steps: *buildSteps,
|
||||
Race: *buildRace,
|
||||
Tags: *buildTags,
|
||||
LdFlags: *buildLdFlags,
|
||||
Mode: *buildMode,
|
||||
}
|
||||
folder, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to retrieve the working directory: %v.", err)
|
||||
}
|
||||
if *outFolder != "" {
|
||||
folder, err = filepath.Abs(*outFolder)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to resolve destination path (%s): %v.", *outFolder, err)
|
||||
}
|
||||
}
|
||||
// Execute the cross compilation, either in a container or the current system
|
||||
if !xgoInXgo {
|
||||
err = compile(image, config, flags, folder)
|
||||
} else {
|
||||
err = compileContained(config, flags, folder)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to cross compile package: %v.", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether a docker installation can be found and is functional.
|
||||
func checkDocker() error {
|
||||
fmt.Println("Checking docker installation...")
|
||||
if err := run(exec.Command("docker", "version")); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks whether a required docker image is available locally.
|
||||
func checkDockerImage(image string) (bool, error) {
|
||||
fmt.Printf("Checking for required docker image %s... ", image)
|
||||
out, err := exec.Command("docker", "images", "--no-trunc").Output()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return bytes.Contains(out, []byte(image)), nil
|
||||
}
|
||||
|
||||
// Pulls an image from the docker registry.
|
||||
func pullDockerImage(image string) error {
|
||||
fmt.Printf("Pulling %s from docker registry...\n", image)
|
||||
return run(exec.Command("docker", "pull", image))
|
||||
}
|
||||
|
||||
// compile cross builds a requested package according to the given build specs
|
||||
// using a specific docker cross compilation image.
|
||||
func compile(image string, config *ConfigFlags, flags *BuildFlags, folder string) error {
|
||||
// If a local build was requested, find the import path and mount all GOPATH sources
|
||||
locals, mounts, paths := []string{}, []string{}, []string{}
|
||||
var usesModules bool
|
||||
if strings.HasPrefix(config.Repository, string(filepath.Separator)) || strings.HasPrefix(config.Repository, ".") {
|
||||
// Resolve the repository import path from the file path
|
||||
config.Repository = resolveImportPath(config.Repository)
|
||||
|
||||
// Determine if this is a module-based repository
|
||||
var modFile = config.Repository + "/go.mod"
|
||||
_, err := os.Stat(modFile)
|
||||
usesModules = !os.IsNotExist(err)
|
||||
|
||||
// Iterate over all the local libs and export the mount points
|
||||
if os.Getenv("GOPATH") == "" && !usesModules {
|
||||
log.Fatalf("No $GOPATH is set or forwarded to xgo")
|
||||
}
|
||||
if !usesModules {
|
||||
for _, gopath := range strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) {
|
||||
// Since docker sandboxes volumes, resolve any symlinks manually
|
||||
sources := filepath.Join(gopath, "src")
|
||||
filepath.Walk(sources, func(path string, info os.FileInfo, err error) error {
|
||||
// Skip any folders that errored out
|
||||
if err != nil {
|
||||
log.Printf("Failed to access GOPATH element %s: %v", path, err)
|
||||
return nil
|
||||
}
|
||||
// Skip anything that's not a symlink
|
||||
if info.Mode()&os.ModeSymlink == 0 {
|
||||
return nil
|
||||
}
|
||||
// Resolve the symlink and skip if it's not a folder
|
||||
target, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if info, err = os.Stat(target); err != nil || !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
// Skip if the symlink points within GOPATH
|
||||
if filepath.HasPrefix(target, sources) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Folder needs explicit mounting due to docker symlink security
|
||||
locals = append(locals, target)
|
||||
mounts = append(mounts, filepath.Join("/ext-go", strconv.Itoa(len(locals)), "src", strings.TrimPrefix(path, sources)))
|
||||
paths = append(paths, filepath.Join("/ext-go", strconv.Itoa(len(locals))))
|
||||
return nil
|
||||
})
|
||||
// Export the main mount point for this GOPATH entry
|
||||
locals = append(locals, sources)
|
||||
mounts = append(mounts, filepath.Join("/ext-go", strconv.Itoa(len(locals)), "src"))
|
||||
paths = append(paths, filepath.Join("/ext-go", strconv.Itoa(len(locals))))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Assemble and run the cross compilation command
|
||||
fmt.Printf("Cross compiling %s...\n", config.Repository)
|
||||
|
||||
args := []string{
|
||||
"run", "--rm",
|
||||
"-v", folder + ":/build",
|
||||
"-v", depsCache + ":/deps-cache:ro",
|
||||
"-e", "REPO_REMOTE=" + config.Remote,
|
||||
"-e", "REPO_BRANCH=" + config.Branch,
|
||||
"-e", "PACK=" + config.Package,
|
||||
"-e", "DEPS=" + config.Dependencies,
|
||||
"-e", "ARGS=" + config.Arguments,
|
||||
"-e", "OUT=" + config.Prefix,
|
||||
"-e", fmt.Sprintf("FLAG_V=%v", flags.Verbose),
|
||||
"-e", fmt.Sprintf("FLAG_X=%v", flags.Steps),
|
||||
"-e", fmt.Sprintf("FLAG_RACE=%v", flags.Race),
|
||||
"-e", fmt.Sprintf("FLAG_TAGS=%s", flags.Tags),
|
||||
"-e", fmt.Sprintf("FLAG_LDFLAGS=%s", flags.LdFlags),
|
||||
"-e", fmt.Sprintf("FLAG_BUILDMODE=%s", flags.Mode),
|
||||
"-e", "TARGETS=" + strings.Replace(strings.Join(config.Targets, " "), "*", ".", -1),
|
||||
}
|
||||
if usesModules {
|
||||
args = append(args, []string{"-e", "GO111MODULE=on"}...)
|
||||
args = append(args, []string{"-v", os.Getenv("GOPATH") + ":/go"}...)
|
||||
|
||||
// Map this repository to the /source folder
|
||||
absRepository, err := filepath.Abs(config.Repository)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to locate requested module repository: %v.", err)
|
||||
}
|
||||
args = append(args, []string{"-v", absRepository + ":/source"}...)
|
||||
|
||||
fmt.Printf("Enabled Go module support\n")
|
||||
|
||||
// Check whether it has a vendor folder, and if so, use it
|
||||
vendorPath := absRepository + "/vendor"
|
||||
vendorfolder, err := os.Stat(vendorPath)
|
||||
if !os.IsNotExist(err) && vendorfolder.Mode().IsDir() {
|
||||
args = append(args, []string{"-e", "FLAG_MOD=vendor"}...)
|
||||
fmt.Printf("Using vendored Go module dependencies\n")
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(locals); i++ {
|
||||
args = append(args, []string{"-v", fmt.Sprintf("%s:%s:ro", locals[i], mounts[i])}...)
|
||||
}
|
||||
args = append(args, []string{"-e", "EXT_GOPATH=" + strings.Join(paths, ":")}...)
|
||||
}
|
||||
|
||||
args = append(args, []string{image, config.Repository}...)
|
||||
return run(exec.Command("docker", args...))
|
||||
}
|
||||
|
||||
// compileContained cross builds a requested package according to the given build
|
||||
// specs using the current system opposed to running in a container. This is meant
|
||||
// to be used for cross compilation already from within an xgo image, allowing the
|
||||
// inheritance and bundling of the root xgo images.
|
||||
func compileContained(config *ConfigFlags, flags *BuildFlags, folder string) error {
|
||||
// If a local build was requested, resolve the import path
|
||||
local := strings.HasPrefix(config.Repository, string(filepath.Separator)) || strings.HasPrefix(config.Repository, ".")
|
||||
if local {
|
||||
config.Repository = resolveImportPath(config.Repository)
|
||||
}
|
||||
// Fine tune the original environment variables with those required by the build script
|
||||
env := []string{
|
||||
"REPO_REMOTE=" + config.Remote,
|
||||
"REPO_BRANCH=" + config.Branch,
|
||||
"PACK=" + config.Package,
|
||||
"DEPS=" + config.Dependencies,
|
||||
"ARGS=" + config.Arguments,
|
||||
"OUT=" + config.Prefix,
|
||||
fmt.Sprintf("FLAG_V=%v", flags.Verbose),
|
||||
fmt.Sprintf("FLAG_X=%v", flags.Steps),
|
||||
fmt.Sprintf("FLAG_RACE=%v", flags.Race),
|
||||
fmt.Sprintf("FLAG_TAGS=%s", flags.Tags),
|
||||
fmt.Sprintf("FLAG_LDFLAGS=%s", flags.LdFlags),
|
||||
fmt.Sprintf("FLAG_BUILDMODE=%s", flags.Mode),
|
||||
"TARGETS=" + strings.Replace(strings.Join(config.Targets, " "), "*", ".", -1),
|
||||
}
|
||||
if local {
|
||||
env = append(env, "EXT_GOPATH=/non-existent-path-to-signal-local-build")
|
||||
}
|
||||
// Assemble and run the local cross compilation command
|
||||
fmt.Printf("Cross compiling %s...\n", config.Repository)
|
||||
|
||||
cmd := exec.Command("/build.sh", config.Repository)
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
|
||||
return run(cmd)
|
||||
}
|
||||
|
||||
// resolveImportPath converts a package given by a relative path to a Go import
|
||||
// path using the local GOPATH environment.
|
||||
func resolveImportPath(path string) string {
|
||||
abs, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to locate requested package: %v.", err)
|
||||
}
|
||||
stat, err := os.Stat(abs)
|
||||
if err != nil || !stat.IsDir() {
|
||||
log.Fatalf("Requested path invalid.")
|
||||
}
|
||||
pack, err := build.ImportDir(abs, build.FindOnly)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to resolve import path: %v.", err)
|
||||
}
|
||||
return pack.ImportPath
|
||||
}
|
||||
|
||||
// Executes a command synchronously, redirecting its output to stdout.
|
||||
func run(cmd *exec.Cmd) error {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
Loading…
Reference in New Issue