mirror of https://github.com/k3s-io/k3s
Vendor godep v79 and use it
parent
a29c048e33
commit
ac4ffb1e6e
|
@ -5,6 +5,7 @@
|
|||
"Packages": [
|
||||
"github.com/onsi/ginkgo/ginkgo",
|
||||
"github.com/jteeuwen/go-bindata/go-bindata",
|
||||
"github.com/tools/godep",
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
|
@ -1922,11 +1923,20 @@
|
|||
"ImportPath": "github.com/kr/fs",
|
||||
"Rev": "2788f0dbd16903de03cb8186e5c7d97b69ad387b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kr/pretty",
|
||||
"Comment": "go.weekly.2011-12-22-24-gf31442d",
|
||||
"Rev": "f31442d60e51465c69811e2107ae978868dbea5c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kr/pty",
|
||||
"Comment": "release.r56-29-gf7ee69f",
|
||||
"Rev": "f7ee69f31298ecbe5d2b349c711e2547a617d398"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kr/text",
|
||||
"Rev": "6807e777504f54ad073ecef66747de158294b639"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/libopenstorage/openstorage/api",
|
||||
"Rev": "093a0c3888753c2056e7373183693d670c6bba01"
|
||||
|
@ -2519,6 +2529,11 @@
|
|||
"ImportPath": "github.com/syndtr/gocapability/capability",
|
||||
"Rev": "e7cb7fa329f456b3855136a2642b197bad7366ba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/tools/godep",
|
||||
"Comment": "v79",
|
||||
"Rev": "f15f6db5da33a4ac48be83e20b9dd838e14f117b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ugorji/go/codec",
|
||||
"Rev": "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74"
|
||||
|
@ -2859,6 +2874,10 @@
|
|||
"ImportPath": "golang.org/x/tools/container/intsets",
|
||||
"Rev": "2382e3994d48b1d22acc2c86bcad0a2aff028e32"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/tools/go/vcs",
|
||||
"Rev": "2382e3994d48b1d22acc2c86bcad0a2aff028e32"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/cloudkms/v1",
|
||||
"Rev": "654f863362977d69086620b5f72f13e911da2410"
|
||||
|
|
|
@ -66824,6 +66824,35 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/kr/pretty licensed under: =
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2012 Keith Rarick
|
||||
|
||||
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.
|
||||
|
||||
= vendor/github.com/kr/pretty/License 9d305c2010c6891ee4f3cd42a562f78f
|
||||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/kr/pty licensed under: =
|
||||
|
||||
|
@ -66855,6 +66884,33 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/kr/text licensed under: =
|
||||
|
||||
Copyright 2012 Keith Rarick
|
||||
|
||||
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.
|
||||
|
||||
= vendor/github.com/kr/text/License 449bfedd81a372635934cf9ce004c0cf
|
||||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/libopenstorage/openstorage/api licensed under: =
|
||||
|
||||
|
@ -79402,6 +79458,42 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/tools/godep licensed under: =
|
||||
|
||||
Copyright © 2013 Keith Rarick.
|
||||
Portions Copyright (c) 2012 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.
|
||||
|
||||
= vendor/github.com/tools/godep/License 71eb66e9b353dd06ca5a81ce0f469e1a
|
||||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/ugorji/go/codec licensed under: =
|
||||
|
||||
|
@ -86286,6 +86378,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/golang.org/x/tools/go/vcs licensed under: =
|
||||
|
||||
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.
|
||||
|
||||
= vendor/golang.org/x/tools/LICENSE 5d4950ecb7b26d2c5e4e7b4e0dd74707
|
||||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/google.golang.org/api/cloudkms/v1 licensed under: =
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ fi
|
|||
REQUIRED_BINS=(
|
||||
"github.com/onsi/ginkgo/ginkgo"
|
||||
"github.com/jteeuwen/go-bindata/go-bindata"
|
||||
"github.com/tools/godep"
|
||||
"./..."
|
||||
)
|
||||
|
||||
|
|
|
@ -521,20 +521,9 @@ kube::util::ensure_godep_version() {
|
|||
return
|
||||
fi
|
||||
|
||||
kube::log::status "Getting godep version ${GODEP_VERSION}"
|
||||
kube::util::ensure-temp-dir
|
||||
mkdir -p "${KUBE_TEMP}/go/src"
|
||||
kube::log::status "Installing godep version ${GODEP_VERSION}"
|
||||
go install ./vendor/github.com/tools/godep/
|
||||
|
||||
GP="$(echo $GOPATH | cut -f1 -d:)"
|
||||
rm -rf "${GP}/src/github.com/tools/godep"
|
||||
go get -d -u github.com/tools/godep
|
||||
pushd "${GP}/src/github.com/tools/godep" >/dev/null
|
||||
git checkout -q "${GODEP_VERSION}"
|
||||
go install .
|
||||
popd >/dev/null
|
||||
|
||||
hash -r # force bash to clear PATH cache
|
||||
PATH="${PATH}:${GP}/bin"
|
||||
if [[ "$(godep version 2>/dev/null)" != *"godep ${GODEP_VERSION}"* ]]; then
|
||||
kube::log::error "Expected godep ${GODEP_VERSION}, got $(godep version)"
|
||||
return 1
|
||||
|
|
|
@ -258,7 +258,9 @@ filegroup(
|
|||
"//vendor/github.com/kardianos/osext:all-srcs",
|
||||
"//vendor/github.com/karlseguin/ccache:all-srcs",
|
||||
"//vendor/github.com/kr/fs:all-srcs",
|
||||
"//vendor/github.com/kr/pretty:all-srcs",
|
||||
"//vendor/github.com/kr/pty:all-srcs",
|
||||
"//vendor/github.com/kr/text:all-srcs",
|
||||
"//vendor/github.com/libopenstorage/openstorage/api:all-srcs",
|
||||
"//vendor/github.com/libopenstorage/openstorage/pkg/parser:all-srcs",
|
||||
"//vendor/github.com/libopenstorage/openstorage/pkg/units:all-srcs",
|
||||
|
@ -323,6 +325,7 @@ filegroup(
|
|||
"//vendor/github.com/stretchr/testify/mock:all-srcs",
|
||||
"//vendor/github.com/stretchr/testify/require:all-srcs",
|
||||
"//vendor/github.com/syndtr/gocapability/capability:all-srcs",
|
||||
"//vendor/github.com/tools/godep:all-srcs",
|
||||
"//vendor/github.com/ugorji/go/codec:all-srcs",
|
||||
"//vendor/github.com/vishvananda/netlink:all-srcs",
|
||||
"//vendor/github.com/vishvananda/netns:all-srcs",
|
||||
|
@ -368,6 +371,7 @@ filegroup(
|
|||
"//vendor/golang.org/x/text/width:all-srcs",
|
||||
"//vendor/golang.org/x/time/rate:all-srcs",
|
||||
"//vendor/golang.org/x/tools/container/intsets:all-srcs",
|
||||
"//vendor/golang.org/x/tools/go/vcs:all-srcs",
|
||||
"//vendor/google.golang.org/api/cloudkms/v1:all-srcs",
|
||||
"//vendor/google.golang.org/api/cloudmonitoring/v2beta2:all-srcs",
|
||||
"//vendor/google.golang.org/api/compute/v0.alpha:all-srcs",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[568].out
|
||||
_go*
|
||||
_test*
|
||||
_obj
|
|
@ -0,0 +1,27 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"diff.go",
|
||||
"formatter.go",
|
||||
"pretty.go",
|
||||
"zero.go",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//vendor/github.com/kr/text:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright 2012 Keith Rarick
|
||||
|
||||
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,9 @@
|
|||
package pretty
|
||||
|
||||
import "github.com/kr/pretty"
|
||||
|
||||
Package pretty provides pretty-printing for Go values.
|
||||
|
||||
Documentation
|
||||
|
||||
http://godoc.org/github.com/kr/pretty
|
|
@ -0,0 +1,158 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type sbuf []string
|
||||
|
||||
func (s *sbuf) Write(b []byte) (int, error) {
|
||||
*s = append(*s, string(b))
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Diff returns a slice where each element describes
|
||||
// a difference between a and b.
|
||||
func Diff(a, b interface{}) (desc []string) {
|
||||
Fdiff((*sbuf)(&desc), a, b)
|
||||
return desc
|
||||
}
|
||||
|
||||
// Fdiff writes to w a description of the differences between a and b.
|
||||
func Fdiff(w io.Writer, a, b interface{}) {
|
||||
diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b))
|
||||
}
|
||||
|
||||
type diffWriter struct {
|
||||
w io.Writer
|
||||
l string // label
|
||||
}
|
||||
|
||||
func (w diffWriter) printf(f string, a ...interface{}) {
|
||||
var l string
|
||||
if w.l != "" {
|
||||
l = w.l + ": "
|
||||
}
|
||||
fmt.Fprintf(w.w, l+f, a...)
|
||||
}
|
||||
|
||||
func (w diffWriter) diff(av, bv reflect.Value) {
|
||||
if !av.IsValid() && bv.IsValid() {
|
||||
w.printf("nil != %#v", bv.Interface())
|
||||
return
|
||||
}
|
||||
if av.IsValid() && !bv.IsValid() {
|
||||
w.printf("%#v != nil", av.Interface())
|
||||
return
|
||||
}
|
||||
if !av.IsValid() && !bv.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
at := av.Type()
|
||||
bt := bv.Type()
|
||||
if at != bt {
|
||||
w.printf("%v != %v", at, bt)
|
||||
return
|
||||
}
|
||||
|
||||
// numeric types, including bool
|
||||
if at.Kind() < reflect.Array {
|
||||
a, b := av.Interface(), bv.Interface()
|
||||
if a != b {
|
||||
w.printf("%#v != %#v", a, b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch at.Kind() {
|
||||
case reflect.String:
|
||||
a, b := av.Interface(), bv.Interface()
|
||||
if a != b {
|
||||
w.printf("%q != %q", a, b)
|
||||
}
|
||||
case reflect.Ptr:
|
||||
switch {
|
||||
case av.IsNil() && !bv.IsNil():
|
||||
w.printf("nil != %v", bv.Interface())
|
||||
case !av.IsNil() && bv.IsNil():
|
||||
w.printf("%v != nil", av.Interface())
|
||||
case !av.IsNil() && !bv.IsNil():
|
||||
w.diff(av.Elem(), bv.Elem())
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < av.NumField(); i++ {
|
||||
w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
|
||||
}
|
||||
case reflect.Slice:
|
||||
lenA := av.Len()
|
||||
lenB := bv.Len()
|
||||
if lenA != lenB {
|
||||
w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB)
|
||||
break
|
||||
}
|
||||
for i := 0; i < lenA; i++ {
|
||||
w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
|
||||
}
|
||||
case reflect.Map:
|
||||
ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
|
||||
for _, k := range ak {
|
||||
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||||
w.printf("%q != (missing)", av.MapIndex(k))
|
||||
}
|
||||
for _, k := range both {
|
||||
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||||
w.diff(av.MapIndex(k), bv.MapIndex(k))
|
||||
}
|
||||
for _, k := range bk {
|
||||
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||||
w.printf("(missing) != %q", bv.MapIndex(k))
|
||||
}
|
||||
case reflect.Interface:
|
||||
w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface()))
|
||||
default:
|
||||
if !reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||||
w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d diffWriter) relabel(name string) (d1 diffWriter) {
|
||||
d1 = d
|
||||
if d.l != "" && name[0] != '[' {
|
||||
d1.l += "."
|
||||
}
|
||||
d1.l += name
|
||||
return d1
|
||||
}
|
||||
|
||||
func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
|
||||
for _, av := range a {
|
||||
inBoth := false
|
||||
for _, bv := range b {
|
||||
if reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||||
inBoth = true
|
||||
both = append(both, av)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inBoth {
|
||||
ak = append(ak, av)
|
||||
}
|
||||
}
|
||||
for _, bv := range b {
|
||||
inBoth := false
|
||||
for _, av := range a {
|
||||
if reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||||
inBoth = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inBoth {
|
||||
bk = append(bk, bv)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/kr/text"
|
||||
)
|
||||
|
||||
const (
|
||||
limit = 50
|
||||
)
|
||||
|
||||
type formatter struct {
|
||||
x interface{}
|
||||
force bool
|
||||
quote bool
|
||||
}
|
||||
|
||||
// Formatter makes a wrapper, f, that will format x as go source with line
|
||||
// breaks and tabs. Object f responds to the "%v" formatting verb when both the
|
||||
// "#" and " " (space) flags are set, for example:
|
||||
//
|
||||
// fmt.Sprintf("%# v", Formatter(x))
|
||||
//
|
||||
// If one of these two flags is not set, or any other verb is used, f will
|
||||
// format x according to the usual rules of package fmt.
|
||||
// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
|
||||
func Formatter(x interface{}) (f fmt.Formatter) {
|
||||
return formatter{x: x, quote: true}
|
||||
}
|
||||
|
||||
func (fo formatter) String() string {
|
||||
return fmt.Sprint(fo.x) // unwrap it
|
||||
}
|
||||
|
||||
func (fo formatter) passThrough(f fmt.State, c rune) {
|
||||
s := "%"
|
||||
for i := 0; i < 128; i++ {
|
||||
if f.Flag(i) {
|
||||
s += string(i)
|
||||
}
|
||||
}
|
||||
if w, ok := f.Width(); ok {
|
||||
s += fmt.Sprintf("%d", w)
|
||||
}
|
||||
if p, ok := f.Precision(); ok {
|
||||
s += fmt.Sprintf(".%d", p)
|
||||
}
|
||||
s += string(c)
|
||||
fmt.Fprintf(f, s, fo.x)
|
||||
}
|
||||
|
||||
func (fo formatter) Format(f fmt.State, c rune) {
|
||||
if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
|
||||
w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
|
||||
p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
|
||||
p.printValue(reflect.ValueOf(fo.x), true, fo.quote)
|
||||
w.Flush()
|
||||
return
|
||||
}
|
||||
fo.passThrough(f, c)
|
||||
}
|
||||
|
||||
type printer struct {
|
||||
io.Writer
|
||||
tw *tabwriter.Writer
|
||||
visited map[visit]int
|
||||
depth int
|
||||
}
|
||||
|
||||
func (p *printer) indent() *printer {
|
||||
q := *p
|
||||
q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0)
|
||||
q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
|
||||
return &q
|
||||
}
|
||||
|
||||
func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
|
||||
if showType {
|
||||
io.WriteString(p, v.Type().String())
|
||||
fmt.Fprintf(p, "(%#v)", x)
|
||||
} else {
|
||||
fmt.Fprintf(p, "%#v", x)
|
||||
}
|
||||
}
|
||||
|
||||
// printValue must keep track of already-printed pointer values to avoid
|
||||
// infinite recursion.
|
||||
type visit struct {
|
||||
v uintptr
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
func (p *printer) printValue(v reflect.Value, showType, quote bool) {
|
||||
if p.depth > 10 {
|
||||
io.WriteString(p, "!%v(DEPTH EXCEEDED)")
|
||||
return
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
p.printInline(v, v.Bool(), showType)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p.printInline(v, v.Int(), showType)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
p.printInline(v, v.Uint(), showType)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
p.printInline(v, v.Float(), showType)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
fmt.Fprintf(p, "%#v", v.Complex())
|
||||
case reflect.String:
|
||||
p.fmtString(v.String(), quote)
|
||||
case reflect.Map:
|
||||
t := v.Type()
|
||||
if showType {
|
||||
io.WriteString(p, t.String())
|
||||
}
|
||||
writeByte(p, '{')
|
||||
if nonzero(v) {
|
||||
expand := !canInline(v.Type())
|
||||
pp := p
|
||||
if expand {
|
||||
writeByte(p, '\n')
|
||||
pp = p.indent()
|
||||
}
|
||||
keys := v.MapKeys()
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
showTypeInStruct := true
|
||||
k := keys[i]
|
||||
mv := v.MapIndex(k)
|
||||
pp.printValue(k, false, true)
|
||||
writeByte(pp, ':')
|
||||
if expand {
|
||||
writeByte(pp, '\t')
|
||||
}
|
||||
showTypeInStruct = t.Elem().Kind() == reflect.Interface
|
||||
pp.printValue(mv, showTypeInStruct, true)
|
||||
if expand {
|
||||
io.WriteString(pp, ",\n")
|
||||
} else if i < v.Len()-1 {
|
||||
io.WriteString(pp, ", ")
|
||||
}
|
||||
}
|
||||
if expand {
|
||||
pp.tw.Flush()
|
||||
}
|
||||
}
|
||||
writeByte(p, '}')
|
||||
case reflect.Struct:
|
||||
t := v.Type()
|
||||
if v.CanAddr() {
|
||||
addr := v.UnsafeAddr()
|
||||
vis := visit{addr, t}
|
||||
if vd, ok := p.visited[vis]; ok && vd < p.depth {
|
||||
p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false)
|
||||
break // don't print v again
|
||||
}
|
||||
p.visited[vis] = p.depth
|
||||
}
|
||||
|
||||
if showType {
|
||||
io.WriteString(p, t.String())
|
||||
}
|
||||
writeByte(p, '{')
|
||||
if nonzero(v) {
|
||||
expand := !canInline(v.Type())
|
||||
pp := p
|
||||
if expand {
|
||||
writeByte(p, '\n')
|
||||
pp = p.indent()
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
showTypeInStruct := true
|
||||
if f := t.Field(i); f.Name != "" {
|
||||
io.WriteString(pp, f.Name)
|
||||
writeByte(pp, ':')
|
||||
if expand {
|
||||
writeByte(pp, '\t')
|
||||
}
|
||||
showTypeInStruct = labelType(f.Type)
|
||||
}
|
||||
pp.printValue(getField(v, i), showTypeInStruct, true)
|
||||
if expand {
|
||||
io.WriteString(pp, ",\n")
|
||||
} else if i < v.NumField()-1 {
|
||||
io.WriteString(pp, ", ")
|
||||
}
|
||||
}
|
||||
if expand {
|
||||
pp.tw.Flush()
|
||||
}
|
||||
}
|
||||
writeByte(p, '}')
|
||||
case reflect.Interface:
|
||||
switch e := v.Elem(); {
|
||||
case e.Kind() == reflect.Invalid:
|
||||
io.WriteString(p, "nil")
|
||||
case e.IsValid():
|
||||
pp := *p
|
||||
pp.depth++
|
||||
pp.printValue(e, showType, true)
|
||||
default:
|
||||
io.WriteString(p, v.Type().String())
|
||||
io.WriteString(p, "(nil)")
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
t := v.Type()
|
||||
if showType {
|
||||
io.WriteString(p, t.String())
|
||||
}
|
||||
if v.Kind() == reflect.Slice && v.IsNil() && showType {
|
||||
io.WriteString(p, "(nil)")
|
||||
break
|
||||
}
|
||||
if v.Kind() == reflect.Slice && v.IsNil() {
|
||||
io.WriteString(p, "nil")
|
||||
break
|
||||
}
|
||||
writeByte(p, '{')
|
||||
expand := !canInline(v.Type())
|
||||
pp := p
|
||||
if expand {
|
||||
writeByte(p, '\n')
|
||||
pp = p.indent()
|
||||
}
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
showTypeInSlice := t.Elem().Kind() == reflect.Interface
|
||||
pp.printValue(v.Index(i), showTypeInSlice, true)
|
||||
if expand {
|
||||
io.WriteString(pp, ",\n")
|
||||
} else if i < v.Len()-1 {
|
||||
io.WriteString(pp, ", ")
|
||||
}
|
||||
}
|
||||
if expand {
|
||||
pp.tw.Flush()
|
||||
}
|
||||
writeByte(p, '}')
|
||||
case reflect.Ptr:
|
||||
e := v.Elem()
|
||||
if !e.IsValid() {
|
||||
writeByte(p, '(')
|
||||
io.WriteString(p, v.Type().String())
|
||||
io.WriteString(p, ")(nil)")
|
||||
} else {
|
||||
pp := *p
|
||||
pp.depth++
|
||||
writeByte(pp, '&')
|
||||
pp.printValue(e, true, true)
|
||||
}
|
||||
case reflect.Chan:
|
||||
x := v.Pointer()
|
||||
if showType {
|
||||
writeByte(p, '(')
|
||||
io.WriteString(p, v.Type().String())
|
||||
fmt.Fprintf(p, ")(%#v)", x)
|
||||
} else {
|
||||
fmt.Fprintf(p, "%#v", x)
|
||||
}
|
||||
case reflect.Func:
|
||||
io.WriteString(p, v.Type().String())
|
||||
io.WriteString(p, " {...}")
|
||||
case reflect.UnsafePointer:
|
||||
p.printInline(v, v.Pointer(), showType)
|
||||
case reflect.Invalid:
|
||||
io.WriteString(p, "nil")
|
||||
}
|
||||
}
|
||||
|
||||
func canInline(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Map:
|
||||
return !canExpand(t.Elem())
|
||||
case reflect.Struct:
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if canExpand(t.Field(i).Type) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Interface:
|
||||
return false
|
||||
case reflect.Array, reflect.Slice:
|
||||
return !canExpand(t.Elem())
|
||||
case reflect.Ptr:
|
||||
return false
|
||||
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func canExpand(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Map, reflect.Struct,
|
||||
reflect.Interface, reflect.Array, reflect.Slice,
|
||||
reflect.Ptr:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func labelType(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Interface, reflect.Struct:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *printer) fmtString(s string, quote bool) {
|
||||
if quote {
|
||||
s = strconv.Quote(s)
|
||||
}
|
||||
io.WriteString(p, s)
|
||||
}
|
||||
|
||||
func tryDeepEqual(a, b interface{}) bool {
|
||||
defer func() { recover() }()
|
||||
return reflect.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
func writeByte(w io.Writer, b byte) {
|
||||
w.Write([]byte{b})
|
||||
}
|
||||
|
||||
func getField(v reflect.Value, i int) reflect.Value {
|
||||
val := v.Field(i)
|
||||
if val.Kind() == reflect.Interface && !val.IsNil() {
|
||||
val = val.Elem()
|
||||
}
|
||||
return val
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
// Package pretty provides pretty-printing for Go values. This is
|
||||
// useful during debugging, to avoid wrapping long output lines in
|
||||
// the terminal.
|
||||
//
|
||||
// It provides a function, Formatter, that can be used with any
|
||||
// function that accepts a format string. It also provides
|
||||
// convenience wrappers for functions in packages fmt and log.
|
||||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Errorf is a convenience wrapper for fmt.Errorf.
|
||||
//
|
||||
// Calling Errorf(f, x, y) is equivalent to
|
||||
// fmt.Errorf(f, Formatter(x), Formatter(y)).
|
||||
func Errorf(format string, a ...interface{}) error {
|
||||
return fmt.Errorf(format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
// Fprintf is a convenience wrapper for fmt.Fprintf.
|
||||
//
|
||||
// Calling Fprintf(w, f, x, y) is equivalent to
|
||||
// fmt.Fprintf(w, f, Formatter(x), Formatter(y)).
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) {
|
||||
return fmt.Fprintf(w, format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
// Log is a convenience wrapper for log.Printf.
|
||||
//
|
||||
// Calling Log(x, y) is equivalent to
|
||||
// log.Print(Formatter(x), Formatter(y)), but each operand is
|
||||
// formatted with "%# v".
|
||||
func Log(a ...interface{}) {
|
||||
log.Print(wrap(a, true)...)
|
||||
}
|
||||
|
||||
// Logf is a convenience wrapper for log.Printf.
|
||||
//
|
||||
// Calling Logf(f, x, y) is equivalent to
|
||||
// log.Printf(f, Formatter(x), Formatter(y)).
|
||||
func Logf(format string, a ...interface{}) {
|
||||
log.Printf(format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
// Logln is a convenience wrapper for log.Printf.
|
||||
//
|
||||
// Calling Logln(x, y) is equivalent to
|
||||
// log.Println(Formatter(x), Formatter(y)), but each operand is
|
||||
// formatted with "%# v".
|
||||
func Logln(a ...interface{}) {
|
||||
log.Println(wrap(a, true)...)
|
||||
}
|
||||
|
||||
// Print pretty-prints its operands and writes to standard output.
|
||||
//
|
||||
// Calling Print(x, y) is equivalent to
|
||||
// fmt.Print(Formatter(x), Formatter(y)), but each operand is
|
||||
// formatted with "%# v".
|
||||
func Print(a ...interface{}) (n int, errno error) {
|
||||
return fmt.Print(wrap(a, true)...)
|
||||
}
|
||||
|
||||
// Printf is a convenience wrapper for fmt.Printf.
|
||||
//
|
||||
// Calling Printf(f, x, y) is equivalent to
|
||||
// fmt.Printf(f, Formatter(x), Formatter(y)).
|
||||
func Printf(format string, a ...interface{}) (n int, errno error) {
|
||||
return fmt.Printf(format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
// Println pretty-prints its operands and writes to standard output.
|
||||
//
|
||||
// Calling Print(x, y) is equivalent to
|
||||
// fmt.Println(Formatter(x), Formatter(y)), but each operand is
|
||||
// formatted with "%# v".
|
||||
func Println(a ...interface{}) (n int, errno error) {
|
||||
return fmt.Println(wrap(a, true)...)
|
||||
}
|
||||
|
||||
// Sprintf is a convenience wrapper for fmt.Sprintf.
|
||||
//
|
||||
// Calling Sprintf(f, x, y) is equivalent to
|
||||
// fmt.Sprintf(f, Formatter(x), Formatter(y)).
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
func wrap(a []interface{}, force bool) []interface{} {
|
||||
w := make([]interface{}, len(a))
|
||||
for i, x := range a {
|
||||
w[i] = formatter{x: x, force: force}
|
||||
}
|
||||
return w
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func nonzero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() != 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() != 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() != 0
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return v.Complex() != complex(0, 0)
|
||||
case reflect.String:
|
||||
return v.String() != ""
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if nonzero(getField(v, i)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Array:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if nonzero(v.Index(i)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func:
|
||||
return !v.IsNil()
|
||||
case reflect.UnsafePointer:
|
||||
return v.Pointer() != 0
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"indent.go",
|
||||
"wrap.go",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
Copyright 2012 Keith Rarick
|
||||
|
||||
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,3 @@
|
|||
This is a Go package for manipulating paragraphs of text.
|
||||
|
||||
See http://go.pkgdoc.org/github.com/kr/text for full documentation.
|
|
@ -0,0 +1,3 @@
|
|||
// Package text provides rudimentary functions for manipulating text in
|
||||
// paragraphs.
|
||||
package text
|
|
@ -0,0 +1,74 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Indent inserts prefix at the beginning of each non-empty line of s. The
|
||||
// end-of-line marker is NL.
|
||||
func Indent(s, prefix string) string {
|
||||
return string(IndentBytes([]byte(s), []byte(prefix)))
|
||||
}
|
||||
|
||||
// IndentBytes inserts prefix at the beginning of each non-empty line of b.
|
||||
// The end-of-line marker is NL.
|
||||
func IndentBytes(b, prefix []byte) []byte {
|
||||
var res []byte
|
||||
bol := true
|
||||
for _, c := range b {
|
||||
if bol && c != '\n' {
|
||||
res = append(res, prefix...)
|
||||
}
|
||||
res = append(res, c)
|
||||
bol = c == '\n'
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Writer indents each line of its input.
|
||||
type indentWriter struct {
|
||||
w io.Writer
|
||||
bol bool
|
||||
pre [][]byte
|
||||
sel int
|
||||
off int
|
||||
}
|
||||
|
||||
// NewIndentWriter makes a new write filter that indents the input
|
||||
// lines. Each line is prefixed in order with the corresponding
|
||||
// element of pre. If there are more lines than elements, the last
|
||||
// element of pre is repeated for each subsequent line.
|
||||
func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer {
|
||||
return &indentWriter{
|
||||
w: w,
|
||||
pre: pre,
|
||||
bol: true,
|
||||
}
|
||||
}
|
||||
|
||||
// The only errors returned are from the underlying indentWriter.
|
||||
func (w *indentWriter) Write(p []byte) (n int, err error) {
|
||||
for _, c := range p {
|
||||
if w.bol {
|
||||
var i int
|
||||
i, err = w.w.Write(w.pre[w.sel][w.off:])
|
||||
w.off += i
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
_, err = w.w.Write([]byte{c})
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
w.bol = c == '\n'
|
||||
if w.bol {
|
||||
w.off = 0
|
||||
if w.sel < len(w.pre)-1 {
|
||||
w.sel++
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
)
|
||||
|
||||
var (
|
||||
nl = []byte{'\n'}
|
||||
sp = []byte{' '}
|
||||
)
|
||||
|
||||
const defaultPenalty = 1e5
|
||||
|
||||
// Wrap wraps s into a paragraph of lines of length lim, with minimal
|
||||
// raggedness.
|
||||
func Wrap(s string, lim int) string {
|
||||
return string(WrapBytes([]byte(s), lim))
|
||||
}
|
||||
|
||||
// WrapBytes wraps b into a paragraph of lines of length lim, with minimal
|
||||
// raggedness.
|
||||
func WrapBytes(b []byte, lim int) []byte {
|
||||
words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
|
||||
var lines [][]byte
|
||||
for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
|
||||
lines = append(lines, bytes.Join(line, sp))
|
||||
}
|
||||
return bytes.Join(lines, nl)
|
||||
}
|
||||
|
||||
// WrapWords is the low-level line-breaking algorithm, useful if you need more
|
||||
// control over the details of the text wrapping process. For most uses, either
|
||||
// Wrap or WrapBytes will be sufficient and more convenient.
|
||||
//
|
||||
// WrapWords splits a list of words into lines with minimal "raggedness",
|
||||
// treating each byte as one unit, accounting for spc units between adjacent
|
||||
// words on each line, and attempting to limit lines to lim units. Raggedness
|
||||
// is the total error over all lines, where error is the square of the
|
||||
// difference of the length of the line and lim. Too-long lines (which only
|
||||
// happen when a single word is longer than lim units) have pen penalty units
|
||||
// added to the error.
|
||||
func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
|
||||
n := len(words)
|
||||
|
||||
length := make([][]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
length[i] = make([]int, n)
|
||||
length[i][i] = len(words[i])
|
||||
for j := i + 1; j < n; j++ {
|
||||
length[i][j] = length[i][j-1] + spc + len(words[j])
|
||||
}
|
||||
}
|
||||
|
||||
nbrk := make([]int, n)
|
||||
cost := make([]int, n)
|
||||
for i := range cost {
|
||||
cost[i] = math.MaxInt32
|
||||
}
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
if length[i][n-1] <= lim {
|
||||
cost[i] = 0
|
||||
nbrk[i] = n
|
||||
} else {
|
||||
for j := i + 1; j < n; j++ {
|
||||
d := lim - length[i][j-1]
|
||||
c := d*d + cost[j]
|
||||
if length[i][j-1] > lim {
|
||||
c += pen // too-long lines get a worse penalty
|
||||
}
|
||||
if c < cost[i] {
|
||||
cost[i] = c
|
||||
nbrk[i] = j
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var lines [][][]byte
|
||||
i := 0
|
||||
for i < n {
|
||||
lines = append(lines, words[i:nbrk[i]])
|
||||
i = nbrk[i]
|
||||
}
|
||||
return lines
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/godep
|
|
@ -0,0 +1,34 @@
|
|||
language: go
|
||||
sudo: false
|
||||
go: 1.6
|
||||
script:
|
||||
# Godep's unit tests run git, and git complains
|
||||
# if we don't set these config parameters.
|
||||
# We put dummy values here because they don't matter.
|
||||
- git config --global user.email "you@example.com"
|
||||
- git config --global user.name "Your Name"
|
||||
- test -z "$(go fmt)"
|
||||
- go vet
|
||||
- go test -v
|
||||
- go test -v -race
|
||||
- test -z "$(goimports -l .)"
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
before_deploy:
|
||||
- export OS_TARGETS="linux darwin windows"
|
||||
- export ARCH_TARGETS="386 amd64"
|
||||
- go get github.com/mitchellh/gox
|
||||
- gox -os "$OS_TARGETS" -arch="$ARCH_TARGETS"
|
||||
deploy:
|
||||
skip_cleanup: true
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: Q1JP8LziaXMTxFmNXiyC1YhS9e4M4WnI6UDjRTMf6mm1LZeJyUFOCCtXnifL7RyCIR1hpjp6s8M1aWE+NpuweF96IZI3Uk4ASx5C8FePC4qvhsCdtJ2sLD2GTIrp9b0MS9/+ao20AIbpVDSaLaF9IjqXpMxMyM0P8P5coRTkwItlGxmQbVJW3YuiYcPa8UojwM4EyafO2CIoUKapW8lwb9KcimBJV8PfF/XZjPVhMkn2ABhh5Hqbn2zBJtvPYMMzi0CnY50JQF5LwN3vGTMpTsRP+lOLCNbOWfkl+2hgG7VpKrtx+cX62knOodpF457sIJ31KUzmeLUVBejTGb1zuVeTojuyi8Huo8YBIBCcN+p3Dqd+n2ZK45mIrheGiEJIkf/vI4MI6A01Nu/o+xU0IPsVfAL/xU5j5nntEGfFWVoclPrl9qcfqf74xdRcARzcCJVmdc8iw49DBDHJfnPa3zxzVz//00+Rz6mZXmhk+Npk/HLLNW59vmJIjP+8XOtPor7dST9HrS1a9AcnmIjNuw9yfbwK5769SDVxCKgqNwXW/Dy5F39aIH5AL4I4y9hCEeeT8ctvSJHGOyiB9MWU5jnt5tluPtz5opG51tFXnIYP/XaWpTfO+eJ6x55pbwT+n3LfRS5l1POM+jGAFF1MFWwc14RY7qynEIEzm4Wb/UE=
|
||||
file:
|
||||
- godep_darwin_amd64
|
||||
- godep_linux_amd64
|
||||
- godep_windows_386.exe
|
||||
- godep_windows_amd64.exe
|
||||
on:
|
||||
tags: true
|
||||
repo: tools/godep
|
|
@ -0,0 +1,54 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"dep.go",
|
||||
"diff.go",
|
||||
"doc.go",
|
||||
"errors.go",
|
||||
"get.go",
|
||||
"go.go",
|
||||
"godepfile.go",
|
||||
"license.go",
|
||||
"list.go",
|
||||
"main.go",
|
||||
"msg.go",
|
||||
"path.go",
|
||||
"pkg.go",
|
||||
"restore.go",
|
||||
"rewrite.go",
|
||||
"save.go",
|
||||
"update.go",
|
||||
"util.go",
|
||||
"vcs.go",
|
||||
"version.go",
|
||||
],
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
"//vendor/github.com/kr/fs:go_default_library",
|
||||
"//vendor/github.com/kr/pretty:go_default_library",
|
||||
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
|
||||
"//vendor/golang.org/x/tools/go/vcs:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "godep",
|
||||
library = ":go_default_library",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
|
@ -0,0 +1,408 @@
|
|||
#v79 (2017/02/01)
|
||||
|
||||
* Fixes #531: fullPackageInDir didn't capture the error from fillPackage()
|
||||
|
||||
#v78 (2017/01/19)
|
||||
|
||||
* Don't use build.ImportDir when discovering packages for the package spec. Fixes #529
|
||||
|
||||
#v77 (2017/01/13)
|
||||
|
||||
* Don't include quotes around hg revisions
|
||||
|
||||
#v76 (2017/01/10)
|
||||
|
||||
* Default to vendor being on unless older go versions.
|
||||
|
||||
#v75 (2016/11/02)
|
||||
|
||||
* Add "AUTHORS" and "CONTRIBUTORS" to legal files list: https://github.com/tools/godep/pull/522
|
||||
|
||||
#v74 (2016/06/01)
|
||||
|
||||
* Enable vendor/ on go1.7
|
||||
* No longer use a godep workspace, use vendor/ (yay!)
|
||||
* Notify that support for Godep workspaces will be removed once go1.8 ships
|
||||
|
||||
#v73 (2016/05/31)
|
||||
|
||||
* Fix permission changes on Windows via @alexbrand. Closes #481.
|
||||
|
||||
#v72 (2016/05/27)
|
||||
|
||||
* Improve handling of git remote show origin. Should help in cases where remote HEAD is ambiguous.
|
||||
* Add ISSUE_TEMPLATE
|
||||
|
||||
#v71 (2016/05/24)
|
||||
|
||||
* Preserve permissions on copied files.
|
||||
|
||||
#v70 (2016/05/20)
|
||||
|
||||
* Fix the May changelog dates
|
||||
* No need to call build.Import, we already have the root of the dependency. Fixes an additional comment on #365
|
||||
|
||||
#v69 (2016/05/16)
|
||||
|
||||
* Make sure `devel-<short sha>` enabled `vendor/` unless there is a classic Godep _workspace already.
|
||||
|
||||
#v68 (2016/05/16)
|
||||
|
||||
* `devel-<short sha>` is always considered newer than any released go version
|
||||
|
||||
#v67 (2016/05/13)
|
||||
|
||||
* Attempt to handle missing deps a little better.
|
||||
|
||||
#v66 (2016/05/10)
|
||||
|
||||
* Use `git remote show origin` to find the default branch when restoring a git based package repository that is in detached head state
|
||||
|
||||
#v65 (2016/05/09)
|
||||
|
||||
* Rewrite update so that it considers new transitive dependencies, both in the same repo and outside of it.
|
||||
|
||||
#v64 (2016/05/09)
|
||||
|
||||
* godep update golang.org/x/tools/go/vcs
|
||||
|
||||
#v63 (2016/05/03)
|
||||
|
||||
* Support recording devel-<short sha> so development versions of Go can be matched
|
||||
|
||||
#v62 (2016/04/07)
|
||||
|
||||
* Note new go1.6+ behavior of not checking out master in README / restore help text.
|
||||
|
||||
#v61 (2016/04/06)
|
||||
|
||||
* Obey go version build tags based on recorded major go version. Fixes #448.
|
||||
|
||||
#v60 (2016/03/18)
|
||||
|
||||
* Make the $GOPATH check a warning.
|
||||
|
||||
#v59 (2016/03/18)
|
||||
|
||||
* Enforce requirement to be inside of a go src directory. A lot of time is usually spent
|
||||
tracking down bug reports where people are doign stuff from outside of their $GOPATH. This
|
||||
should help with that, at least until there it time to properly test godep use outside of a
|
||||
$GOPATH and fix the issues.
|
||||
|
||||
#v58 (2016/03/15)
|
||||
|
||||
* Add GodepVersion to Godeps.json file so that as godep changes / adds features / fixes bugs we can know which version of godep most recently wrote out the file.
|
||||
|
||||
#v57 (2016/03/07)
|
||||
|
||||
* Don't use `git rev-parse --show-toplevel` to determine git repo roots as it resolves symlinks: https://github.com/tools/godep/pull/418
|
||||
|
||||
# v56 (2016/02/26)
|
||||
|
||||
* replace path comparisons with case insensitive pathEqual()
|
||||
* add versionString() to debug output
|
||||
* Send log output to Stderr
|
||||
|
||||
# v55 2016/02/22
|
||||
|
||||
* re-saved deps to clean out extra stuff (see v54; godep restore; godep save -r=false; rm -rf Godeps; godep save -r). We're still using a workspace with rewrites so users of older go version can still go get this tool.
|
||||
* Replace simple == with strings.EqualFold in listFiles to avoid problems with case insensitive filesystems ("Code" != "code" when doing a byte by byte comparison)
|
||||
|
||||
# v54 2016/02/22
|
||||
|
||||
* Update some docs around vendor/
|
||||
* More precise recording of dependencies. Removed recursive copying of sub directories of a package (precise vendoring). This should allow using `./...` with the go tool for compilation of project using `vendor/`. See https://github.com/tools/godep/pull/415
|
||||
|
||||
# v53 2016/02/11
|
||||
|
||||
* Disable VendorExperiment if a godep workspace already exists.
|
||||
|
||||
# v52 2016/01/27
|
||||
|
||||
* Trim 'rc' out of go version strings when determining major version.
|
||||
|
||||
# v51 2016/01/21
|
||||
|
||||
* Trim 'beta' out of go version strings when determining major version.
|
||||
|
||||
# v50 2016/01/19
|
||||
|
||||
* More verbose output on save -v.
|
||||
|
||||
# v49 2016/01/13
|
||||
|
||||
* Add UK spelling license/licence to the pile + fix up a bunch of typos
|
||||
* Clarify tag handling in docs
|
||||
|
||||
# v48 2016/01/13
|
||||
|
||||
* Abort restore if there is no $GOPATH set.
|
||||
|
||||
# v47 2016/01/12
|
||||
|
||||
* Dev versions of go should honor the current meaning of GO15VENDOREXPERIMENT
|
||||
|
||||
# v46 2016/01/03
|
||||
|
||||
* Record "devel" when the release is a devel release of go (compiled from git).
|
||||
|
||||
# v45 2015/12/28
|
||||
|
||||
* Upcase windows drive letters before comparing. Fixes #383.
|
||||
|
||||
# v44 2015/12/23
|
||||
|
||||
* Clean package roots when attempting to find a vendor directory so we don't loop forever.
|
||||
* Fixes 382
|
||||
|
||||
# v43 2015/12/22
|
||||
|
||||
* Better error messages when parsing Godeps.json: Fixes #372
|
||||
|
||||
# v42 2015/12/22
|
||||
|
||||
* Fix a bunch of GO15VENDOREXPERIMENT issues
|
||||
* Find package directories better. Previously we used build.FindOnly which didn't work the way I expected it to (any dir would work w/o error).
|
||||
* Set the VendorExperiment bool based on go version as 1.6 defaults to on.
|
||||
* A bunch of extra debugging for use while sanity checking myself.
|
||||
* vendor flag for test structs.
|
||||
* Some tests for vendor/ stuff:
|
||||
* Basic Test
|
||||
* Transitive
|
||||
* Transitive, across GOPATHs + collapse vendor/ directories.
|
||||
* Should Fix #358
|
||||
|
||||
# v41 2015/12/17
|
||||
|
||||
* Don't rewrite packages outside of the project. This would happen if you specified
|
||||
an external package for vendoring when you ran `goodep save -r ./... github.com/some/other/package`
|
||||
|
||||
# v40 2015/12/17
|
||||
|
||||
* When downloading a dependency, create the base directory if needed.
|
||||
|
||||
# v39 2015/12/16
|
||||
|
||||
* Record only the major go version (ex. go1.5) instead of the complete string.
|
||||
|
||||
# v38 2015/12/16
|
||||
|
||||
* Replace `go get`, further fix up restore error handling/reporting.
|
||||
* Fixes #186
|
||||
* Don't bother restoring/downloading if already done.
|
||||
|
||||
# v37 2015/12/15
|
||||
|
||||
* Change up how download/restore works a little
|
||||
* Try to load the package after downloading/restoring. Previously
|
||||
that was done too early in the process.
|
||||
* make previous verbose output debug output
|
||||
* report a typed error instead of a string from listPackage so it can
|
||||
be asserted to provide a nicer error.
|
||||
* Catch go get errors that say there are no go files found. See code
|
||||
comment as to why.
|
||||
* do *all* downloading during download phase.
|
||||
|
||||
# v36 2015/12/14
|
||||
|
||||
* Fixes #358: Using wrong variable. Will add test after release.
|
||||
|
||||
# v35 2015/12/11
|
||||
|
||||
* Fixes #356: Major performance regressions in v34
|
||||
* Enable cpu profiling via flag on save.
|
||||
* Cache packages by dir
|
||||
* Don't do a full import pass on deps for packages in the GOROOT
|
||||
* create a bit less garbage at times
|
||||
* Generalize -v & -d flags
|
||||
|
||||
# v34 2015/12/08
|
||||
|
||||
* We now use build.Context to help locate packages only and do our own parsing (via go/ast).
|
||||
* Fixes reported issues caused by v33 (Removal of `go list`):
|
||||
* #345: Bug in godep restore
|
||||
* #346: Fix loading a dot package
|
||||
* #348: Godep save issue when importing lib/pq
|
||||
* #350: undefined: build.MultiplePackageError
|
||||
* #351: stow away helper files
|
||||
* #353: cannot find package "appengine"
|
||||
* Don't process imports of `.go` files tagged with the `appengine` build tag.
|
||||
|
||||
# v33 2015/12/07
|
||||
|
||||
* Replace the use of `go list`. This is a large change although all existing tests pass.
|
||||
* Don't process the imports of `.go` files with the `ignore` build tag.
|
||||
|
||||
# v32 2015/12/02
|
||||
|
||||
* Eval Symlinks in Contains() check.
|
||||
|
||||
# v31 2015/12/02
|
||||
|
||||
* In restore, mention which package had the problem -- @shurcool
|
||||
|
||||
# v30 2015/11/25
|
||||
|
||||
* Add `-t` flag to the `godep get` command.
|
||||
|
||||
# v29 2015/11/17
|
||||
|
||||
* Temp work around to fix issue with LICENSE files.
|
||||
|
||||
# v28 2015/11/09
|
||||
|
||||
* Make `version` an actual command.
|
||||
|
||||
# v27 2015/11/06
|
||||
|
||||
* run command once during restore -v
|
||||
|
||||
# v26 2015/11/05
|
||||
|
||||
* Better fix for the issue fixed in v25: All update paths are now path.Clean()'d
|
||||
|
||||
# v25 2015/11/05
|
||||
|
||||
* `godep update package/` == `godep update package`. Fixes #313
|
||||
|
||||
# v24 2015/11/05
|
||||
|
||||
* Honor -t during update. Fixes #312
|
||||
|
||||
# v23 2015/11/05
|
||||
|
||||
* Do not use --debug to find full revision name for mercurial repositories
|
||||
|
||||
# v22 2015/11/14
|
||||
|
||||
* s/GOVENDOREXPERIMENT/GO15VENDOREXPERIMENT :-(
|
||||
|
||||
# v21 2015/11/13
|
||||
|
||||
* Fix #310: Case insensitive fs issue
|
||||
|
||||
# v20 2015/11/13
|
||||
|
||||
* Attempt to include license files when vendoring. (@client9)
|
||||
|
||||
# v19 2015/11/3
|
||||
|
||||
* Fix conflict error message. Revisions were swapped. Also better selection of package that needs update.
|
||||
|
||||
# v18 2015/10/16
|
||||
|
||||
* Improve error message when trying to save a conflicting revision.
|
||||
|
||||
# v17 2015/10/15
|
||||
|
||||
* Fix for v16 bug. All vcs list commands now produce paths relative to the root of the vcs.
|
||||
|
||||
# v16 2015/10/15
|
||||
|
||||
* Determine repo root using vcs commands and use that instead of dep.dir
|
||||
|
||||
# v15 2015/10/14
|
||||
|
||||
* Update .travis.yml file to do releases to github
|
||||
|
||||
# v14 2015/10/08
|
||||
|
||||
* Don't print out a workspace path when GO15VENDOREXPERIMENT is active. The vendor/ directory is not a valid workspace, so can't be added to your $GOPATH.
|
||||
|
||||
# v13 2015/10/07
|
||||
|
||||
* Do restores in 2 separate steps, first download all deps and then check out the recorded revisions.
|
||||
* Update Changelog date format
|
||||
|
||||
# v12 2015/09/22
|
||||
|
||||
* Extract errors into separate file.
|
||||
|
||||
# v11 2015/08/22
|
||||
|
||||
* Amend code to pass golint.
|
||||
|
||||
# v10 2015/09/21
|
||||
|
||||
* Analyse vendored package test dependencies.
|
||||
* Update documentation.
|
||||
|
||||
# v9 2015/09/17
|
||||
|
||||
* Don't save test dependencies by default.
|
||||
|
||||
# v8 2015/09/17
|
||||
|
||||
* Reorganize code.
|
||||
|
||||
# v7 2015/09/09
|
||||
|
||||
* Add verbose flag.
|
||||
* Skip untracked files.
|
||||
* Add VCS list command.
|
||||
|
||||
# v6 2015/09/04
|
||||
|
||||
* Revert ignoring testdata directories and instead ignore it while
|
||||
processing Go files and copy the whole directory unmodified.
|
||||
|
||||
# v5 2015/09/04
|
||||
|
||||
* Fix vcs selection in restore command to work as go get does
|
||||
|
||||
# v4 2015/09/03
|
||||
|
||||
* Remove the deprecated copy option.
|
||||
|
||||
# v3 2015/08/26
|
||||
|
||||
* Ignore testdata directories
|
||||
|
||||
# v2 2015/08/11
|
||||
|
||||
* Include command line packages in the set to copy
|
||||
|
||||
This is a simplification to how we define the behavior
|
||||
of the save command. Now it has two distinct package
|
||||
parameters, the "root set" and the "destination", and
|
||||
they have clearer roles. The packages listed on the
|
||||
command line form the root set; they and all their
|
||||
dependencies will be copied into the Godeps directory.
|
||||
Additionally, the destination (always ".") will form the
|
||||
initial list of "seen" import paths to exclude from
|
||||
copying.
|
||||
|
||||
In the common case, the root set is equal to the
|
||||
destination, so the effective behavior doesn't change.
|
||||
This is primarily just a simpler definition. However, if
|
||||
the user specifies a package on the command line that
|
||||
lives outside of . then that package will be copied.
|
||||
|
||||
As a side effect, there's a simplification to the way we
|
||||
add packages to the initial "seen" set. Formerly, to
|
||||
avoid copying dependencies unnecessarily, we would try
|
||||
to find the root of the VCS repo for each package in the
|
||||
root set, and mark the import path of the entire repo as
|
||||
seen. This meant for a repo at path C, if destination
|
||||
C/S imports C/T, we would not copy C/T into C/S/Godeps.
|
||||
Now we don't treat the repo root specially, and as
|
||||
mentioned above, the destination alone is considered
|
||||
seen.
|
||||
|
||||
This also means we don't require listed packages to be
|
||||
in VCS unless they're outside of the destination.
|
||||
|
||||
# v1 2015/07/20
|
||||
|
||||
* godep version command
|
||||
|
||||
Output the version as well as some godep runtime information that is
|
||||
useful for debugging user's issues.
|
||||
|
||||
The version const would be bumped each time a PR is merged into master
|
||||
to ensure that we'll be able to tell which version someone got when they
|
||||
did a `go get github.com/tools/godep`.
|
||||
|
||||
# Older changes
|
||||
|
||||
Many and more, see `git log -p`
|
|
@ -0,0 +1,22 @@
|
|||
## Why do I need to check in `vendor/`?
|
||||
|
||||
godep's primary concern is to allow you to repeatably build your project. Your
|
||||
dependencies are part of that project. Without them it won't build. Not
|
||||
committing `vendor/` adds additional external dependencies that are outside of
|
||||
your control. In Go, fetching packages is tied to multiple external systems
|
||||
(DNS, web servers, etc). Over time other developers or code hosting sites may
|
||||
discontinue service, delete code, force push, or take any number of other
|
||||
actions that may make a package unreachable. Therefore it's the opinion of the
|
||||
godep authors that `vendor/` should always be checked in.
|
||||
|
||||
## Should I use `godep restore`?
|
||||
|
||||
Probably not, unless you **need** to. Situations where you would **need** to are:
|
||||
|
||||
1. Using older Godep Workspaces (`Godeps/_workspace`) and not using `godep go
|
||||
<cmd>`.
|
||||
1. Resetting the state of $GOPATH to what is in your `Godeps.json` file in order
|
||||
to cleanly re-vendor everything w/o upgrading/changing any deps. This is
|
||||
useful when [migrating](https://github.com/tools/godep#migrating-to-vendor)
|
||||
from workspaces to `vendor` or when a bug is fixed in `godep` that cleans up
|
||||
a previous vendoring error.
|
|
@ -0,0 +1,28 @@
|
|||
Copyright © 2013 Keith Rarick.
|
||||
Portions Copyright (c) 2012 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,199 @@
|
|||
## Godep
|
||||
|
||||
[![Build Status](https://travis-ci.org/tools/godep.svg)](https://travis-ci.org/tools/godep)
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/tools/godep?status.svg)](https://godoc.org/github.com/tools/godep)
|
||||
|
||||
godep helps build packages reproducibly by fixing their dependencies.
|
||||
|
||||
This tool assumes you are working in a standard Go workspace, as described in
|
||||
http://golang.org/doc/code.html. We expect godep to build on Go 1.4* or newer,
|
||||
but you can use it on any project that works with Go 1 or newer.
|
||||
|
||||
Please check the [FAQ](FAQ.md) if you have a question.
|
||||
|
||||
## Install
|
||||
|
||||
```console
|
||||
$ go get github.com/tools/godep
|
||||
```
|
||||
|
||||
## How to use godep with a new project
|
||||
|
||||
Assuming you've got everything working already, so you can build your project
|
||||
with `go install` and test it with `go test`, it's one command to start using:
|
||||
|
||||
```console
|
||||
$ godep save
|
||||
```
|
||||
|
||||
This will save a list of dependencies to the file `Godeps/Godeps.json` and copy
|
||||
their source code into `vendor/` (or `Godeps/_workspace/` when using older
|
||||
versions of Go). Godep does **not copy**:
|
||||
|
||||
- files from source repositories that are not tracked in version control.
|
||||
- `*_test.go` files.
|
||||
- `testdata` directories.
|
||||
- files outside of the go packages.
|
||||
|
||||
Godep does not process the imports of `.go` files with either the `ignore`
|
||||
or `appengine` build tags.
|
||||
|
||||
Test files and testdata directories can be saved by adding `-t`.
|
||||
|
||||
Read over the contents of `vendor/` and make sure it looks reasonable. Then
|
||||
commit the `Godeps/` and `vendor/` directories to version control.
|
||||
|
||||
## The deprecated `-r` flag
|
||||
|
||||
For older versions of Go, the `-r` flag tells save to automatically rewrite
|
||||
package import paths. This allows your code to refer directly to the copied
|
||||
dependencies in `Godeps/_workspace`. So, a package C that depends on package
|
||||
D will actually import `C/Godeps/_workspace/src/D`. This makes C's repo
|
||||
self-contained and causes `go get` to build C with the right version of all
|
||||
dependencies.
|
||||
|
||||
If you don't use `-r`, when using older version of Go, then in order to use the
|
||||
fixed dependencies and get reproducible builds, you must make sure that **every
|
||||
time** you run a Go-related command, you wrap it in one of these two ways:
|
||||
|
||||
- If the command you are running is just `go`, run it as `godep go ...`, e.g.
|
||||
`godep go install -v ./...`
|
||||
- When using a different command, set your `$GOPATH` using `godep path` as
|
||||
described below.
|
||||
|
||||
`-r` isn't necessary with go1.6+ and isn't allowed.
|
||||
|
||||
|
||||
## Additional Operations
|
||||
|
||||
### Restore
|
||||
|
||||
The `godep restore` installs the
|
||||
package versions specified in `Godeps/Godeps.json` to your `$GOPATH`. This
|
||||
modifies the state of packages in your `$GOPATH`. NOTE: `godep restore` leaves
|
||||
git repositories in a detached state. `go1.6`+ no longer checks out the master
|
||||
branch when doing a `go get`, see [here](https://github.com/golang/go/commit/42206598671a44111c8f726ad33dc7b265bdf669).
|
||||
|
||||
Please see the [FAQ](https://github.com/tools/godep/blob/master/FAQ.md#should-i-use-godep-restore) section about restore.
|
||||
|
||||
### Edit-test Cycle
|
||||
|
||||
1. Edit code
|
||||
1. Run `godep go test`
|
||||
1. (repeat)
|
||||
|
||||
### Add a Dependency
|
||||
|
||||
To add a new package foo/bar, do this:
|
||||
|
||||
1. Run `go get foo/bar`
|
||||
1. Edit your code to import foo/bar.
|
||||
1. Run `godep save` (or `godep save ./...`).
|
||||
|
||||
### Update a Dependency
|
||||
|
||||
To update a package from your `$GOPATH`, do this:
|
||||
|
||||
1. Run `go get -u foo/bar`
|
||||
1. Run `godep update foo/bar`. (You can use the `...` wildcard, for example
|
||||
`godep update foo/...`).
|
||||
|
||||
Before comitting the change, you'll probably want to inspect the changes to
|
||||
Godeps, for example with `git diff`, and make sure it looks reasonable.
|
||||
|
||||
## Multiple Packages
|
||||
|
||||
If your repository has more than one package, you're probably accustomed to
|
||||
running commands like `go test ./...`, `go install ./...`, and `go fmt ./...`.
|
||||
Similarly, you should run `godep save ./...` to capture the dependencies of all
|
||||
packages in your application.
|
||||
|
||||
|
||||
## File Format
|
||||
|
||||
Godeps is a json file with the following structure:
|
||||
|
||||
```go
|
||||
type Godeps struct {
|
||||
ImportPath string
|
||||
GoVersion string // Abridged output of 'go version'.
|
||||
GodepVersion string // Abridged output of 'godep version'
|
||||
Packages []string // Arguments to godep save, if any.
|
||||
Deps []struct {
|
||||
ImportPath string
|
||||
Comment string // Description of commit, if present.
|
||||
Rev string // VCS-specific commit ID.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example Godeps:
|
||||
|
||||
```json
|
||||
{
|
||||
"ImportPath": "github.com/kr/hk",
|
||||
"GoVersion": "go1.6",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "code.google.com/p/go-netrc/netrc",
|
||||
"Rev": "28676070ab99"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kr/binarydist",
|
||||
"Rev": "3380ade90f8b0dfa3e363fd7d7e941fa857d0d13"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Migrating to vendor/
|
||||
|
||||
Godep supports the Go 1.5+ vendor/
|
||||
[experiment](https://github.com/golang/go/commit/183cc0cd41f06f83cb7a2490a499e3f9101befff)
|
||||
utilizing the same environment variable that the go tooling itself supports
|
||||
(`GO15VENDOREXPERIMENT`).
|
||||
|
||||
godep mostly works the same way as the `go` command line tool. If you have go
|
||||
1.5.X and set `GO15VENDOREXPERIMENT=1` or have go1.6.X (or devel) `vendor/`
|
||||
is enabled. **Unless** you already have a `Godeps/_workspace`. This is a safety
|
||||
feature and godep warns you about this.
|
||||
|
||||
When `vendor/` is enabled godep will write the vendored code into the top level
|
||||
`./vendor/` directory. A `./Godeps/Godeps.json` file is created to track
|
||||
the dependencies and revisions. `vendor/` is not compatible with rewrites.
|
||||
|
||||
There is currently no automated migration between the old Godeps workspace and
|
||||
the vendor directory, but the following steps should work:
|
||||
|
||||
```term
|
||||
# just to be safe
|
||||
$ unset GO15VENDOREXPERIMENT
|
||||
|
||||
# restore currently vendored deps to the $GOPATH
|
||||
$ godep restore
|
||||
|
||||
# The next line is only needed to automatically undo rewritten imports that were
|
||||
# created with godep save -r.
|
||||
$ godep save -r=false <pkg spec>
|
||||
|
||||
# Remove the old Godeps folder
|
||||
$ rm -rf Godeps
|
||||
|
||||
# If on go1.5.X to enable `vendor/`
|
||||
$ export GO15VENDOREXPERIMENT=1
|
||||
|
||||
# re-analyze deps and save to `vendor/`.
|
||||
$ godep save <pkg spec>
|
||||
|
||||
# Add the changes to your VCS
|
||||
$ git add -A . ; git commit -am "Godep workspace -> vendor/"
|
||||
|
||||
# You should see your Godeps/_workspace/src files "moved" to vendor/.
|
||||
```
|
||||
|
||||
## Releasing
|
||||
|
||||
1. Increment the version in `version.go`.
|
||||
1. Tag the commit with the same version number.
|
||||
1. Update `Changelog.md`.
|
|
@ -0,0 +1,128 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Dependency is a specific revision of a package.
|
||||
type Dependency struct {
|
||||
ImportPath string
|
||||
Comment string `json:",omitempty"` // Description of commit, if present.
|
||||
Rev string // VCS-specific commit ID.
|
||||
|
||||
// used by command save & update
|
||||
ws string // workspace
|
||||
root string // import path to repo root
|
||||
dir string // full path to package
|
||||
|
||||
// used by command update
|
||||
matched bool // selected for update by command line
|
||||
pkg *Package
|
||||
missing bool // packages is missing
|
||||
|
||||
// used by command go
|
||||
vcs *VCS
|
||||
}
|
||||
|
||||
func eqDeps(a, b []Dependency) bool {
|
||||
ok := true
|
||||
for _, da := range a {
|
||||
for _, db := range b {
|
||||
if da.ImportPath == db.ImportPath && da.Rev != db.Rev {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// containsPathPrefix returns whether any string in a
|
||||
// is s or a directory containing s.
|
||||
// For example, pattern ["a"] matches "a" and "a/b"
|
||||
// (but not "ab").
|
||||
func containsPathPrefix(pats []string, s string) bool {
|
||||
for _, pat := range pats {
|
||||
if pat == s || strings.HasPrefix(s, pat+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func uniq(a []string) []string {
|
||||
var s string
|
||||
var i int
|
||||
if !sort.StringsAreSorted(a) {
|
||||
sort.Strings(a)
|
||||
}
|
||||
for _, t := range a {
|
||||
if t != s {
|
||||
a[i] = t
|
||||
i++
|
||||
s = t
|
||||
}
|
||||
}
|
||||
return a[:i]
|
||||
}
|
||||
|
||||
// trimGoVersion and return the major version
|
||||
func trimGoVersion(version string) (string, error) {
|
||||
if version == "devel" {
|
||||
return "devel", nil
|
||||
}
|
||||
if strings.HasPrefix(version, "devel+") || strings.HasPrefix(version, "devel-") {
|
||||
return strings.Replace(version, "devel+", "devel-", 1), nil
|
||||
}
|
||||
p := strings.Split(version, ".")
|
||||
if len(p) < 2 {
|
||||
return "", fmt.Errorf("Error determining major go version from: %q", version)
|
||||
}
|
||||
var split string
|
||||
switch {
|
||||
case strings.Contains(p[1], "beta"):
|
||||
split = "beta"
|
||||
case strings.Contains(p[1], "rc"):
|
||||
split = "rc"
|
||||
}
|
||||
if split != "" {
|
||||
p[1] = strings.Split(p[1], split)[0]
|
||||
}
|
||||
return p[0] + "." + p[1], nil
|
||||
}
|
||||
|
||||
var goVersionTestOutput = ""
|
||||
|
||||
func getGoVersion() (string, error) {
|
||||
// For testing purposes only
|
||||
if goVersionTestOutput != "" {
|
||||
return goVersionTestOutput, nil
|
||||
}
|
||||
|
||||
// Godep might have been compiled with a different
|
||||
// version, so we can't just use runtime.Version here.
|
||||
cmd := exec.Command("go", "version")
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
// goVersion returns the major version string of the Go compiler
|
||||
// currently installed, e.g. "go1.5".
|
||||
func goVersion() (string, error) {
|
||||
out, err := getGoVersion()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gv := strings.Split(out, " ")
|
||||
if len(gv) < 4 {
|
||||
return "", fmt.Errorf("Error splitting output of `go version`: Expected 4 or more elements, but there are < 4: %q", out)
|
||||
}
|
||||
if gv[2] == "devel" {
|
||||
return trimGoVersion(gv[2] + gv[3])
|
||||
}
|
||||
return trimGoVersion(gv[2])
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
)
|
||||
|
||||
var cmdDiff = &Command{
|
||||
Name: "diff",
|
||||
Short: "shows the diff between current and previously saved set of dependencies",
|
||||
Long: `
|
||||
Shows the difference, in a unified diff format, between the
|
||||
current set of dependencies and those generated on a
|
||||
previous 'go save' execution.
|
||||
`,
|
||||
Run: runDiff,
|
||||
OnlyInGOPATH: true,
|
||||
}
|
||||
|
||||
func runDiff(cmd *Command, args []string) {
|
||||
gold, err := loadDefaultGodepsFile()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
pkgs := []string{"."}
|
||||
dot, err := LoadPackages(pkgs...)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
gnew := &Godeps{
|
||||
ImportPath: dot[0].ImportPath,
|
||||
GoVersion: gold.GoVersion,
|
||||
}
|
||||
|
||||
err = gnew.fill(dot, dot[0].ImportPath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
diff, err := diffStr(&gold, gnew)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println(diff)
|
||||
}
|
||||
|
||||
// diffStr returns a unified diff string of two Godeps.
|
||||
func diffStr(a, b *Godeps) (string, error) {
|
||||
var ab, bb bytes.Buffer
|
||||
|
||||
_, err := a.writeTo(&ab)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
_, err = b.writeTo(&bb)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
diff := difflib.UnifiedDiff{
|
||||
A: difflib.SplitLines(ab.String()),
|
||||
B: difflib.SplitLines(bb.String()),
|
||||
FromFile: b.file(),
|
||||
ToFile: "$GOPATH",
|
||||
Context: 10,
|
||||
}
|
||||
return difflib.GetUnifiedDiffString(diff)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
|
||||
Command godep helps build packages reproducibly by fixing
|
||||
their dependencies.
|
||||
|
||||
Example Usage
|
||||
|
||||
Save currently-used dependencies to file Godeps:
|
||||
|
||||
$ godep save
|
||||
|
||||
Build project using saved dependencies:
|
||||
|
||||
$ godep go install
|
||||
|
||||
or
|
||||
|
||||
$ GOPATH=`godep path`:$GOPATH
|
||||
$ go install
|
||||
|
||||
*/
|
||||
package main
|
|
@ -0,0 +1,18 @@
|
|||
package main
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errorLoadingDeps = errors.New("error loading dependencies")
|
||||
errorLoadingPackages = errors.New("error loading packages")
|
||||
errorCopyingSourceCode = errors.New("error copying source code")
|
||||
errorNoPackagesUpdatable = errors.New("no packages can be updated")
|
||||
)
|
||||
|
||||
type errPackageNotFound struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (e errPackageNotFound) Error() string {
|
||||
return "Package (" + e.path + ") not found"
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var cmdGet = &Command{
|
||||
Name: "get",
|
||||
Args: "[-t] [packages]",
|
||||
Short: "download and install packages with specified dependencies",
|
||||
Long: `
|
||||
Get downloads to GOPATH the packages named by the import paths, and installs
|
||||
them with the dependencies specified in their Godeps files.
|
||||
|
||||
If any of the packages do not have Godeps files, those are installed
|
||||
as if by go get.
|
||||
|
||||
If -t is given, dependencies of test files are also downloaded and installed.
|
||||
|
||||
For more about specifying packages, see 'go help packages'.
|
||||
`,
|
||||
Run: runGet,
|
||||
OnlyInGOPATH: true,
|
||||
}
|
||||
|
||||
var getT bool
|
||||
|
||||
func init() {
|
||||
cmdGet.Flag.BoolVar(&getT, "t", false, "get test dependencies")
|
||||
}
|
||||
|
||||
func runGet(cmd *Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
args = []string{"."}
|
||||
}
|
||||
|
||||
cmdArgs := []interface{}{"get", "-d"}
|
||||
if verbose {
|
||||
cmdArgs = append(cmdArgs, "-v")
|
||||
}
|
||||
|
||||
if getT {
|
||||
cmdArgs = append(cmdArgs, "-t")
|
||||
}
|
||||
|
||||
err := command("go", append(cmdArgs, args)...).Run()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// group import paths by Godeps location
|
||||
groups := make(map[string][]string)
|
||||
ps, err := LoadPackages(args...)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for _, pkg := range ps {
|
||||
if pkg.Error.Err != "" {
|
||||
log.Fatalln(pkg.Error.Err)
|
||||
}
|
||||
dir, _ := findInParents(pkg.Dir, "Godeps")
|
||||
groups[dir] = append(groups[dir], pkg.ImportPath)
|
||||
}
|
||||
for dir, packages := range groups {
|
||||
var c *exec.Cmd
|
||||
if dir == "" {
|
||||
c = command("go", "install", packages)
|
||||
} else {
|
||||
c = command("godep", "go", "install", packages)
|
||||
c.Dir = dir
|
||||
}
|
||||
if err := c.Run(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// command is like exec.Command, but the returned
|
||||
// Cmd inherits stderr from the current process, and
|
||||
// elements of args may be either string or []string.
|
||||
func command(name string, args ...interface{}) *exec.Cmd {
|
||||
var a []string
|
||||
for _, arg := range args {
|
||||
switch v := arg.(type) {
|
||||
case string:
|
||||
a = append(a, v)
|
||||
case []string:
|
||||
a = append(a, v...)
|
||||
}
|
||||
}
|
||||
c := exec.Command(name, a...)
|
||||
c.Stderr = os.Stderr
|
||||
return c
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var cmdGo = &Command{
|
||||
Name: "go",
|
||||
Args: "command [arguments]",
|
||||
Short: "run the go tool with saved dependencies",
|
||||
Long: `
|
||||
Go runs the go tool with a modified GOPATH giving access to
|
||||
dependencies saved in Godeps.
|
||||
|
||||
Any go tool command can run this way, but "godep go get"
|
||||
is unnecessary and has been disabled. Instead, use
|
||||
"godep go install".
|
||||
`,
|
||||
Run: runGo,
|
||||
OnlyInGOPATH: true,
|
||||
}
|
||||
|
||||
// Find the godep GOPATH for this file tree and run the go tool.
|
||||
func runGo(cmd *Command, args []string) {
|
||||
gopath := prepareGopath()
|
||||
if s := os.Getenv("GOPATH"); s != "" {
|
||||
gopath += string(os.PathListSeparator) + os.Getenv("GOPATH")
|
||||
}
|
||||
if len(args) > 0 && args[0] == "get" {
|
||||
log.Printf("invalid subcommand: %q", "go get")
|
||||
fmt.Fprintln(os.Stderr, "Use 'godep go install' instead.")
|
||||
fmt.Fprintln(os.Stderr, "Run 'godep help go' for usage.")
|
||||
os.Exit(2)
|
||||
}
|
||||
c := exec.Command("go", args...)
|
||||
c.Env = append(envNoGopath(), "GOPATH="+gopath)
|
||||
c.Stdin = os.Stdin
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
err := c.Run()
|
||||
if err != nil {
|
||||
log.Fatalln("go", err)
|
||||
}
|
||||
}
|
||||
|
||||
// prepareGopath reads dependency information from the filesystem
|
||||
// entry name, fetches any necessary code, and returns a gopath
|
||||
// causing the specified dependencies to be used.
|
||||
func prepareGopath() (gopath string) {
|
||||
dir, isDir := findGodeps()
|
||||
if dir == "" {
|
||||
log.Fatalln("No Godeps found (or in any parent directory)")
|
||||
}
|
||||
if !isDir {
|
||||
log.Fatalln(strings.TrimSpace(needSource))
|
||||
}
|
||||
return filepath.Join(dir, "Godeps", "_workspace")
|
||||
}
|
||||
|
||||
// findGodeps looks for a directory entry "Godeps" in the
|
||||
// current directory or any parent, and returns the containing
|
||||
// directory and whether the entry itself is a directory.
|
||||
// If Godeps can't be found, findGodeps returns "".
|
||||
// For any other error, it exits the program.
|
||||
func findGodeps() (dir string, isDir bool) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return findInParents(wd, "Godeps")
|
||||
}
|
||||
|
||||
// isRoot returns true iff a path is a root.
|
||||
// On Unix: "/".
|
||||
// On Windows: "C:\", "D:\", ...
|
||||
func isRoot(p string) bool {
|
||||
p = filepath.Clean(p)
|
||||
volume := filepath.VolumeName(p)
|
||||
|
||||
p = strings.TrimPrefix(p, volume)
|
||||
p = filepath.ToSlash(p)
|
||||
|
||||
return p == "/"
|
||||
}
|
||||
|
||||
// findInParents returns the path to the directory containing name
|
||||
// in dir or any ancestor, and whether name itself is a directory.
|
||||
// If name cannot be found, findInParents returns the empty string.
|
||||
func findInParents(dir, name string) (container string, isDir bool) {
|
||||
for {
|
||||
fi, err := os.Stat(filepath.Join(dir, name))
|
||||
if os.IsNotExist(err) && isRoot(dir) {
|
||||
return "", false
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
dir = filepath.Dir(dir)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return dir, fi.IsDir()
|
||||
}
|
||||
}
|
||||
|
||||
func envNoGopath() (a []string) {
|
||||
for _, s := range os.Environ() {
|
||||
if !strings.HasPrefix(s, "GOPATH=") {
|
||||
a = append(a, s)
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
const needSource = `
|
||||
outdated Godeps missing source code
|
||||
|
||||
This dependency list was created with an old version of godep.
|
||||
|
||||
To work around this, you have two options:
|
||||
1. Run 'godep restore', and try again.
|
||||
2. Ask the maintainer to switch to a newer version of godep,
|
||||
then try again with the updated package.
|
||||
`
|
|
@ -0,0 +1,224 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
godepsFile = filepath.Join("Godeps", "Godeps.json")
|
||||
oldGodepsFile = filepath.Join("Godeps")
|
||||
)
|
||||
|
||||
// Godeps describes what a package needs to be rebuilt reproducibly.
|
||||
// It's the same information stored in file Godeps.
|
||||
type Godeps struct {
|
||||
ImportPath string
|
||||
GoVersion string
|
||||
GodepVersion string
|
||||
Packages []string `json:",omitempty"` // Arguments to save, if any.
|
||||
Deps []Dependency
|
||||
isOldFile bool
|
||||
}
|
||||
|
||||
func createGodepsFile() (*os.File, error) {
|
||||
return os.Create(godepsFile)
|
||||
}
|
||||
|
||||
func loadGodepsFile(path string) (Godeps, error) {
|
||||
var g Godeps
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return g, err
|
||||
}
|
||||
defer f.Close()
|
||||
err = json.NewDecoder(f).Decode(&g)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to parse %s: %s", path, err.Error())
|
||||
}
|
||||
return g, err
|
||||
}
|
||||
|
||||
func loadDefaultGodepsFile() (Godeps, error) {
|
||||
var g Godeps
|
||||
var err error
|
||||
g, err = loadGodepsFile(godepsFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
var err1 error
|
||||
g, err1 = loadGodepsFile(oldGodepsFile)
|
||||
if err1 != nil {
|
||||
if os.IsNotExist(err1) {
|
||||
return g, err
|
||||
}
|
||||
return g, err1
|
||||
}
|
||||
g.isOldFile = true
|
||||
return g, nil
|
||||
}
|
||||
}
|
||||
return g, err
|
||||
}
|
||||
|
||||
// pkgs is the list of packages to read dependencies for
|
||||
func (g *Godeps) fill(pkgs []*Package, destImportPath string) error {
|
||||
debugln("fill", destImportPath)
|
||||
ppln(pkgs)
|
||||
var err1 error
|
||||
var path, testImports []string
|
||||
dipp := []string{destImportPath}
|
||||
for _, p := range pkgs {
|
||||
if p.Standard {
|
||||
log.Println("ignoring stdlib package:", p.ImportPath)
|
||||
continue
|
||||
}
|
||||
if p.Error.Err != "" {
|
||||
log.Println(p.Error.Err)
|
||||
err1 = errorLoadingPackages
|
||||
continue
|
||||
}
|
||||
path = append(path, p.ImportPath)
|
||||
path = append(path, p.Deps...)
|
||||
testImports = append(testImports, p.TestImports...)
|
||||
testImports = append(testImports, p.XTestImports...)
|
||||
}
|
||||
ps, err := LoadPackages(testImports...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range ps {
|
||||
if p.Standard {
|
||||
continue
|
||||
}
|
||||
if p.Error.Err != "" {
|
||||
log.Println(p.Error.Err)
|
||||
err1 = errorLoadingPackages
|
||||
continue
|
||||
}
|
||||
path = append(path, p.ImportPath)
|
||||
path = append(path, p.Deps...)
|
||||
}
|
||||
debugln("path", path)
|
||||
for i, p := range path {
|
||||
path[i] = unqualify(p)
|
||||
}
|
||||
path = uniq(path)
|
||||
debugln("uniq, unqualify'd path", path)
|
||||
ps, err = LoadPackages(path...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pkg := range ps {
|
||||
if pkg.Error.Err != "" {
|
||||
log.Println(pkg.Error.Err)
|
||||
err1 = errorLoadingDeps
|
||||
continue
|
||||
}
|
||||
if pkg.Standard || containsPathPrefix(dipp, pkg.ImportPath) {
|
||||
debugln("standard or dest skipping", pkg.ImportPath)
|
||||
continue
|
||||
}
|
||||
vcs, reporoot, err := VCSFromDir(pkg.Dir, filepath.Join(pkg.Root, "src"))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
err1 = errorLoadingDeps
|
||||
continue
|
||||
}
|
||||
id, err := vcs.identify(pkg.Dir)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
err1 = errorLoadingDeps
|
||||
continue
|
||||
}
|
||||
if vcs.isDirty(pkg.Dir, id) {
|
||||
log.Println("dirty working tree (please commit changes):", pkg.Dir)
|
||||
err1 = errorLoadingDeps
|
||||
continue
|
||||
}
|
||||
comment := vcs.describe(pkg.Dir, id)
|
||||
g.Deps = append(g.Deps, Dependency{
|
||||
ImportPath: pkg.ImportPath,
|
||||
Rev: id,
|
||||
Comment: comment,
|
||||
dir: pkg.Dir,
|
||||
ws: pkg.Root,
|
||||
root: filepath.ToSlash(reporoot),
|
||||
vcs: vcs,
|
||||
})
|
||||
}
|
||||
return err1
|
||||
}
|
||||
|
||||
func (g *Godeps) copy() *Godeps {
|
||||
h := *g
|
||||
h.Deps = make([]Dependency, len(g.Deps))
|
||||
copy(h.Deps, g.Deps)
|
||||
return &h
|
||||
}
|
||||
|
||||
func (g *Godeps) file() string {
|
||||
if g.isOldFile {
|
||||
return oldGodepsFile
|
||||
}
|
||||
return godepsFile
|
||||
}
|
||||
|
||||
func (g *Godeps) save() (int64, error) {
|
||||
f, err := os.Create(g.file())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
return g.writeTo(f)
|
||||
}
|
||||
|
||||
func (g *Godeps) writeTo(w io.Writer) (int64, error) {
|
||||
g.GodepVersion = fmt.Sprintf("v%d", version) // godep always writes its current version.
|
||||
b, err := json.MarshalIndent(g, "", "\t")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := w.Write(append(b, '\n'))
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
func (g *Godeps) addOrUpdateDeps(deps []Dependency) {
|
||||
var missing []Dependency
|
||||
for _, d := range deps {
|
||||
var found bool
|
||||
for i := range g.Deps {
|
||||
if g.Deps[i].ImportPath == d.ImportPath {
|
||||
g.Deps[i] = d
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
missing = append(missing, d)
|
||||
}
|
||||
}
|
||||
for _, d := range missing {
|
||||
g.Deps = append(g.Deps, d)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Godeps) removeDeps(deps []Dependency) {
|
||||
var f []Dependency
|
||||
for i := range g.Deps {
|
||||
var found bool
|
||||
for _, d := range deps {
|
||||
if g.Deps[i].ImportPath == d.ImportPath {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
f = append(f, g.Deps[i])
|
||||
}
|
||||
}
|
||||
g.Deps = f
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LicenseFilePrefix is a list of filename prefixes that indicate it
|
||||
// might contain a software license
|
||||
var LicenseFilePrefix = []string{
|
||||
"licence", // UK spelling
|
||||
"license", // US spelling
|
||||
"copying",
|
||||
"unlicense",
|
||||
"copyright",
|
||||
"copyleft",
|
||||
"authors",
|
||||
"contributors",
|
||||
}
|
||||
|
||||
// LegalFileSubstring are substrings that indicate the file is likely
|
||||
// to contain some type of legal declaration. "legal" is often used
|
||||
// that it might moved to LicenseFilePrefix
|
||||
var LegalFileSubstring = []string{
|
||||
"legal",
|
||||
"notice",
|
||||
"disclaimer",
|
||||
"patent",
|
||||
"third-party",
|
||||
"thirdparty",
|
||||
}
|
||||
|
||||
// IsLicenseFile returns true if the filename might be contain a
|
||||
// software license
|
||||
func IsLicenseFile(filename string) bool {
|
||||
lowerfile := strings.ToLower(filename)
|
||||
for _, prefix := range LicenseFilePrefix {
|
||||
if strings.HasPrefix(lowerfile, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsLegalFile returns true if the file is likely to contain some type
|
||||
// of of legal declaration or licensing information
|
||||
func IsLegalFile(filename string) bool {
|
||||
lowerfile := strings.ToLower(filename)
|
||||
for _, prefix := range LicenseFilePrefix {
|
||||
if strings.HasPrefix(lowerfile, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, substring := range LegalFileSubstring {
|
||||
if strings.Index(lowerfile, substring) != -1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,604 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
pathpkg "path"
|
||||
)
|
||||
|
||||
var (
|
||||
gorootSrc = filepath.Join(build.Default.GOROOT, "src")
|
||||
ignoreTags = []string{"appengine", "ignore"} //TODO: appengine is a special case for now: https://github.com/tools/godep/issues/353
|
||||
versionMatch = regexp.MustCompile(`\Ago\d+\.\d+\z`)
|
||||
versionNegativeMatch = regexp.MustCompile(`\A\!go\d+\.\d+\z`)
|
||||
)
|
||||
|
||||
type errorMissingDep struct {
|
||||
i, dir string // import, dir
|
||||
}
|
||||
|
||||
func (e errorMissingDep) Error() string {
|
||||
return "Unable to find dependent package " + e.i + " in context of " + e.dir
|
||||
}
|
||||
|
||||
// packageContext is used to track an import and which package imported it.
|
||||
type packageContext struct {
|
||||
pkg *build.Package // package that imports the import
|
||||
imp string // import
|
||||
}
|
||||
|
||||
// depScanner tracks the processed and to be processed packageContexts
|
||||
type depScanner struct {
|
||||
processed []packageContext
|
||||
todo []packageContext
|
||||
}
|
||||
|
||||
// Next package and import to process
|
||||
func (ds *depScanner) Next() (*build.Package, string) {
|
||||
c := ds.todo[0]
|
||||
ds.processed = append(ds.processed, c)
|
||||
ds.todo = ds.todo[1:]
|
||||
return c.pkg, c.imp
|
||||
}
|
||||
|
||||
// Continue looping?
|
||||
func (ds *depScanner) Continue() bool {
|
||||
if len(ds.todo) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Add a package and imports to the depScanner. Skips already processed/pending package/import combos
|
||||
func (ds *depScanner) Add(pkg *build.Package, imports ...string) {
|
||||
NextImport:
|
||||
for _, i := range imports {
|
||||
if i == "C" {
|
||||
i = "runtime/cgo"
|
||||
}
|
||||
for _, epc := range ds.processed {
|
||||
if pkg.Dir == epc.pkg.Dir && i == epc.imp {
|
||||
debugln("ctxts epc.pkg.Dir == pkg.Dir && i == epc.imp, skipping", epc.pkg.Dir, i)
|
||||
continue NextImport
|
||||
}
|
||||
}
|
||||
for _, epc := range ds.todo {
|
||||
if pkg.Dir == epc.pkg.Dir && i == epc.imp {
|
||||
debugln("ctxts epc.pkg.Dir == pkg.Dir && i == epc.imp, skipping", epc.pkg.Dir, i)
|
||||
continue NextImport
|
||||
}
|
||||
}
|
||||
pc := packageContext{pkg, i}
|
||||
debugln("Adding pc:", pc.pkg.Dir, pc.imp)
|
||||
ds.todo = append(ds.todo, pc)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
pkgCache = make(map[string]*build.Package) // dir => *build.Package
|
||||
)
|
||||
|
||||
// returns the package in dir either from a cache or by importing it and then caching it
|
||||
func fullPackageInDir(dir string) (*build.Package, error) {
|
||||
var err error
|
||||
pkg, ok := pkgCache[dir]
|
||||
if !ok {
|
||||
pkg, err = build.ImportDir(dir, build.FindOnly)
|
||||
if pkg.Goroot {
|
||||
pkg, err = build.ImportDir(pkg.Dir, 0)
|
||||
} else {
|
||||
err = fillPackage(pkg)
|
||||
}
|
||||
if err == nil {
|
||||
pkgCache[dir] = pkg
|
||||
}
|
||||
}
|
||||
return pkg, err
|
||||
}
|
||||
|
||||
// listPackage specified by path
|
||||
func listPackage(path string) (*Package, error) {
|
||||
debugln("listPackage", path)
|
||||
var lp *build.Package
|
||||
dir, err := findDirForPath(path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lp, err = fullPackageInDir(dir)
|
||||
p := &Package{
|
||||
Dir: lp.Dir,
|
||||
Root: lp.Root,
|
||||
ImportPath: lp.ImportPath,
|
||||
XTestImports: lp.XTestImports,
|
||||
TestImports: lp.TestImports,
|
||||
GoFiles: lp.GoFiles,
|
||||
CgoFiles: lp.CgoFiles,
|
||||
TestGoFiles: lp.TestGoFiles,
|
||||
XTestGoFiles: lp.XTestGoFiles,
|
||||
IgnoredGoFiles: lp.IgnoredGoFiles,
|
||||
}
|
||||
p.Standard = lp.Goroot && lp.ImportPath != "" && !strings.Contains(lp.ImportPath, ".")
|
||||
if err != nil || p.Standard {
|
||||
return p, err
|
||||
}
|
||||
debugln("Looking For Package:", path, "in", dir)
|
||||
ppln(lp)
|
||||
|
||||
ds := depScanner{}
|
||||
ds.Add(lp, lp.Imports...)
|
||||
for ds.Continue() {
|
||||
ip, i := ds.Next()
|
||||
|
||||
debugf("Processing import %s for %s\n", i, ip.Dir)
|
||||
pdir, err := findDirForPath(i, ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dp, err := fullPackageInDir(pdir)
|
||||
if err != nil { // This really should happen in this context though
|
||||
ppln(err)
|
||||
return nil, errorMissingDep{i: i, dir: ip.Dir}
|
||||
}
|
||||
ppln(dp)
|
||||
if !dp.Goroot {
|
||||
// Don't bother adding packages in GOROOT to the dependency scanner, they don't import things from outside of it.
|
||||
ds.Add(dp, dp.Imports...)
|
||||
}
|
||||
debugln("lp:")
|
||||
ppln(lp)
|
||||
debugln("ip:")
|
||||
ppln(ip)
|
||||
if lp == ip {
|
||||
debugln("lp == ip")
|
||||
p.Imports = append(p.Imports, dp.ImportPath)
|
||||
}
|
||||
p.Deps = append(p.Deps, dp.ImportPath)
|
||||
p.Dependencies = addDependency(p.Dependencies, dp)
|
||||
}
|
||||
p.Imports = uniq(p.Imports)
|
||||
p.Deps = uniq(p.Deps)
|
||||
debugln("Done Looking For Package:", path, "in", dir)
|
||||
ppln(p)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func addDependency(deps []build.Package, d *build.Package) []build.Package {
|
||||
for i := range deps {
|
||||
if deps[i].Dir == d.Dir {
|
||||
return deps
|
||||
}
|
||||
}
|
||||
return append(deps, *d)
|
||||
}
|
||||
|
||||
// finds the directory for the given import path in the context of the provided build.Package (if provided)
|
||||
func findDirForPath(path string, ip *build.Package) (string, error) {
|
||||
debugln("findDirForPath", path, ip)
|
||||
var search []string
|
||||
|
||||
if build.IsLocalImport(path) {
|
||||
dir := path
|
||||
if !filepath.IsAbs(dir) {
|
||||
if abs, err := filepath.Abs(dir); err == nil {
|
||||
// interpret relative to current directory
|
||||
dir = abs
|
||||
}
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// We need to check to see if the import exists in vendor/ folders up the hierarchy of the importing package
|
||||
if VendorExperiment && ip != nil {
|
||||
debugln("resolving vendor posibilities:", ip.Dir, ip.Root)
|
||||
cr := cleanPath(ip.Root)
|
||||
|
||||
for base := cleanPath(ip.Dir); !pathEqual(base, cr); base = cleanPath(filepath.Dir(base)) {
|
||||
s := filepath.Join(base, "vendor", path)
|
||||
debugln("Adding search dir:", s)
|
||||
search = append(search, s)
|
||||
}
|
||||
}
|
||||
|
||||
for _, base := range build.Default.SrcDirs() {
|
||||
search = append(search, filepath.Join(base, path))
|
||||
}
|
||||
|
||||
for _, dir := range search {
|
||||
debugln("searching", dir)
|
||||
fi, err := stat(dir)
|
||||
if err == nil && fi.IsDir() {
|
||||
return dir, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errPackageNotFound{path}
|
||||
}
|
||||
|
||||
type statEntry struct {
|
||||
fi os.FileInfo
|
||||
err error
|
||||
}
|
||||
|
||||
var (
|
||||
statCache = make(map[string]statEntry)
|
||||
)
|
||||
|
||||
func clearStatCache() {
|
||||
statCache = make(map[string]statEntry)
|
||||
}
|
||||
|
||||
func stat(p string) (os.FileInfo, error) {
|
||||
if e, ok := statCache[p]; ok {
|
||||
return e.fi, e.err
|
||||
}
|
||||
fi, err := os.Stat(p)
|
||||
statCache[p] = statEntry{fi, err}
|
||||
return fi, err
|
||||
}
|
||||
|
||||
// fillPackage full of info. Assumes p.Dir is set at a minimum
|
||||
func fillPackage(p *build.Package) error {
|
||||
if p.Goroot {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.SrcRoot == "" {
|
||||
for _, base := range build.Default.SrcDirs() {
|
||||
if strings.HasPrefix(p.Dir, base) {
|
||||
p.SrcRoot = base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.SrcRoot == "" {
|
||||
return errors.New("Unable to find SrcRoot for package " + p.ImportPath)
|
||||
}
|
||||
|
||||
if p.Root == "" {
|
||||
p.Root = filepath.Dir(p.SrcRoot)
|
||||
}
|
||||
|
||||
var buildMatch = "+build "
|
||||
var buildFieldSplit = func(r rune) bool {
|
||||
return unicode.IsSpace(r) || r == ','
|
||||
}
|
||||
|
||||
debugln("Filling package:", p.ImportPath, "from", p.Dir)
|
||||
gofiles, err := filepath.Glob(filepath.Join(p.Dir, "*.go"))
|
||||
if err != nil {
|
||||
debugln("Error globbing", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(gofiles) == 0 {
|
||||
return &build.NoGoError{Dir: p.Dir}
|
||||
}
|
||||
|
||||
var testImports []string
|
||||
var imports []string
|
||||
NextFile:
|
||||
for _, file := range gofiles {
|
||||
debugln(file)
|
||||
pf, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ImportsOnly|parser.ParseComments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
testFile := strings.HasSuffix(file, "_test.go")
|
||||
fname := filepath.Base(file)
|
||||
for _, c := range pf.Comments {
|
||||
ct := c.Text()
|
||||
if i := strings.Index(ct, buildMatch); i != -1 {
|
||||
for _, t := range strings.FieldsFunc(ct[i+len(buildMatch):], buildFieldSplit) {
|
||||
for _, tag := range ignoreTags {
|
||||
if t == tag {
|
||||
p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname)
|
||||
continue NextFile
|
||||
}
|
||||
}
|
||||
|
||||
if versionMatch.MatchString(t) && !isSameOrNewer(t, majorGoVersion) {
|
||||
debugln("Adding", fname, "to ignored list because of version tag", t)
|
||||
p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname)
|
||||
continue NextFile
|
||||
}
|
||||
if versionNegativeMatch.MatchString(t) && isSameOrNewer(t[1:], majorGoVersion) {
|
||||
debugln("Adding", fname, "to ignored list because of version tag", t)
|
||||
p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname)
|
||||
continue NextFile
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if testFile {
|
||||
p.TestGoFiles = append(p.TestGoFiles, fname)
|
||||
} else {
|
||||
p.GoFiles = append(p.GoFiles, fname)
|
||||
}
|
||||
for _, is := range pf.Imports {
|
||||
name, err := strconv.Unquote(is.Path.Value)
|
||||
if err != nil {
|
||||
return err // can't happen?
|
||||
}
|
||||
if testFile {
|
||||
testImports = append(testImports, name)
|
||||
} else {
|
||||
imports = append(imports, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
imports = uniq(imports)
|
||||
testImports = uniq(testImports)
|
||||
p.Imports = imports
|
||||
p.TestImports = testImports
|
||||
return nil
|
||||
}
|
||||
|
||||
// All of the following functions were vendored from go proper. Locations are noted in comments, but may change in future Go versions.
|
||||
|
||||
// importPaths returns the import paths to use for the given command line.
|
||||
// $GOROOT/src/cmd/main.go:366
|
||||
func importPaths(args []string) []string {
|
||||
debugf("importPathsNoDotExpansion(%q) == ", args)
|
||||
args = importPathsNoDotExpansion(args)
|
||||
debugf("%q\n", args)
|
||||
var out []string
|
||||
for _, a := range args {
|
||||
if strings.Contains(a, "...") {
|
||||
if build.IsLocalImport(a) {
|
||||
debugf("build.IsLocalImport(%q) == true\n", a)
|
||||
pkgs := allPackagesInFS(a)
|
||||
debugf("allPackagesInFS(%q) == %q\n", a, pkgs)
|
||||
out = append(out, pkgs...)
|
||||
} else {
|
||||
debugf("build.IsLocalImport(%q) == false\n", a)
|
||||
pkgs := allPackages(a)
|
||||
debugf("allPackages(%q) == %q\n", a, pkgs)
|
||||
out = append(out, allPackages(a)...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
out = append(out, a)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// importPathsNoDotExpansion returns the import paths to use for the given
|
||||
// command line, but it does no ... expansion.
|
||||
// $GOROOT/src/cmd/main.go:332
|
||||
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 = "./" + pathpkg.Clean(a)
|
||||
if a == "./." {
|
||||
a = "."
|
||||
}
|
||||
} else {
|
||||
a = pathpkg.Clean(a)
|
||||
}
|
||||
if a == "all" || a == "std" || a == "cmd" {
|
||||
out = append(out, allPackages(a)...)
|
||||
continue
|
||||
}
|
||||
out = append(out, a)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// 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.
|
||||
// $GOROOT/src/cmd/main.go:620
|
||||
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
|
||||
}
|
||||
|
||||
// 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),
|
||||
// "cmd" (standard commands), or a path including "...".
|
||||
// $GOROOT/src/cmd/main.go:542
|
||||
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
|
||||
}
|
||||
|
||||
// $GOROOT/src/cmd/main.go:554
|
||||
// This has been changed to not use build.ImportDir
|
||||
func matchPackages(pattern string) []string {
|
||||
match := func(string) bool { return true }
|
||||
treeCanMatch := func(string) bool { return true }
|
||||
if pattern != "all" && pattern != "std" && pattern != "cmd" {
|
||||
match = matchPattern(pattern)
|
||||
treeCanMatch = treeCanMatchPattern(pattern)
|
||||
}
|
||||
|
||||
have := map[string]bool{
|
||||
"builtin": true, // ignore pseudo-package that exists only for documentation
|
||||
}
|
||||
if !build.Default.CgoEnabled {
|
||||
have["runtime/cgo"] = true // ignore during walk
|
||||
}
|
||||
var pkgs []string
|
||||
|
||||
for _, src := range build.Default.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 has a dot, assume it's a domain name for go get,
|
||||
// and 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
|
||||
}
|
||||
|
||||
ap, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if _, err = fullPackageInDir(ap); err != nil {
|
||||
debugf("matchPackage(%q) ap=%q Error: %q\n", ap, pattern, err)
|
||||
if _, noGo := err.(*build.NoGoError); noGo {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
pkgs = append(pkgs, name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// treeCanMatchPattern(pattern)(name) reports whether
|
||||
// name or children of name can possibly match pattern.
|
||||
// Pattern is the same limited glob accepted by matchPattern.
|
||||
// $GOROOT/src/cmd/main.go:527
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// hasPathPrefix reports whether the path s begins with the
|
||||
// elements in prefix.
|
||||
// $GOROOT/src/cmd/main.go:489
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// $GOROOT/src/cmd/go/main.go:631
|
||||
// This has been changed to not use build.ImportDir
|
||||
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, _ := pathpkg.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; 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
|
||||
}
|
||||
ap, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if _, err = fullPackageInDir(ap); err != nil {
|
||||
debugf("matchPackageInFS(%q) ap=%q Error: %q\n", ap, pattern, err)
|
||||
if _, noGo := err.(*build.NoGoError); !noGo {
|
||||
log.Print(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
pkgs = append(pkgs, name)
|
||||
return nil
|
||||
})
|
||||
return pkgs
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
cpuprofile string
|
||||
verbose bool // Verbose flag for commands that support it
|
||||
debug bool // Debug flag for commands that support it
|
||||
majorGoVersion string
|
||||
VendorExperiment bool
|
||||
sep string
|
||||
)
|
||||
|
||||
// Command is an implementation of a godep command
|
||||
// like godep save or godep go.
|
||||
type Command struct {
|
||||
// Run runs the command.
|
||||
// The args are the arguments after the command name.
|
||||
Run func(cmd *Command, args []string)
|
||||
|
||||
// Name of the command
|
||||
Name string
|
||||
|
||||
// Args the command would expect
|
||||
Args string
|
||||
|
||||
// Short is the short description shown in the 'godep help' output.
|
||||
Short string
|
||||
|
||||
// Long is the long message shown in the
|
||||
// 'godep help <this-command>' output.
|
||||
Long string
|
||||
|
||||
// Flag is a set of flags specific to this command.
|
||||
Flag flag.FlagSet
|
||||
|
||||
// OnlyInGOPATH limits this command to being run only while inside of a GOPATH
|
||||
OnlyInGOPATH bool
|
||||
}
|
||||
|
||||
// UsageExit prints usage information and exits.
|
||||
func (c *Command) UsageExit() {
|
||||
fmt.Fprintf(os.Stderr, "Args: godep %s [-v] [-d] %s\n\n", c.Name, c.Args)
|
||||
fmt.Fprintf(os.Stderr, "Run 'godep help %s' for help.\n", c.Name)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Commands lists the available commands and help topics.
|
||||
// The order here is the order in which they are printed
|
||||
// by 'godep help'.
|
||||
var commands = []*Command{
|
||||
cmdSave,
|
||||
cmdGo,
|
||||
cmdGet,
|
||||
cmdPath,
|
||||
cmdRestore,
|
||||
cmdUpdate,
|
||||
cmdDiff,
|
||||
cmdVersion,
|
||||
}
|
||||
|
||||
// VendorExperiment is the Go 1.5 vendor directory experiment flag, see
|
||||
// https://github.com/golang/go/commit/183cc0cd41f06f83cb7a2490a499e3f9101befff
|
||||
// Honor the env var unless the project already has an old school godep workspace
|
||||
func determineVendor(v string) bool {
|
||||
go15ve := os.Getenv("GO15VENDOREXPERIMENT")
|
||||
var ev bool
|
||||
switch v {
|
||||
case "go1", "go1.1", "go1.2", "go1.3", "go1.4":
|
||||
ev = false
|
||||
case "go1.5":
|
||||
ev = go15ve == "1"
|
||||
case "go1.6":
|
||||
ev = go15ve != "0"
|
||||
default: //go1.7+, devel*
|
||||
ev = true
|
||||
}
|
||||
|
||||
ws := filepath.Join("Godeps", "_workspace")
|
||||
s, err := os.Stat(ws)
|
||||
if err == nil && s.IsDir() {
|
||||
log.Printf("WARNING: Godep workspaces (./Godeps/_workspace) are deprecated and support for them will be removed when go1.8 is released.")
|
||||
if ev {
|
||||
log.Printf("WARNING: Go version (%s) & $GO15VENDOREXPERIMENT=%s wants to enable the vendor experiment, but disabling because a Godep workspace (%s) exists\n", v, go15ve, ws)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return ev
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("godep: ")
|
||||
log.SetOutput(os.Stderr)
|
||||
|
||||
flag.Usage = usageExit
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) < 1 {
|
||||
usageExit()
|
||||
}
|
||||
|
||||
if args[0] == "help" {
|
||||
help(args[1:])
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
majorGoVersion, err = goVersion()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name == args[0] {
|
||||
if cmd.OnlyInGOPATH {
|
||||
checkInGOPATH()
|
||||
}
|
||||
|
||||
VendorExperiment = determineVendor(majorGoVersion)
|
||||
// sep is the signature set of path elements that
|
||||
// precede the original path of an imported package.
|
||||
sep = defaultSep(VendorExperiment)
|
||||
|
||||
cmd.Flag.BoolVar(&verbose, "v", false, "enable verbose output")
|
||||
cmd.Flag.BoolVar(&debug, "d", false, "enable debug output")
|
||||
cmd.Flag.StringVar(&cpuprofile, "cpuprofile", "", "Write cpu profile to this file")
|
||||
cmd.Flag.Usage = func() { cmd.UsageExit() }
|
||||
cmd.Flag.Parse(args[1:])
|
||||
|
||||
debugln("versionString()", versionString())
|
||||
debugln("majorGoVersion", majorGoVersion)
|
||||
debugln("VendorExperiment", VendorExperiment)
|
||||
debugln("sep", sep)
|
||||
|
||||
if cpuprofile != "" {
|
||||
f, err := os.Create(cpuprofile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
cmd.Run(cmd, cmd.Flag.Args())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "godep: unknown command %q\n", args[0])
|
||||
fmt.Fprintf(os.Stderr, "Run 'godep help' for usage.\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func subPath(sub, path string) bool {
|
||||
ls := strings.ToLower(sub)
|
||||
lp := strings.ToLower(path)
|
||||
if ls == lp {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(ls, lp)
|
||||
}
|
||||
|
||||
func checkInGOPATH() {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to determine current working directory", err)
|
||||
}
|
||||
dirs := build.Default.SrcDirs()
|
||||
for _, p := range dirs {
|
||||
if ok := subPath(pwd, p); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("[WARNING]: godep should only be used inside a valid go package directory and")
|
||||
log.Println("[WARNING]: may not function correctly. You are probably outside of your $GOPATH.")
|
||||
log.Printf("[WARNING]:\tCurrent Directory: %s\n", pwd)
|
||||
log.Printf("[WARNING]:\t$GOPATH: %s\n", os.Getenv("GOPATH"))
|
||||
}
|
||||
|
||||
var usageTemplate = `
|
||||
Godep is a tool for managing Go package dependencies.
|
||||
|
||||
Usage:
|
||||
|
||||
godep command [arguments]
|
||||
|
||||
The commands are:
|
||||
{{range .}}
|
||||
{{.Name | printf "%-8s"}} {{.Short}}{{end}}
|
||||
|
||||
Use "godep help [command]" for more information about a command.
|
||||
`
|
||||
|
||||
var helpTemplate = `
|
||||
Args: godep {{.Name}} [-v] [-d] {{.Args}}
|
||||
|
||||
{{.Long | trim}}
|
||||
|
||||
If -v is given, verbose output is enabled.
|
||||
|
||||
If -d is given, debug output is enabled (you probably don't want this, see -v).
|
||||
|
||||
`
|
||||
|
||||
func help(args []string) {
|
||||
if len(args) == 0 {
|
||||
printUsage(os.Stdout)
|
||||
return
|
||||
}
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintf(os.Stderr, "usage: godep help command\n\n")
|
||||
fmt.Fprintf(os.Stderr, "Too many arguments given.\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name == args[0] {
|
||||
tmpl(os.Stdout, helpTemplate, cmd)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func usageExit() {
|
||||
printUsage(os.Stderr)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func printUsage(w io.Writer) {
|
||||
tmpl(w, usageTemplate, commands)
|
||||
}
|
||||
|
||||
// tmpl executes the given template text on data, writing the result to w.
|
||||
func tmpl(w io.Writer, text string, data interface{}) {
|
||||
t := template.New("top")
|
||||
t.Funcs(template.FuncMap{
|
||||
"trim": strings.TrimSpace,
|
||||
})
|
||||
template.Must(t.Parse(strings.TrimSpace(text) + "\n\n"))
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/kr/pretty"
|
||||
)
|
||||
|
||||
func debugln(a ...interface{}) (int, error) {
|
||||
if debug {
|
||||
return fmt.Println(a...)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func verboseln(a ...interface{}) {
|
||||
if verbose {
|
||||
log.Println(a...)
|
||||
}
|
||||
}
|
||||
|
||||
func debugf(format string, a ...interface{}) (int, error) {
|
||||
if debug {
|
||||
return fmt.Printf(format, a...)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func verbosef(format string, a ...interface{}) {
|
||||
if verbose {
|
||||
log.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func pp(a ...interface{}) (int, error) {
|
||||
if debug {
|
||||
return pretty.Print(a...)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func ppln(a ...interface{}) (int, error) {
|
||||
if debug {
|
||||
return pretty.Println(a...)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func ppf(format string, a ...interface{}) (int, error) {
|
||||
if debug {
|
||||
return pretty.Printf(format, a...)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var cmdPath = &Command{
|
||||
Name: "path",
|
||||
Short: "print GOPATH for dependency code",
|
||||
Long: `
|
||||
Command path prints a path for use in env var GOPATH
|
||||
that makes available the specified version of each dependency.
|
||||
|
||||
The printed path does not include any GOPATH value from
|
||||
the environment.
|
||||
|
||||
For more about how GOPATH works, see 'go help gopath'.
|
||||
`,
|
||||
Run: runPath,
|
||||
OnlyInGOPATH: true,
|
||||
}
|
||||
|
||||
// Print the gopath that points to
|
||||
// the included dependency code.
|
||||
func runPath(cmd *Command, args []string) {
|
||||
if len(args) != 0 {
|
||||
cmd.UsageExit()
|
||||
}
|
||||
if VendorExperiment {
|
||||
fmt.Fprintln(os.Stderr, "Error: GO15VENDOREXPERIMENT is enabled and the vendor/ directory is not a valid Go workspace.")
|
||||
os.Exit(1)
|
||||
}
|
||||
gopath := prepareGopath()
|
||||
fmt.Println(gopath)
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Package represents a Go package.
|
||||
type Package struct {
|
||||
Dir string
|
||||
Root string
|
||||
ImportPath string
|
||||
Deps []string
|
||||
Standard bool
|
||||
Processed bool
|
||||
|
||||
GoFiles []string
|
||||
CgoFiles []string
|
||||
IgnoredGoFiles []string
|
||||
|
||||
TestGoFiles []string
|
||||
TestImports []string
|
||||
XTestGoFiles []string
|
||||
XTestImports []string
|
||||
|
||||
Error struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
// --- New stuff for now
|
||||
Imports []string
|
||||
Dependencies []build.Package
|
||||
}
|
||||
|
||||
// LoadPackages loads the named packages
|
||||
// Unlike the go tool, an empty argument list is treated as an empty list; "."
|
||||
// must be given explicitly if desired.
|
||||
// IgnoredGoFiles will be processed and their dependencies resolved recursively
|
||||
func LoadPackages(names ...string) (a []*Package, err error) {
|
||||
debugln("LoadPackages", names)
|
||||
if len(names) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
for _, i := range importPaths(names) {
|
||||
p, err := listPackage(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a = append(a, p)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (p *Package) allGoFiles() []string {
|
||||
var a []string
|
||||
a = append(a, p.GoFiles...)
|
||||
a = append(a, p.CgoFiles...)
|
||||
a = append(a, p.TestGoFiles...)
|
||||
a = append(a, p.XTestGoFiles...)
|
||||
a = append(a, p.IgnoredGoFiles...)
|
||||
return a
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Taken from $GOROOT/src/cmd/go/main.go.
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/tools/go/vcs"
|
||||
)
|
||||
|
||||
var cmdRestore = &Command{
|
||||
Name: "restore",
|
||||
Short: "check out listed dependency versions in GOPATH",
|
||||
Long: `
|
||||
Restore checks out the Godeps-specified version of each package in GOPATH.
|
||||
|
||||
NOTE: restore leaves git repositories in a detached state. go1.6+ no longer
|
||||
checks out the master branch when doing a "go get", see:
|
||||
https://github.com/golang/go/commit/42206598671a44111c8f726ad33dc7b265bdf669.
|
||||
|
||||
`,
|
||||
Run: runRestore,
|
||||
OnlyInGOPATH: true,
|
||||
}
|
||||
|
||||
// Three phases:
|
||||
// 1. Download all deps
|
||||
// 2. Restore all deps (checkout the recorded rev)
|
||||
// 3. Attempt to load all deps as a simple consistency check
|
||||
func runRestore(cmd *Command, args []string) {
|
||||
if len(build.Default.GOPATH) == 0 {
|
||||
log.Println("Error restore requires GOPATH but it is empty.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var hadError bool
|
||||
checkErr := func(s string) {
|
||||
if hadError {
|
||||
log.Println(s)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
g, err := loadDefaultGodepsFile()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for i, dep := range g.Deps {
|
||||
verboseln("Downloading dependency (if needed):", dep.ImportPath)
|
||||
err := download(&dep)
|
||||
if err != nil {
|
||||
log.Printf("error downloading dep (%s): %s\n", dep.ImportPath, err)
|
||||
hadError = true
|
||||
}
|
||||
g.Deps[i] = dep
|
||||
}
|
||||
checkErr("Error downloading some deps. Aborting restore and check.")
|
||||
for _, dep := range g.Deps {
|
||||
verboseln("Restoring dependency (if needed):", dep.ImportPath)
|
||||
err := restore(dep)
|
||||
if err != nil {
|
||||
log.Printf("error restoring dep (%s): %s\n", dep.ImportPath, err)
|
||||
hadError = true
|
||||
}
|
||||
}
|
||||
checkErr("Error restoring some deps. Aborting check.")
|
||||
for _, dep := range g.Deps {
|
||||
verboseln("Checking dependency:", dep.ImportPath)
|
||||
_, err := LoadPackages(dep.ImportPath)
|
||||
if err != nil {
|
||||
log.Printf("Dep (%s) restored, but was unable to load it with error:\n\t%s\n", dep.ImportPath, err)
|
||||
if me, ok := err.(errorMissingDep); ok {
|
||||
log.Println("\tThis may be because the dependencies were saved with an older version of godep (< v33).")
|
||||
log.Printf("\tTry `go get %s`. Then `godep save` to update deps.\n", me.i)
|
||||
}
|
||||
hadError = true
|
||||
}
|
||||
}
|
||||
checkErr("Error checking some deps.")
|
||||
}
|
||||
|
||||
var downloaded = make(map[string]bool)
|
||||
|
||||
// download the given dependency.
|
||||
// 2 Passes: 1) go get -d <pkg>, 2) git pull (if necessary)
|
||||
func download(dep *Dependency) error {
|
||||
|
||||
rr, err := vcs.RepoRootForImportPath(dep.ImportPath, debug)
|
||||
if err != nil {
|
||||
debugln("Error determining repo root for", dep.ImportPath)
|
||||
return err
|
||||
}
|
||||
ppln("rr", rr)
|
||||
|
||||
dep.vcs = cmd[rr.VCS]
|
||||
|
||||
// try to find an existing directory in the GOPATHs
|
||||
for _, gp := range filepath.SplitList(build.Default.GOPATH) {
|
||||
t := filepath.Join(gp, "src", rr.Root)
|
||||
fi, err := os.Stat(t)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if fi.IsDir() {
|
||||
dep.root = t
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If none found, just pick the first GOPATH entry (AFAICT that's what go get does)
|
||||
if dep.root == "" {
|
||||
dep.root = filepath.Join(filepath.SplitList(build.Default.GOPATH)[0], "src", rr.Root)
|
||||
}
|
||||
ppln("dep", dep)
|
||||
|
||||
if downloaded[rr.Repo] {
|
||||
verboseln("Skipping already downloaded repo", rr.Repo)
|
||||
return nil
|
||||
}
|
||||
|
||||
fi, err := os.Stat(dep.root)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(dep.root), os.ModePerm); err != nil {
|
||||
debugln("Error creating base dir of", dep.root)
|
||||
return err
|
||||
}
|
||||
err := rr.VCS.CreateAtRev(dep.root, rr.Repo, dep.Rev)
|
||||
debugln("CreatedAtRev", dep.root, rr.Repo, dep.Rev)
|
||||
if err != nil {
|
||||
debugln("CreateAtRev error", err)
|
||||
return err
|
||||
}
|
||||
downloaded[rr.Repo] = true
|
||||
return nil
|
||||
}
|
||||
debugln("Error checking repo root for", dep.ImportPath, "at", dep.root, ":", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return errors.New("repo root src dir exists, but isn't a directory for " + dep.ImportPath + " at " + dep.root)
|
||||
}
|
||||
|
||||
if !dep.vcs.exists(dep.root, dep.Rev) {
|
||||
debugln("Updating existing", dep.root)
|
||||
if dep.vcs == vcsGit {
|
||||
detached, err := gitDetached(dep.root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if detached {
|
||||
db, err := gitDefaultBranch(dep.root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gitCheckout(dep.root, db); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dep.vcs.vcs.Download(dep.root)
|
||||
downloaded[rr.Repo] = true
|
||||
}
|
||||
|
||||
debugln("Nothing to download")
|
||||
return nil
|
||||
}
|
||||
|
||||
var restored = make(map[string]string) // dep.root -> dep.Rev
|
||||
|
||||
// restore checks out the given revision.
|
||||
func restore(dep Dependency) error {
|
||||
rev, ok := restored[dep.root]
|
||||
debugln(rev)
|
||||
debugln(ok)
|
||||
debugln(dep.root)
|
||||
if ok {
|
||||
if rev != dep.Rev {
|
||||
return errors.New("Wanted to restore rev " + dep.Rev + ", already restored rev " + rev + " for another package in the repo")
|
||||
}
|
||||
verboseln("Skipping already restored repo")
|
||||
return nil
|
||||
}
|
||||
|
||||
debugln("Restoring:", dep.ImportPath, dep.Rev)
|
||||
err := dep.vcs.RevSync(dep.root, dep.Rev)
|
||||
if err == nil {
|
||||
restored[dep.root] = dep.Rev
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
|
||||
"github.com/kr/fs"
|
||||
)
|
||||
|
||||
// rewrite visits the go files in pkgs, plus all go files
|
||||
// in the directory tree Godeps, rewriting import statements
|
||||
// according to the rules for func qualify.
|
||||
func rewrite(pkgs []*Package, qual string, paths []string) error {
|
||||
for _, path := range pkgFiles(pkgs) {
|
||||
debugln("rewrite", path)
|
||||
err := rewriteTree(path, qual, paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return rewriteTree("Godeps", qual, paths)
|
||||
}
|
||||
|
||||
// pkgFiles returns the full filesystem path to all go files in pkgs.
|
||||
func pkgFiles(pkgs []*Package) []string {
|
||||
var a []string
|
||||
for _, pkg := range pkgs {
|
||||
for _, s := range pkg.allGoFiles() {
|
||||
a = append(a, filepath.Join(pkg.Dir, s))
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// rewriteTree recursively visits the go files in path, rewriting
|
||||
// import statements according to the rules for func qualify.
|
||||
// This function ignores the 'testdata' directory.
|
||||
func rewriteTree(path, qual string, paths []string) error {
|
||||
w := fs.Walk(path)
|
||||
for w.Step() {
|
||||
if w.Err() != nil {
|
||||
log.Println("rewrite:", w.Err())
|
||||
continue
|
||||
}
|
||||
s := w.Stat()
|
||||
if s.IsDir() && s.Name() == "testdata" {
|
||||
w.SkipDir()
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(w.Path(), ".go") {
|
||||
err := rewriteGoFile(w.Path(), qual, paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rewriteGoFile rewrites import statements in the named file
|
||||
// according to the rules for func qualify.
|
||||
func rewriteGoFile(name, qual string, paths []string) error {
|
||||
debugln("rewriteGoFile", name, ",", qual, ",", paths)
|
||||
printerConfig := &printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var changed bool
|
||||
for _, s := range f.Imports {
|
||||
name, err := strconv.Unquote(s.Path.Value)
|
||||
if err != nil {
|
||||
return err // can't happen
|
||||
}
|
||||
q := qualify(unqualify(name), qual, paths)
|
||||
if q != name {
|
||||
s.Path.Value = strconv.Quote(q)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
if err = printerConfig.Fprint(&buffer, fset, f); err != nil {
|
||||
return err
|
||||
}
|
||||
fset = token.NewFileSet()
|
||||
f, err = parser.ParseFile(fset, name, &buffer, parser.ParseComments)
|
||||
ast.SortImports(fset, f)
|
||||
tpath := name + ".temp"
|
||||
t, err := os.Create(tpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = printerConfig.Fprint(t, fset, f); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = t.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
// This is required before the rename on windows.
|
||||
if err = os.Remove(name); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tpath, name)
|
||||
}
|
||||
|
||||
func defaultSep(experiment bool) string {
|
||||
if experiment {
|
||||
return "/vendor/"
|
||||
}
|
||||
return "/Godeps/_workspace/src/"
|
||||
}
|
||||
|
||||
func relativeVendorTarget(experiment bool) string {
|
||||
full := defaultSep(experiment)
|
||||
if full[0] == '/' {
|
||||
full = full[1:]
|
||||
}
|
||||
return filepath.FromSlash(full)
|
||||
}
|
||||
|
||||
// unqualify returns the part of importPath after the last
|
||||
// occurrence of the signature path elements
|
||||
// (Godeps/_workspace/src) that always precede imported
|
||||
// packages in rewritten import paths.
|
||||
//
|
||||
// For example,
|
||||
// unqualify(C) = C
|
||||
// unqualify(D/Godeps/_workspace/src/C) = C
|
||||
func unqualify(importPath string) string {
|
||||
if i := strings.LastIndex(importPath, sep); i != -1 {
|
||||
importPath = importPath[i+len(sep):]
|
||||
}
|
||||
return importPath
|
||||
}
|
||||
|
||||
// qualify qualifies importPath with its corresponding import
|
||||
// path in the Godeps src copy of package pkg. If importPath
|
||||
// is a directory lexically contained in a path in paths,
|
||||
// it will be qualified with package pkg; otherwise, it will
|
||||
// be returned unchanged.
|
||||
//
|
||||
// For example, given paths {D, T} and pkg C,
|
||||
// importPath returns
|
||||
// C C
|
||||
// fmt fmt
|
||||
// D C/Godeps/_workspace/src/D
|
||||
// D/P C/Godeps/_workspace/src/D/P
|
||||
// T C/Godeps/_workspace/src/T
|
||||
func qualify(importPath, pkg string, paths []string) string {
|
||||
if containsPathPrefix(paths, importPath) {
|
||||
return pkg + sep + importPath
|
||||
}
|
||||
return importPath
|
||||
}
|
|
@ -0,0 +1,612 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/kr/fs"
|
||||
)
|
||||
|
||||
var cmdSave = &Command{
|
||||
Name: "save",
|
||||
Args: "[-r] [-t] [packages]",
|
||||
Short: "list and copy dependencies into Godeps",
|
||||
Long: `
|
||||
|
||||
Save writes a list of the named packages and their dependencies along
|
||||
with the exact source control revision of each package, and copies
|
||||
their source code into a subdirectory. Packages inside "." are excluded
|
||||
from the list to be copied.
|
||||
|
||||
The list is written to Godeps/Godeps.json, and source code for all
|
||||
dependencies is copied into either Godeps/_workspace or, if the vendor
|
||||
experiment is turned on, vendor/.
|
||||
|
||||
The dependency list is a JSON document with the following structure:
|
||||
|
||||
type Godeps struct {
|
||||
ImportPath string
|
||||
GoVersion string // Abridged output of 'go version'.
|
||||
Packages []string // Arguments to godep save, if any.
|
||||
Deps []struct {
|
||||
ImportPath string
|
||||
Comment string // Tag or description of commit.
|
||||
Rev string // VCS-specific commit ID.
|
||||
}
|
||||
}
|
||||
|
||||
Any packages already present in the list will be left unchanged.
|
||||
To update a dependency to a newer revision, use 'godep update'.
|
||||
|
||||
If -r is given, import statements will be rewritten to refer directly
|
||||
to the copied source code. This is not compatible with the vendor
|
||||
experiment. Note that this will not rewrite the statements in the
|
||||
files outside the project.
|
||||
|
||||
If -t is given, test files (*_test.go files + testdata directories) are
|
||||
also saved.
|
||||
|
||||
For more about specifying packages, see 'go help packages'.
|
||||
`,
|
||||
Run: runSave,
|
||||
OnlyInGOPATH: true,
|
||||
}
|
||||
|
||||
var (
|
||||
saveR, saveT bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdSave.Flag.BoolVar(&saveR, "r", false, "rewrite import paths")
|
||||
cmdSave.Flag.BoolVar(&saveT, "t", false, "save test files")
|
||||
|
||||
}
|
||||
|
||||
func runSave(cmd *Command, args []string) {
|
||||
if VendorExperiment && saveR {
|
||||
log.Println("flag -r is incompatible with the vendoring experiment")
|
||||
cmd.UsageExit()
|
||||
}
|
||||
err := save(args)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func dotPackage() (*build.Package, error) {
|
||||
dir, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return build.ImportDir(dir, build.FindOnly)
|
||||
}
|
||||
|
||||
func projectPackages(dDir string, a []*Package) []*Package {
|
||||
var projPkgs []*Package
|
||||
dotDir := fmt.Sprintf("%s%c", dDir, filepath.Separator)
|
||||
for _, p := range a {
|
||||
pkgDir := fmt.Sprintf("%s%c", p.Dir, filepath.Separator)
|
||||
if strings.HasPrefix(pkgDir, dotDir) {
|
||||
projPkgs = append(projPkgs, p)
|
||||
}
|
||||
}
|
||||
return projPkgs
|
||||
}
|
||||
|
||||
func save(pkgs []string) error {
|
||||
var err error
|
||||
dp, err := dotPackage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
debugln("dotPackageImportPath:", dp.ImportPath)
|
||||
debugln("dotPackageDir:", dp.Dir)
|
||||
|
||||
cv, err := goVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verboseln("Go Version:", cv)
|
||||
|
||||
gold, err := loadDefaultGodepsFile()
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
verboseln("No old Godeps.json found.")
|
||||
gold.GoVersion = cv
|
||||
}
|
||||
|
||||
printVersionWarnings(gold.GoVersion)
|
||||
if len(gold.GoVersion) == 0 {
|
||||
gold.GoVersion = majorGoVersion
|
||||
} else {
|
||||
majorGoVersion, err = trimGoVersion(gold.GoVersion)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to determine go major version from value specified in %s: %s\n", gold.file(), gold.GoVersion)
|
||||
}
|
||||
}
|
||||
|
||||
gnew := &Godeps{
|
||||
ImportPath: dp.ImportPath,
|
||||
GoVersion: gold.GoVersion,
|
||||
}
|
||||
|
||||
switch len(pkgs) {
|
||||
case 0:
|
||||
pkgs = []string{"."}
|
||||
default:
|
||||
gnew.Packages = pkgs
|
||||
}
|
||||
|
||||
verboseln("Finding dependencies for", pkgs)
|
||||
a, err := LoadPackages(pkgs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range a {
|
||||
verboseln("Found package:", p.ImportPath)
|
||||
verboseln("\tDeps:", strings.Join(p.Deps, " "))
|
||||
}
|
||||
ppln(a)
|
||||
|
||||
projA := projectPackages(dp.Dir, a)
|
||||
debugln("Filtered projectPackages")
|
||||
ppln(projA)
|
||||
|
||||
verboseln("Computing new Godeps.json file")
|
||||
err = gnew.fill(a, dp.ImportPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
debugln("New Godeps Filled")
|
||||
ppln(gnew)
|
||||
|
||||
if gnew.Deps == nil {
|
||||
gnew.Deps = make([]Dependency, 0) // produce json [], not null
|
||||
}
|
||||
gdisk := gnew.copy()
|
||||
err = carryVersions(&gold, gnew)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gold.isOldFile {
|
||||
// If we are migrating from an old format file,
|
||||
// we require that the listed version of every
|
||||
// dependency must be installed in GOPATH, so it's
|
||||
// available to copy.
|
||||
if !eqDeps(gnew.Deps, gdisk.Deps) {
|
||||
return errors.New(strings.TrimSpace(needRestore))
|
||||
}
|
||||
gold = Godeps{}
|
||||
}
|
||||
os.Remove("Godeps") // remove regular file if present; ignore error
|
||||
readme := filepath.Join("Godeps", "Readme")
|
||||
err = writeFile(readme, strings.TrimSpace(Readme)+"\n")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
_, err = gnew.save()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
verboseln("Computing diff between old and new deps")
|
||||
// We use a name starting with "_" so the go tool
|
||||
// ignores this directory when traversing packages
|
||||
// starting at the project's root. For example,
|
||||
// godep go list ./...
|
||||
srcdir := filepath.FromSlash(strings.Trim(sep, "/"))
|
||||
rem := subDeps(gold.Deps, gnew.Deps)
|
||||
ppln(rem)
|
||||
add := subDeps(gnew.Deps, gold.Deps)
|
||||
ppln(add)
|
||||
if len(rem) > 0 {
|
||||
verboseln("Deps to remove:")
|
||||
for _, r := range rem {
|
||||
verboseln("\t", r.ImportPath)
|
||||
}
|
||||
verboseln("Removing unused dependencies")
|
||||
err = removeSrc(srcdir, rem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(add) > 0 {
|
||||
verboseln("Deps to add:")
|
||||
for _, a := range add {
|
||||
verboseln("\t", a.ImportPath)
|
||||
}
|
||||
verboseln("Adding new dependencies")
|
||||
err = copySrc(srcdir, add)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !VendorExperiment {
|
||||
f, _ := filepath.Split(srcdir)
|
||||
writeVCSIgnore(f)
|
||||
}
|
||||
var rewritePaths []string
|
||||
if saveR {
|
||||
for _, dep := range gnew.Deps {
|
||||
rewritePaths = append(rewritePaths, dep.ImportPath)
|
||||
}
|
||||
}
|
||||
verboseln("Rewriting paths (if necessary)")
|
||||
ppln(rewritePaths)
|
||||
return rewrite(projA, dp.ImportPath, rewritePaths)
|
||||
}
|
||||
|
||||
func printVersionWarnings(ov string) {
|
||||
var warning bool
|
||||
cv, err := goVersion()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Trim the old version because we may have saved it w/o trimming it
|
||||
// cv is already trimmed by goVersion()
|
||||
tov, err := trimGoVersion(ov)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tov != ov {
|
||||
log.Printf("WARNING: Recorded go version (%s) with minor version string found.\n", ov)
|
||||
warning = true
|
||||
}
|
||||
if cv != tov {
|
||||
log.Printf("WARNING: Recorded major go version (%s) and in-use major go version (%s) differ.\n", tov, cv)
|
||||
warning = true
|
||||
}
|
||||
if warning {
|
||||
log.Println("To record current major go version run `godep update -goversion`.")
|
||||
}
|
||||
}
|
||||
|
||||
type revError struct {
|
||||
ImportPath string
|
||||
WantRev string
|
||||
HavePath string
|
||||
HaveRev string
|
||||
}
|
||||
|
||||
func (v *revError) Error() string {
|
||||
return fmt.Sprintf("cannot save %s at revision %s: already have %s at revision %s.\n"+
|
||||
"Run `godep update %s' first.", v.ImportPath, v.WantRev, v.HavePath, v.HaveRev, v.HavePath)
|
||||
}
|
||||
|
||||
// carryVersions copies Rev and Comment from a to b for
|
||||
// each dependency with an identical ImportPath. For any
|
||||
// dependency in b that appears to be from the same repo
|
||||
// as one in a (for example, a parent or child directory),
|
||||
// the Rev must already match - otherwise it is an error.
|
||||
func carryVersions(a, b *Godeps) error {
|
||||
for i := range b.Deps {
|
||||
err := carryVersion(a, &b.Deps[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func carryVersion(a *Godeps, db *Dependency) error {
|
||||
// First see if this exact package is already in the list.
|
||||
for _, da := range a.Deps {
|
||||
if db.ImportPath == da.ImportPath {
|
||||
db.Rev = da.Rev
|
||||
db.Comment = da.Comment
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// No exact match, check for child or sibling package.
|
||||
// We can't handle mismatched versions for packages in
|
||||
// the same repo, so report that as an error.
|
||||
for _, da := range a.Deps {
|
||||
if strings.HasPrefix(db.ImportPath, da.ImportPath+"/") ||
|
||||
strings.HasPrefix(da.ImportPath, db.root+"/") {
|
||||
if da.Rev != db.Rev {
|
||||
return &revError{
|
||||
ImportPath: db.ImportPath,
|
||||
WantRev: db.Rev,
|
||||
HavePath: da.ImportPath,
|
||||
HaveRev: da.Rev,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// No related package in the list, must be a new repo.
|
||||
return nil
|
||||
}
|
||||
|
||||
// subDeps returns a - b, using ImportPath for equality.
|
||||
func subDeps(a, b []Dependency) (diff []Dependency) {
|
||||
Diff:
|
||||
for _, da := range a {
|
||||
for _, db := range b {
|
||||
if da.ImportPath == db.ImportPath {
|
||||
continue Diff
|
||||
}
|
||||
}
|
||||
diff = append(diff, da)
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func removeSrc(srcdir string, deps []Dependency) error {
|
||||
for _, dep := range deps {
|
||||
path := filepath.FromSlash(dep.ImportPath)
|
||||
err := os.RemoveAll(filepath.Join(srcdir, path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copySrc(dir string, deps []Dependency) error {
|
||||
// mapping to see if we visited a parent directory already
|
||||
visited := make(map[string]bool)
|
||||
ok := true
|
||||
for _, dep := range deps {
|
||||
debugln("copySrc for", dep.ImportPath)
|
||||
srcdir := filepath.Join(dep.ws, "src")
|
||||
rel, err := filepath.Rel(srcdir, dep.dir)
|
||||
debugln("srcdir", srcdir)
|
||||
debugln("rel", rel)
|
||||
debugln("err", err)
|
||||
if err != nil { // this should never happen
|
||||
return err
|
||||
}
|
||||
dstpkgroot := filepath.Join(dir, rel)
|
||||
err = os.RemoveAll(dstpkgroot)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ok = false
|
||||
}
|
||||
|
||||
// copy actual dependency
|
||||
vf := dep.vcs.listFiles(dep.dir)
|
||||
debugln("vf", vf)
|
||||
w := fs.Walk(dep.dir)
|
||||
for w.Step() {
|
||||
err = copyPkgFile(vf, dir, srcdir, w)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
// Look for legal files in root
|
||||
// some packages are imports as a sub-package but license info
|
||||
// is at root: exampleorg/common has license file in exampleorg
|
||||
//
|
||||
if dep.ImportPath == dep.root {
|
||||
// we are already at root
|
||||
continue
|
||||
}
|
||||
|
||||
// prevent copying twice This could happen if we have
|
||||
// two subpackages listed someorg/common and
|
||||
// someorg/anotherpack which has their license in
|
||||
// the parent dir of someorg
|
||||
rootdir := filepath.Join(srcdir, filepath.FromSlash(dep.root))
|
||||
if visited[rootdir] {
|
||||
continue
|
||||
}
|
||||
visited[rootdir] = true
|
||||
vf = dep.vcs.listFiles(rootdir)
|
||||
w = fs.Walk(rootdir)
|
||||
for w.Step() {
|
||||
fname := filepath.Base(w.Path())
|
||||
if IsLegalFile(fname) && !strings.Contains(w.Path(), sep) {
|
||||
err = copyPkgFile(vf, dir, srcdir, w)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return errorCopyingSourceCode
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyPkgFile(vf vcsFiles, dstroot, srcroot string, w *fs.Walker) error {
|
||||
if w.Err() != nil {
|
||||
return w.Err()
|
||||
}
|
||||
name := w.Stat().Name()
|
||||
if w.Stat().IsDir() {
|
||||
if name[0] == '.' || name[0] == '_' || (!saveT && name == "testdata") {
|
||||
// Skip directories starting with '.' or '_' or
|
||||
// 'testdata' (last is only skipped if saveT is false)
|
||||
w.SkipDir()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
rel, err := filepath.Rel(srcroot, w.Path())
|
||||
if err != nil { // this should never happen
|
||||
return err
|
||||
}
|
||||
if !saveT && strings.HasSuffix(name, "_test.go") {
|
||||
if verbose {
|
||||
log.Printf("save: skipping test file: %s", w.Path())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !vf.Contains(w.Path()) {
|
||||
if verbose {
|
||||
log.Printf("save: skipping untracked file: %s", w.Path())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return copyFile(filepath.Join(dstroot, rel), w.Path())
|
||||
}
|
||||
|
||||
// copyFile copies a regular file from src to dst.
|
||||
// dst is opened with os.Create.
|
||||
// If the file name ends with .go,
|
||||
// copyFile strips canonical import path annotations.
|
||||
// These are comments of the form:
|
||||
// package foo // import "bar/foo"
|
||||
// package foo /* import "bar/foo" */
|
||||
func copyFile(dst, src string) error {
|
||||
err := os.MkdirAll(filepath.Dir(dst), 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
linkDst, err := os.Readlink(src)
|
||||
if err == nil {
|
||||
return os.Symlink(linkDst, dst)
|
||||
}
|
||||
|
||||
si, err := stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
w, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(dst, si.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(dst, ".go") {
|
||||
debugln("Copy Without Import Comment", w, r)
|
||||
err = copyWithoutImportComment(w, r)
|
||||
} else {
|
||||
debugln("Copy (plain)", w, r)
|
||||
_, err = io.Copy(w, r)
|
||||
}
|
||||
err1 := w.Close()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func copyWithoutImportComment(w io.Writer, r io.Reader) error {
|
||||
b := bufio.NewReader(r)
|
||||
for {
|
||||
l, err := b.ReadBytes('\n')
|
||||
eof := err == io.EOF
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we have data then write it out...
|
||||
if len(l) > 0 {
|
||||
// Strip off \n if it exists because stripImportComment
|
||||
_, err := w.Write(append(stripImportComment(bytes.TrimRight(l, "\n")), '\n'))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if eof {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
importAnnotation = `import\s+(?:"[^"]*"|` + "`[^`]*`" + `)`
|
||||
importComment = `(?://\s*` + importAnnotation + `\s*$|/\*\s*` + importAnnotation + `\s*\*/)`
|
||||
)
|
||||
|
||||
var (
|
||||
importCommentRE = regexp.MustCompile(`^\s*(package\s+\w+)\s+` + importComment + `(.*)`)
|
||||
pkgPrefix = []byte("package ")
|
||||
)
|
||||
|
||||
// stripImportComment returns line with its import comment removed.
|
||||
// If s is not a package statement containing an import comment,
|
||||
// it is returned unaltered.
|
||||
// FIXME: expects lines w/o a \n at the end
|
||||
// See also http://golang.org/s/go14customimport.
|
||||
func stripImportComment(line []byte) []byte {
|
||||
if !bytes.HasPrefix(line, pkgPrefix) {
|
||||
// Fast path; this will skip all but one line in the file.
|
||||
// This assumes there is no whitespace before the keyword.
|
||||
return line
|
||||
}
|
||||
if m := importCommentRE.FindSubmatch(line); m != nil {
|
||||
return append(m[1], m[2]...)
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
// Func writeVCSIgnore writes "ignore" files inside dir for known VCSs,
|
||||
// so that dir/pkg and dir/bin don't accidentally get committed.
|
||||
// It logs any errors it encounters.
|
||||
func writeVCSIgnore(dir string) {
|
||||
// Currently git is the only VCS for which we know how to do this.
|
||||
// Mercurial and Bazaar have similar mechanisms, but they apparently
|
||||
// require writing files outside of dir.
|
||||
const ignore = "/pkg\n/bin\n"
|
||||
name := filepath.Join(dir, ".gitignore")
|
||||
err := writeFile(name, ignore)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
// writeFile is like ioutil.WriteFile but it creates
|
||||
// intermediate directories with os.MkdirAll.
|
||||
func writeFile(name, body string) error {
|
||||
err := os.MkdirAll(filepath.Dir(name), 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(name, []byte(body), 0666)
|
||||
}
|
||||
|
||||
const (
|
||||
// Readme contains the README text.
|
||||
Readme = `
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
||||
`
|
||||
needRestore = `
|
||||
mismatched versions while migrating
|
||||
|
||||
It looks like you are switching from the old Godeps format
|
||||
(from flag -copy=false). The old format is just a file; it
|
||||
doesn't contain source code. For this migration, godep needs
|
||||
the appropriate version of each dependency to be installed in
|
||||
GOPATH, so that the source code is available to copy.
|
||||
|
||||
To fix this, run 'godep restore'.
|
||||
`
|
||||
)
|
|
@ -0,0 +1,292 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var cmdUpdate = &Command{
|
||||
Name: "update",
|
||||
Args: "[-goversion] [packages]",
|
||||
Short: "update selected packages or the go version",
|
||||
Long: `
|
||||
Update changes the named dependency packages to use the
|
||||
revision of each currently installed in GOPATH. New code will
|
||||
be copied into the Godeps workspace or vendor folder and the
|
||||
new revision will be written to the manifest.
|
||||
|
||||
If -goversion is specified, update the recorded go version.
|
||||
|
||||
For more about specifying packages, see 'go help packages'.
|
||||
`,
|
||||
Run: runUpdate,
|
||||
OnlyInGOPATH: true,
|
||||
}
|
||||
|
||||
var (
|
||||
updateGoVer bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdUpdate.Flag.BoolVar(&saveT, "t", false, "save test files during update")
|
||||
cmdUpdate.Flag.BoolVar(&updateGoVer, "goversion", false, "update the recorded go version")
|
||||
}
|
||||
|
||||
func runUpdate(cmd *Command, args []string) {
|
||||
if updateGoVer {
|
||||
err := updateGoVersion()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
if len(args) > 0 {
|
||||
err := update(args)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateGoVersion() error {
|
||||
gold, err := loadDefaultGodepsFile()
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cv, err := goVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gv := gold.GoVersion
|
||||
gold.GoVersion = cv
|
||||
_, err = gold.save()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gv != cv {
|
||||
log.Println("Updated major go version to", cv)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func update(args []string) error {
|
||||
if len(args) == 0 {
|
||||
args = []string{"."}
|
||||
}
|
||||
g, err := loadDefaultGodepsFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, arg := range args {
|
||||
arg := path.Clean(arg)
|
||||
any := markMatches(arg, g.Deps)
|
||||
if !any {
|
||||
log.Println("not in manifest:", arg)
|
||||
}
|
||||
}
|
||||
deps, rdeps, err := LoadVCSAndUpdate(g.Deps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(deps) == 0 {
|
||||
return errorNoPackagesUpdatable
|
||||
}
|
||||
g.addOrUpdateDeps(deps)
|
||||
g.removeDeps(rdeps)
|
||||
if _, err = g.save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcdir := relativeVendorTarget(VendorExperiment)
|
||||
if err := removeSrc(filepath.FromSlash(strings.Trim(sep, "/")), rdeps); err != nil {
|
||||
return err
|
||||
}
|
||||
copySrc(srcdir, deps)
|
||||
|
||||
ok, err := needRewrite(g.Packages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var rewritePaths []string
|
||||
if ok {
|
||||
for _, dep := range g.Deps {
|
||||
rewritePaths = append(rewritePaths, dep.ImportPath)
|
||||
}
|
||||
}
|
||||
return rewrite(nil, g.ImportPath, rewritePaths)
|
||||
}
|
||||
|
||||
func needRewrite(importPaths []string) (bool, error) {
|
||||
if len(importPaths) == 0 {
|
||||
importPaths = []string{"."}
|
||||
}
|
||||
a, err := LoadPackages(importPaths...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, p := range a {
|
||||
for _, name := range p.allGoFiles() {
|
||||
path := filepath.Join(p.Dir, name)
|
||||
hasSep, err := hasRewrittenImportStatement(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if hasSep {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func hasRewrittenImportStatement(path string) (bool, error) {
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, path, nil, 0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, s := range f.Imports {
|
||||
name, _ := strconv.Unquote(s.Path.Value)
|
||||
if strings.Contains(name, sep) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// markMatches marks each entry in deps with an import path that
|
||||
// matches pat. It returns whether any matches occurred.
|
||||
func markMatches(pat string, deps []Dependency) (matched bool) {
|
||||
f := matchPattern(pat)
|
||||
for i, dep := range deps {
|
||||
if f(dep.ImportPath) {
|
||||
deps[i].matched = true
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
func fillDeps(deps []Dependency) ([]Dependency, error) {
|
||||
for i := range deps {
|
||||
if deps[i].pkg != nil {
|
||||
continue
|
||||
}
|
||||
ps, err := LoadPackages(deps[i].ImportPath)
|
||||
if err != nil {
|
||||
if _, ok := err.(errPackageNotFound); ok {
|
||||
deps[i].missing = true
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if len(ps) > 1 {
|
||||
panic("More than one package found for " + deps[i].ImportPath)
|
||||
}
|
||||
p := ps[0]
|
||||
deps[i].pkg = p
|
||||
deps[i].dir = p.Dir
|
||||
deps[i].ws = p.Root
|
||||
|
||||
vcs, reporoot, err := VCSFromDir(p.Dir, filepath.Join(p.Root, "src"))
|
||||
if err != nil {
|
||||
return nil, errorLoadingDeps
|
||||
}
|
||||
deps[i].root = filepath.ToSlash(reporoot)
|
||||
deps[i].vcs = vcs
|
||||
}
|
||||
|
||||
return deps, nil
|
||||
}
|
||||
|
||||
// LoadVCSAndUpdate loads and updates a set of dependencies.
|
||||
func LoadVCSAndUpdate(deps []Dependency) ([]Dependency, []Dependency, error) {
|
||||
var err1 error
|
||||
|
||||
deps, err := fillDeps(deps)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repoMask := make(map[string]bool)
|
||||
for i := range deps {
|
||||
if !deps[i].matched {
|
||||
repoMask[deps[i].root] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if we need any new packages because of new transitive imports
|
||||
for _, dep := range deps {
|
||||
if !dep.matched || dep.missing {
|
||||
continue
|
||||
}
|
||||
for _, dp := range dep.pkg.Dependencies {
|
||||
if dp.Goroot {
|
||||
continue
|
||||
}
|
||||
var have bool
|
||||
for _, d := range deps {
|
||||
if d.ImportPath == dp.ImportPath {
|
||||
have = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !have {
|
||||
deps = append(deps, Dependency{ImportPath: dp.ImportPath, matched: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deps, err = fillDeps(deps)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var toUpdate, toRemove []Dependency
|
||||
for _, d := range deps {
|
||||
if !d.matched || repoMask[d.root] {
|
||||
continue
|
||||
}
|
||||
if d.missing {
|
||||
toRemove = append(toRemove, d)
|
||||
continue
|
||||
}
|
||||
toUpdate = append(toUpdate, d)
|
||||
}
|
||||
|
||||
debugln("toUpdate")
|
||||
ppln(toUpdate)
|
||||
|
||||
var toCopy []Dependency
|
||||
for _, d := range toUpdate {
|
||||
id, err := d.vcs.identify(d.dir)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
err1 = errorLoadingDeps
|
||||
continue
|
||||
}
|
||||
if d.vcs.isDirty(d.dir, id) {
|
||||
log.Println("dirty working tree (please commit changes):", d.dir)
|
||||
}
|
||||
d.Rev = id
|
||||
d.Comment = d.vcs.describe(d.dir, id)
|
||||
toCopy = append(toCopy, d)
|
||||
}
|
||||
debugln("toCopy")
|
||||
ppln(toCopy)
|
||||
|
||||
if err1 != nil {
|
||||
return nil, nil, err1
|
||||
}
|
||||
return toCopy, toRemove, nil
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Runs a command in dir.
|
||||
// The name and args are as in exec.Command.
|
||||
// Stdout, stderr, and the environment are inherited
|
||||
// from the current process.
|
||||
func runIn(dir, name string, args ...string) error {
|
||||
_, err := runInWithOutput(dir, name, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func runInWithOutput(dir, name string, args ...string) (string, error) {
|
||||
c := exec.Command(name, args...)
|
||||
c.Dir = dir
|
||||
o, err := c.CombinedOutput()
|
||||
|
||||
if debug {
|
||||
fmt.Printf("execute: %+v\n", c)
|
||||
fmt.Printf(" output: %s\n", string(o))
|
||||
}
|
||||
|
||||
return string(o), err
|
||||
}
|
||||
|
||||
// driveLetterToUpper converts Windows path's drive letters to uppercase. This
|
||||
// is needed when comparing 2 paths with different drive letter case.
|
||||
func driveLetterToUpper(path string) string {
|
||||
if runtime.GOOS != "windows" || path == "" {
|
||||
return path
|
||||
}
|
||||
|
||||
p := path
|
||||
|
||||
// If path's drive letter is lowercase, change it to uppercase.
|
||||
if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' {
|
||||
p = string(p[0]+'A'-'a') + p[1:]
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// clean the path and ensure that a drive letter is upper case (if it exists).
|
||||
func cleanPath(path string) string {
|
||||
return driveLetterToUpper(filepath.Clean(path))
|
||||
}
|
||||
|
||||
// deal with case insensitive filesystems and other weirdness
|
||||
func pathEqual(a, b string) bool {
|
||||
a = cleanPath(a)
|
||||
b = cleanPath(b)
|
||||
return strings.EqualFold(a, b)
|
||||
}
|
|
@ -0,0 +1,322 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/vcs"
|
||||
)
|
||||
|
||||
// VCS represents a version control system.
|
||||
type VCS struct {
|
||||
vcs *vcs.Cmd
|
||||
|
||||
IdentifyCmd string
|
||||
DescribeCmd string
|
||||
DiffCmd string
|
||||
ListCmd string
|
||||
RootCmd string
|
||||
|
||||
// run in sandbox repos
|
||||
ExistsCmd string
|
||||
}
|
||||
|
||||
var vcsBzr = &VCS{
|
||||
vcs: vcs.ByCmd("bzr"),
|
||||
|
||||
IdentifyCmd: "version-info --custom --template {revision_id}",
|
||||
DescribeCmd: "revno", // TODO(kr): find tag names if possible
|
||||
DiffCmd: "diff -r {rev}",
|
||||
ListCmd: "ls --from-root -R",
|
||||
RootCmd: "root",
|
||||
}
|
||||
|
||||
var vcsGit = &VCS{
|
||||
vcs: vcs.ByCmd("git"),
|
||||
|
||||
IdentifyCmd: "rev-parse HEAD",
|
||||
DescribeCmd: "describe --tags",
|
||||
DiffCmd: "diff {rev}",
|
||||
ListCmd: "ls-files --full-name",
|
||||
RootCmd: "rev-parse --show-cdup",
|
||||
|
||||
ExistsCmd: "cat-file -e {rev}",
|
||||
}
|
||||
|
||||
var vcsHg = &VCS{
|
||||
vcs: vcs.ByCmd("hg"),
|
||||
|
||||
IdentifyCmd: "parents --template {node}",
|
||||
DescribeCmd: "log -r . --template {latesttag}-{latesttagdistance}",
|
||||
DiffCmd: "diff -r {rev}",
|
||||
ListCmd: "status --all --no-status",
|
||||
RootCmd: "root",
|
||||
|
||||
ExistsCmd: "cat -r {rev} .",
|
||||
}
|
||||
|
||||
var cmd = map[*vcs.Cmd]*VCS{
|
||||
vcsBzr.vcs: vcsBzr,
|
||||
vcsGit.vcs: vcsGit,
|
||||
vcsHg.vcs: vcsHg,
|
||||
}
|
||||
|
||||
// VCSFromDir returns a VCS value from a directory.
|
||||
func VCSFromDir(dir, srcRoot string) (*VCS, string, error) {
|
||||
vcscmd, reporoot, err := vcs.FromDir(dir, srcRoot)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error while inspecting %q: %v", dir, err)
|
||||
}
|
||||
vcsext := cmd[vcscmd]
|
||||
if vcsext == nil {
|
||||
return nil, "", fmt.Errorf("%s is unsupported: %s", vcscmd.Name, dir)
|
||||
}
|
||||
return vcsext, reporoot, nil
|
||||
}
|
||||
|
||||
// VCSForImportPath returns a VCS value for an import path.
|
||||
func VCSForImportPath(importPath string) (*VCS, error) {
|
||||
rr, err := vcs.RepoRootForImportPath(importPath, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vcs := cmd[rr.VCS]
|
||||
if vcs == nil {
|
||||
return nil, fmt.Errorf("%s is unsupported: %s", rr.VCS.Name, importPath)
|
||||
}
|
||||
return vcs, nil
|
||||
}
|
||||
|
||||
func (v *VCS) identify(dir string) (string, error) {
|
||||
out, err := v.runOutput(dir, v.IdentifyCmd)
|
||||
return string(bytes.TrimSpace(out)), err
|
||||
}
|
||||
|
||||
func absRoot(dir, out string) string {
|
||||
if filepath.IsAbs(out) {
|
||||
return filepath.Clean(out)
|
||||
}
|
||||
return filepath.Join(dir, out)
|
||||
}
|
||||
|
||||
func (v *VCS) root(dir string) (string, error) {
|
||||
out, err := v.runOutput(dir, v.RootCmd)
|
||||
return absRoot(dir, string(bytes.TrimSpace(out))), err
|
||||
}
|
||||
|
||||
func (v *VCS) describe(dir, rev string) string {
|
||||
out, err := v.runOutputVerboseOnly(dir, v.DescribeCmd, "rev", rev)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(bytes.TrimSpace(out))
|
||||
}
|
||||
|
||||
func (v *VCS) isDirty(dir, rev string) bool {
|
||||
out, err := v.runOutput(dir, v.DiffCmd, "rev", rev)
|
||||
return err != nil || len(out) != 0
|
||||
}
|
||||
|
||||
type vcsFiles map[string]bool
|
||||
|
||||
func (vf vcsFiles) Contains(path string) bool {
|
||||
// Fast path, we have the path
|
||||
if vf[path] {
|
||||
return true
|
||||
}
|
||||
|
||||
// Slow path for case insensitive filesystems
|
||||
// See #310
|
||||
for f := range vf {
|
||||
if pathEqual(f, path) {
|
||||
return true
|
||||
}
|
||||
// git's root command (maybe other vcs as well) resolve symlinks, so try that too
|
||||
// FIXME: rev-parse --show-cdup + extra logic will fix this for git but also need to validate the other vcs commands. This is maybe temporary.
|
||||
p, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if pathEqual(f, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// No matches by either method
|
||||
return false
|
||||
}
|
||||
|
||||
// listFiles tracked by the VCS in the repo that contains dir, converted to absolute path.
|
||||
func (v *VCS) listFiles(dir string) vcsFiles {
|
||||
root, err := v.root(dir)
|
||||
debugln("vcs dir", dir)
|
||||
debugln("vcs root", root)
|
||||
ppln(v)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out, err := v.runOutput(dir, v.ListCmd)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
files := make(vcsFiles)
|
||||
for _, file := range bytes.Split(out, []byte{'\n'}) {
|
||||
if len(file) > 0 {
|
||||
path, err := filepath.Abs(filepath.Join(root, string(file)))
|
||||
if err != nil {
|
||||
panic(err) // this should not happen
|
||||
}
|
||||
|
||||
if pathEqual(filepath.Dir(path), dir) {
|
||||
files[path] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func (v *VCS) exists(dir, rev string) bool {
|
||||
err := v.runVerboseOnly(dir, v.ExistsCmd, "rev", rev)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// RevSync checks out the revision given by rev in dir.
|
||||
// The dir must exist and rev must be a valid revision.
|
||||
func (v *VCS) RevSync(dir, rev string) error {
|
||||
return v.run(dir, v.vcs.TagSyncCmd, "tag", rev)
|
||||
}
|
||||
|
||||
// run runs the command line cmd in the given directory.
|
||||
// keyval is a list of key, value pairs. run expands
|
||||
// instances of {key} in cmd into value, but only after
|
||||
// splitting cmd into individual arguments.
|
||||
// If an error occurs, run prints the command line and the
|
||||
// command's combined stdout+stderr to standard error.
|
||||
// Otherwise run discards the command's output.
|
||||
func (v *VCS) run(dir string, cmdline string, kv ...string) error {
|
||||
_, err := v.run1(dir, cmdline, kv, true)
|
||||
return err
|
||||
}
|
||||
|
||||
// runVerboseOnly is like run but only generates error output to standard error in verbose mode.
|
||||
func (v *VCS) runVerboseOnly(dir string, cmdline string, kv ...string) error {
|
||||
_, err := v.run1(dir, cmdline, kv, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// runOutput is like run but returns the output of the command.
|
||||
func (v *VCS) runOutput(dir string, cmdline string, kv ...string) ([]byte, error) {
|
||||
return v.run1(dir, cmdline, kv, true)
|
||||
}
|
||||
|
||||
// runOutputVerboseOnly is like runOutput but only generates error output to standard error in verbose mode.
|
||||
func (v *VCS) runOutputVerboseOnly(dir string, cmdline string, kv ...string) ([]byte, error) {
|
||||
return v.run1(dir, cmdline, kv, false)
|
||||
}
|
||||
|
||||
// run1 is the generalized implementation of run and runOutput.
|
||||
func (v *VCS) run1(dir string, cmdline string, kv []string, verbose bool) ([]byte, error) {
|
||||
m := make(map[string]string)
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
m[kv[i]] = kv[i+1]
|
||||
}
|
||||
args := strings.Fields(cmdline)
|
||||
for i, arg := range args {
|
||||
args[i] = expand(m, arg)
|
||||
}
|
||||
|
||||
_, err := exec.LookPath(v.vcs.Cmd)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "godep: missing %s command.\n", v.vcs.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(v.vcs.Cmd, args...)
|
||||
cmd.Dir = dir
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
err = cmd.Run()
|
||||
out := buf.Bytes()
|
||||
if err != nil {
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.vcs.Cmd, strings.Join(args, " "))
|
||||
os.Stderr.Write(out)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func expand(m map[string]string, s string) string {
|
||||
for k, v := range m {
|
||||
s = strings.Replace(s, "{"+k+"}", v, -1)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Mercurial has no command equivalent to git remote add.
|
||||
// We handle it as a special case in process.
|
||||
func hgLink(dir, remote, url string) error {
|
||||
hgdir := filepath.Join(dir, ".hg")
|
||||
if err := os.MkdirAll(hgdir, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(hgdir, "hgrc")
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(f, "[paths]\n%s = %s\n", remote, url)
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
func gitDetached(r string) (bool, error) {
|
||||
o, err := vcsGit.runOutput(r, "status")
|
||||
if err != nil {
|
||||
return false, errors.New("unable to determine git status " + err.Error())
|
||||
}
|
||||
return bytes.Contains(o, []byte("HEAD detached at")), nil
|
||||
}
|
||||
|
||||
func gitDefaultBranch(r string) (string, error) {
|
||||
o, err := vcsGit.runOutput(r, "remote show origin")
|
||||
if err != nil {
|
||||
return "", errors.New("Running git remote show origin errored with: " + err.Error())
|
||||
}
|
||||
return gitDetermineDefaultBranch(r, string(o))
|
||||
}
|
||||
|
||||
func gitDetermineDefaultBranch(r, o string) (string, error) {
|
||||
e := "Unable to determine HEAD branch: "
|
||||
hb := "HEAD branch:"
|
||||
lbcfgp := "Local branch configured for 'git pull':"
|
||||
s := strings.Index(o, hb)
|
||||
if s < 0 {
|
||||
b := strings.Index(o, lbcfgp)
|
||||
if b < 0 {
|
||||
return "", errors.New(e + "Remote HEAD is ambiguous. Before godep can pull new commits you will need to:" + `
|
||||
cd ` + r + `
|
||||
git checkout <a HEAD branch>
|
||||
Here is what was reported:
|
||||
` + o)
|
||||
}
|
||||
s = b + len(lbcfgp)
|
||||
} else {
|
||||
s += len(hb)
|
||||
}
|
||||
f := strings.Fields(o[s:])
|
||||
if len(f) < 3 {
|
||||
return "", errors.New(e + "git output too short")
|
||||
}
|
||||
return f[0], nil
|
||||
}
|
||||
|
||||
func gitCheckout(r, b string) error {
|
||||
return vcsGit.run(r, "checkout "+b)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const version = 79
|
||||
|
||||
var cmdVersion = &Command{
|
||||
Name: "version",
|
||||
Short: "show version info",
|
||||
Long: `
|
||||
|
||||
Displays the version of godep as well as the target OS, architecture and go runtime version.
|
||||
`,
|
||||
Run: runVersion,
|
||||
}
|
||||
|
||||
func versionString() string {
|
||||
return fmt.Sprintf("godep v%d (%s/%s/%s)", version, runtime.GOOS, runtime.GOARCH, runtime.Version())
|
||||
}
|
||||
|
||||
func runVersion(cmd *Command, args []string) {
|
||||
fmt.Printf("%s\n", versionString())
|
||||
}
|
||||
|
||||
func GoVersionFields(c rune) bool {
|
||||
return c == 'g' || c == 'o' || c == '.'
|
||||
}
|
||||
|
||||
// isSameOrNewer go version (goA.B)
|
||||
// go1.6 >= go1.6 == true
|
||||
// go1.5 >= go1.6 == false
|
||||
func isSameOrNewer(base, check string) bool {
|
||||
if base == check {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(check, "devel-") {
|
||||
return true
|
||||
}
|
||||
bp := strings.FieldsFunc(base, GoVersionFields)
|
||||
cp := strings.FieldsFunc(check, GoVersionFields)
|
||||
if len(bp) < 2 || len(cp) < 2 {
|
||||
log.Fatalf("Error comparing %s to %s\n", base, check)
|
||||
}
|
||||
if bp[0] == cp[0] { // We only have go version 1 right now
|
||||
bm, err := strconv.Atoi(bp[1])
|
||||
// These errors are unlikely and there is nothing nice to do here anyway
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cm, err := strconv.Atoi(cp[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cm >= bm
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"discovery.go",
|
||||
"env.go",
|
||||
"http.go",
|
||||
"vcs.go",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vcs
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// charsetReader returns a reader for the given charset. Currently
|
||||
// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
|
||||
// error which is printed by go get, so the user can find why the package
|
||||
// wasn't downloaded if the encoding is not supported. Note that, in
|
||||
// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
|
||||
// greater than 0x7f are not rejected).
|
||||
func charsetReader(charset string, input io.Reader) (io.Reader, error) {
|
||||
switch strings.ToLower(charset) {
|
||||
case "ascii":
|
||||
return input, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
|
||||
}
|
||||
}
|
||||
|
||||
// parseMetaGoImports returns meta imports from the HTML in r.
|
||||
// Parsing ends at the end of the <head> section or the beginning of the <body>.
|
||||
func parseMetaGoImports(r io.Reader) (imports []metaImport, err error) {
|
||||
d := xml.NewDecoder(r)
|
||||
d.CharsetReader = charsetReader
|
||||
d.Strict = false
|
||||
var t xml.Token
|
||||
for {
|
||||
t, err = d.Token()
|
||||
if err != nil {
|
||||
if err == io.EOF || len(imports) > 0 {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
|
||||
return
|
||||
}
|
||||
if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
|
||||
return
|
||||
}
|
||||
e, ok := t.(xml.StartElement)
|
||||
if !ok || !strings.EqualFold(e.Name.Local, "meta") {
|
||||
continue
|
||||
}
|
||||
if attrValue(e.Attr, "name") != "go-import" {
|
||||
continue
|
||||
}
|
||||
if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
|
||||
imports = append(imports, metaImport{
|
||||
Prefix: f[0],
|
||||
VCS: f[1],
|
||||
RepoRoot: f[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// attrValue returns the attribute value for the case-insensitive key
|
||||
// `name', or the empty string if nothing is found.
|
||||
func attrValue(attrs []xml.Attr, name string) string {
|
||||
for _, a := range attrs {
|
||||
if strings.EqualFold(a.Name.Local, name) {
|
||||
return a.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// 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 vcs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// envForDir returns a copy of the environment
|
||||
// suitable for running in the given directory.
|
||||
// The environment is the current process's environment
|
||||
// but with an updated $PWD, so that an os.Getwd in the
|
||||
// child will be faster.
|
||||
func envForDir(dir string) []string {
|
||||
env := os.Environ()
|
||||
// Internally we only use rooted paths, so dir is rooted.
|
||||
// Even if dir is not rooted, no harm done.
|
||||
return mergeEnvLists([]string{"PWD=" + dir}, env)
|
||||
}
|
||||
|
||||
// mergeEnvLists merges the two environment lists such that
|
||||
// variables with the same name in "in" replace those in "out".
|
||||
func mergeEnvLists(in, out []string) []string {
|
||||
NextVar:
|
||||
for _, inkv := range in {
|
||||
k := strings.SplitAfterN(inkv, "=", 2)[0]
|
||||
for i, outkv := range out {
|
||||
if strings.HasPrefix(outkv, k) {
|
||||
out[i] = inkv
|
||||
continue NextVar
|
||||
}
|
||||
}
|
||||
out = append(out, inkv)
|
||||
}
|
||||
return out
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// httpClient is the default HTTP client, but a variable so it can be
|
||||
// changed by tests, without modifying http.DefaultClient.
|
||||
var httpClient = http.DefaultClient
|
||||
|
||||
// httpGET returns the data from an HTTP GET request for the given URL.
|
||||
func httpGET(url string) ([]byte, error) {
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("%s: %s", url, resp.Status)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %v", url, err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// httpsOrHTTP returns the body of either the importPath's
|
||||
// https resource or, if unavailable, the http resource.
|
||||
func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err error) {
|
||||
fetch := func(scheme string) (urlStr string, res *http.Response, err error) {
|
||||
u, err := url.Parse(scheme + "://" + importPath)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
u.RawQuery = "go-get=1"
|
||||
urlStr = u.String()
|
||||
if Verbose {
|
||||
log.Printf("Fetching %s", urlStr)
|
||||
}
|
||||
res, err = httpClient.Get(urlStr)
|
||||
return
|
||||
}
|
||||
closeBody := func(res *http.Response) {
|
||||
if res != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
}
|
||||
urlStr, res, err := fetch("https")
|
||||
if err != nil || res.StatusCode != 200 {
|
||||
if Verbose {
|
||||
if err != nil {
|
||||
log.Printf("https fetch failed.")
|
||||
} else {
|
||||
log.Printf("ignoring https fetch with status code %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
closeBody(res)
|
||||
urlStr, res, err = fetch("http")
|
||||
}
|
||||
if err != nil {
|
||||
closeBody(res)
|
||||
return "", nil, err
|
||||
}
|
||||
// Note: accepting a non-200 OK here, so people can serve a
|
||||
// meta import in their http 404 page.
|
||||
if Verbose {
|
||||
log.Printf("Parsing meta tags from %s (status code %d)", urlStr, res.StatusCode)
|
||||
}
|
||||
return urlStr, res.Body, nil
|
||||
}
|
|
@ -0,0 +1,710 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vcs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Verbose enables verbose operation logging.
|
||||
var Verbose bool
|
||||
|
||||
// ShowCmd controls whether VCS commands are printed.
|
||||
var ShowCmd bool
|
||||
|
||||
// A Cmd describes how to use a version control system
|
||||
// like Mercurial, Git, or Subversion.
|
||||
type Cmd struct {
|
||||
Name string
|
||||
Cmd string // name of binary to invoke command
|
||||
|
||||
CreateCmd string // command to download a fresh copy of a repository
|
||||
DownloadCmd string // command to download updates into an existing repository
|
||||
|
||||
TagCmd []TagCmd // commands to list tags
|
||||
TagLookupCmd []TagCmd // commands to lookup tags before running tagSyncCmd
|
||||
TagSyncCmd string // command to sync to specific tag
|
||||
TagSyncDefault string // command to sync to default tag
|
||||
|
||||
LogCmd string // command to list repository changelogs in an XML format
|
||||
|
||||
Scheme []string
|
||||
PingCmd string
|
||||
}
|
||||
|
||||
// A TagCmd describes a command to list available tags
|
||||
// that can be passed to Cmd.TagSyncCmd.
|
||||
type TagCmd struct {
|
||||
Cmd string // command to list tags
|
||||
Pattern string // regexp to extract tags from list
|
||||
}
|
||||
|
||||
// vcsList lists the known version control systems
|
||||
var vcsList = []*Cmd{
|
||||
vcsHg,
|
||||
vcsGit,
|
||||
vcsSvn,
|
||||
vcsBzr,
|
||||
}
|
||||
|
||||
// ByCmd returns the version control system for the given
|
||||
// command name (hg, git, svn, bzr).
|
||||
func ByCmd(cmd string) *Cmd {
|
||||
for _, vcs := range vcsList {
|
||||
if vcs.Cmd == cmd {
|
||||
return vcs
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// vcsHg describes how to use Mercurial.
|
||||
var vcsHg = &Cmd{
|
||||
Name: "Mercurial",
|
||||
Cmd: "hg",
|
||||
|
||||
CreateCmd: "clone -U {repo} {dir}",
|
||||
DownloadCmd: "pull",
|
||||
|
||||
// We allow both tag and branch names as 'tags'
|
||||
// for selecting a version. This lets people have
|
||||
// a go.release.r60 branch and a go1 branch
|
||||
// and make changes in both, without constantly
|
||||
// editing .hgtags.
|
||||
TagCmd: []TagCmd{
|
||||
{"tags", `^(\S+)`},
|
||||
{"branches", `^(\S+)`},
|
||||
},
|
||||
TagSyncCmd: "update -r {tag}",
|
||||
TagSyncDefault: "update default",
|
||||
|
||||
LogCmd: "log --encoding=utf-8 --limit={limit} --template={template}",
|
||||
|
||||
Scheme: []string{"https", "http", "ssh"},
|
||||
PingCmd: "identify {scheme}://{repo}",
|
||||
}
|
||||
|
||||
// vcsGit describes how to use Git.
|
||||
var vcsGit = &Cmd{
|
||||
Name: "Git",
|
||||
Cmd: "git",
|
||||
|
||||
CreateCmd: "clone {repo} {dir}",
|
||||
DownloadCmd: "pull --ff-only",
|
||||
|
||||
TagCmd: []TagCmd{
|
||||
// tags/xxx matches a git tag named xxx
|
||||
// origin/xxx matches a git branch named xxx on the default remote repository
|
||||
{"show-ref", `(?:tags|origin)/(\S+)$`},
|
||||
},
|
||||
TagLookupCmd: []TagCmd{
|
||||
{"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
|
||||
},
|
||||
TagSyncCmd: "checkout {tag}",
|
||||
TagSyncDefault: "checkout master",
|
||||
|
||||
Scheme: []string{"git", "https", "http", "git+ssh"},
|
||||
PingCmd: "ls-remote {scheme}://{repo}",
|
||||
}
|
||||
|
||||
// vcsBzr describes how to use Bazaar.
|
||||
var vcsBzr = &Cmd{
|
||||
Name: "Bazaar",
|
||||
Cmd: "bzr",
|
||||
|
||||
CreateCmd: "branch {repo} {dir}",
|
||||
|
||||
// Without --overwrite bzr will not pull tags that changed.
|
||||
// Replace by --overwrite-tags after http://pad.lv/681792 goes in.
|
||||
DownloadCmd: "pull --overwrite",
|
||||
|
||||
TagCmd: []TagCmd{{"tags", `^(\S+)`}},
|
||||
TagSyncCmd: "update -r {tag}",
|
||||
TagSyncDefault: "update -r revno:-1",
|
||||
|
||||
Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
|
||||
PingCmd: "info {scheme}://{repo}",
|
||||
}
|
||||
|
||||
// vcsSvn describes how to use Subversion.
|
||||
var vcsSvn = &Cmd{
|
||||
Name: "Subversion",
|
||||
Cmd: "svn",
|
||||
|
||||
CreateCmd: "checkout {repo} {dir}",
|
||||
DownloadCmd: "update",
|
||||
|
||||
// There is no tag command in subversion.
|
||||
// The branch information is all in the path names.
|
||||
|
||||
LogCmd: "log --xml --limit={limit}",
|
||||
|
||||
Scheme: []string{"https", "http", "svn", "svn+ssh"},
|
||||
PingCmd: "info {scheme}://{repo}",
|
||||
}
|
||||
|
||||
func (v *Cmd) String() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
// run runs the command line cmd in the given directory.
|
||||
// keyval is a list of key, value pairs. run expands
|
||||
// instances of {key} in cmd into value, but only after
|
||||
// splitting cmd into individual arguments.
|
||||
// If an error occurs, run prints the command line and the
|
||||
// command's combined stdout+stderr to standard error.
|
||||
// Otherwise run discards the command's output.
|
||||
func (v *Cmd) run(dir string, cmd string, keyval ...string) error {
|
||||
_, err := v.run1(dir, cmd, keyval, true)
|
||||
return err
|
||||
}
|
||||
|
||||
// runVerboseOnly is like run but only generates error output to standard error in verbose mode.
|
||||
func (v *Cmd) runVerboseOnly(dir string, cmd string, keyval ...string) error {
|
||||
_, err := v.run1(dir, cmd, keyval, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// runOutput is like run but returns the output of the command.
|
||||
func (v *Cmd) runOutput(dir string, cmd string, keyval ...string) ([]byte, error) {
|
||||
return v.run1(dir, cmd, keyval, true)
|
||||
}
|
||||
|
||||
// run1 is the generalized implementation of run and runOutput.
|
||||
func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([]byte, error) {
|
||||
m := make(map[string]string)
|
||||
for i := 0; i < len(keyval); i += 2 {
|
||||
m[keyval[i]] = keyval[i+1]
|
||||
}
|
||||
args := strings.Fields(cmdline)
|
||||
for i, arg := range args {
|
||||
args[i] = expand(m, arg)
|
||||
}
|
||||
|
||||
_, err := exec.LookPath(v.Cmd)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"go: missing %s command. See http://golang.org/s/gogetcmd\n",
|
||||
v.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(v.Cmd, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = envForDir(cmd.Dir)
|
||||
if ShowCmd {
|
||||
fmt.Printf("cd %s\n", dir)
|
||||
fmt.Printf("%s %s\n", v.Cmd, strings.Join(args, " "))
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
err = cmd.Run()
|
||||
out := buf.Bytes()
|
||||
if err != nil {
|
||||
if verbose || Verbose {
|
||||
fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.Cmd, strings.Join(args, " "))
|
||||
os.Stderr.Write(out)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Ping pings the repo to determine if scheme used is valid.
|
||||
// This repo must be pingable with this scheme and VCS.
|
||||
func (v *Cmd) Ping(scheme, repo string) error {
|
||||
return v.runVerboseOnly(".", v.PingCmd, "scheme", scheme, "repo", repo)
|
||||
}
|
||||
|
||||
// Create creates a new copy of repo in dir.
|
||||
// The parent of dir must exist; dir must not.
|
||||
func (v *Cmd) Create(dir, repo string) error {
|
||||
return v.run(".", v.CreateCmd, "dir", dir, "repo", repo)
|
||||
}
|
||||
|
||||
// CreateAtRev creates a new copy of repo in dir at revision rev.
|
||||
// The parent of dir must exist; dir must not.
|
||||
// rev must be a valid revision in repo.
|
||||
func (v *Cmd) CreateAtRev(dir, repo, rev string) error {
|
||||
if err := v.Create(dir, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
return v.run(dir, v.TagSyncCmd, "tag", rev)
|
||||
}
|
||||
|
||||
// Download downloads any new changes for the repo in dir.
|
||||
// dir must be a valid VCS repo compatible with v.
|
||||
func (v *Cmd) Download(dir string) error {
|
||||
return v.run(dir, v.DownloadCmd)
|
||||
}
|
||||
|
||||
// Tags returns the list of available tags for the repo in dir.
|
||||
// dir must be a valid VCS repo compatible with v.
|
||||
func (v *Cmd) Tags(dir string) ([]string, error) {
|
||||
var tags []string
|
||||
for _, tc := range v.TagCmd {
|
||||
out, err := v.runOutput(dir, tc.Cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
re := regexp.MustCompile(`(?m-s)` + tc.Pattern)
|
||||
for _, m := range re.FindAllStringSubmatch(string(out), -1) {
|
||||
tags = append(tags, m[1])
|
||||
}
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// TagSync syncs the repo in dir to the named tag, which is either a
|
||||
// tag returned by Tags or the empty string (the default tag).
|
||||
// dir must be a valid VCS repo compatible with v and the tag must exist.
|
||||
func (v *Cmd) TagSync(dir, tag string) error {
|
||||
if v.TagSyncCmd == "" {
|
||||
return nil
|
||||
}
|
||||
if tag != "" {
|
||||
for _, tc := range v.TagLookupCmd {
|
||||
out, err := v.runOutput(dir, tc.Cmd, "tag", tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re := regexp.MustCompile(`(?m-s)` + tc.Pattern)
|
||||
m := re.FindStringSubmatch(string(out))
|
||||
if len(m) > 1 {
|
||||
tag = m[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if tag == "" && v.TagSyncDefault != "" {
|
||||
return v.run(dir, v.TagSyncDefault)
|
||||
}
|
||||
return v.run(dir, v.TagSyncCmd, "tag", tag)
|
||||
}
|
||||
|
||||
// Log logs the changes for the repo in dir.
|
||||
// dir must be a valid VCS repo compatible with v.
|
||||
func (v *Cmd) Log(dir, logTemplate string) ([]byte, error) {
|
||||
if err := v.Download(dir); err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
const N = 50 // how many revisions to grab
|
||||
return v.runOutput(dir, v.LogCmd, "limit", strconv.Itoa(N), "template", logTemplate)
|
||||
}
|
||||
|
||||
// LogAtRev logs the change for repo in dir at the rev revision.
|
||||
// dir must be a valid VCS repo compatible with v.
|
||||
// rev must be a valid revision for the repo in dir.
|
||||
func (v *Cmd) LogAtRev(dir, rev, logTemplate string) ([]byte, error) {
|
||||
if err := v.Download(dir); err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
// Append revision flag to LogCmd.
|
||||
logAtRevCmd := v.LogCmd + " --rev=" + rev
|
||||
return v.runOutput(dir, logAtRevCmd, "limit", strconv.Itoa(1), "template", logTemplate)
|
||||
}
|
||||
|
||||
// A vcsPath describes how to convert an import path into a
|
||||
// version control system and repository name.
|
||||
type vcsPath struct {
|
||||
prefix string // prefix this description applies to
|
||||
re string // pattern for import path
|
||||
repo string // repository to use (expand with match of re)
|
||||
vcs string // version control system to use (expand with match of re)
|
||||
check func(match map[string]string) error // additional checks
|
||||
ping bool // ping for scheme to use to download repo
|
||||
|
||||
regexp *regexp.Regexp // cached compiled form of re
|
||||
}
|
||||
|
||||
// FromDir inspects dir and its parents to determine the
|
||||
// version control system and code repository to use.
|
||||
// On return, root is the import path
|
||||
// corresponding to the root of the repository.
|
||||
func FromDir(dir, srcRoot string) (vcs *Cmd, root string, err error) {
|
||||
// Clean and double-check that dir is in (a subdirectory of) srcRoot.
|
||||
dir = filepath.Clean(dir)
|
||||
srcRoot = filepath.Clean(srcRoot)
|
||||
if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator {
|
||||
return nil, "", fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
|
||||
}
|
||||
|
||||
origDir := dir
|
||||
for len(dir) > len(srcRoot) {
|
||||
for _, vcs := range vcsList {
|
||||
if _, err := os.Stat(filepath.Join(dir, "."+vcs.Cmd)); err == nil {
|
||||
return vcs, filepath.ToSlash(dir[len(srcRoot)+1:]), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Move to parent.
|
||||
ndir := filepath.Dir(dir)
|
||||
if len(ndir) >= len(dir) {
|
||||
// Shouldn't happen, but just in case, stop.
|
||||
break
|
||||
}
|
||||
dir = ndir
|
||||
}
|
||||
|
||||
return nil, "", fmt.Errorf("directory %q is not using a known version control system", origDir)
|
||||
}
|
||||
|
||||
// RepoRoot represents a version control system, a repo, and a root of
|
||||
// where to put it on disk.
|
||||
type RepoRoot struct {
|
||||
VCS *Cmd
|
||||
|
||||
// Repo is the repository URL, including scheme.
|
||||
Repo string
|
||||
|
||||
// Root is the import path corresponding to the root of the
|
||||
// repository.
|
||||
Root string
|
||||
}
|
||||
|
||||
// RepoRootForImportPath analyzes importPath to determine the
|
||||
// version control system, and code repository to use.
|
||||
func RepoRootForImportPath(importPath string, verbose bool) (*RepoRoot, error) {
|
||||
rr, err := RepoRootForImportPathStatic(importPath, "")
|
||||
if err == errUnknownSite {
|
||||
rr, err = RepoRootForImportDynamic(importPath, verbose)
|
||||
|
||||
// RepoRootForImportDynamic returns error detail
|
||||
// that is irrelevant if the user didn't intend to use a
|
||||
// dynamic import in the first place.
|
||||
// Squelch it.
|
||||
if err != nil {
|
||||
if Verbose {
|
||||
log.Printf("import %q: %v", importPath, err)
|
||||
}
|
||||
err = fmt.Errorf("unrecognized import path %q", importPath)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.Root, "...") {
|
||||
// Do not allow wildcards in the repo root.
|
||||
rr = nil
|
||||
err = fmt.Errorf("cannot expand ... in %q", importPath)
|
||||
}
|
||||
return rr, err
|
||||
}
|
||||
|
||||
var errUnknownSite = errors.New("dynamic lookup required to find mapping")
|
||||
|
||||
// RepoRootForImportPathStatic attempts to map importPath to a
|
||||
// RepoRoot using the commonly-used VCS hosting sites in vcsPaths
|
||||
// (github.com/user/dir), or from a fully-qualified importPath already
|
||||
// containing its VCS type (foo.com/repo.git/dir)
|
||||
//
|
||||
// If scheme is non-empty, that scheme is forced.
|
||||
func RepoRootForImportPathStatic(importPath, scheme string) (*RepoRoot, error) {
|
||||
if strings.Contains(importPath, "://") {
|
||||
return nil, fmt.Errorf("invalid import path %q", importPath)
|
||||
}
|
||||
for _, srv := range vcsPaths {
|
||||
if !strings.HasPrefix(importPath, srv.prefix) {
|
||||
continue
|
||||
}
|
||||
m := srv.regexp.FindStringSubmatch(importPath)
|
||||
if m == nil {
|
||||
if srv.prefix != "" {
|
||||
return nil, fmt.Errorf("invalid %s import path %q", srv.prefix, importPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Build map of named subexpression matches for expand.
|
||||
match := map[string]string{
|
||||
"prefix": srv.prefix,
|
||||
"import": importPath,
|
||||
}
|
||||
for i, name := range srv.regexp.SubexpNames() {
|
||||
if name != "" && match[name] == "" {
|
||||
match[name] = m[i]
|
||||
}
|
||||
}
|
||||
if srv.vcs != "" {
|
||||
match["vcs"] = expand(match, srv.vcs)
|
||||
}
|
||||
if srv.repo != "" {
|
||||
match["repo"] = expand(match, srv.repo)
|
||||
}
|
||||
if srv.check != nil {
|
||||
if err := srv.check(match); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
vcs := ByCmd(match["vcs"])
|
||||
if vcs == nil {
|
||||
return nil, fmt.Errorf("unknown version control system %q", match["vcs"])
|
||||
}
|
||||
if srv.ping {
|
||||
if scheme != "" {
|
||||
match["repo"] = scheme + "://" + match["repo"]
|
||||
} else {
|
||||
for _, scheme := range vcs.Scheme {
|
||||
if vcs.Ping(scheme, match["repo"]) == nil {
|
||||
match["repo"] = scheme + "://" + match["repo"]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rr := &RepoRoot{
|
||||
VCS: vcs,
|
||||
Repo: match["repo"],
|
||||
Root: match["root"],
|
||||
}
|
||||
return rr, nil
|
||||
}
|
||||
return nil, errUnknownSite
|
||||
}
|
||||
|
||||
// RepoRootForImportDynamic finds a *RepoRoot for a custom domain that's not
|
||||
// statically known by RepoRootForImportPathStatic.
|
||||
//
|
||||
// This handles custom import paths like "name.tld/pkg/foo" or just "name.tld".
|
||||
func RepoRootForImportDynamic(importPath string, verbose bool) (*RepoRoot, error) {
|
||||
slash := strings.Index(importPath, "/")
|
||||
if slash < 0 {
|
||||
slash = len(importPath)
|
||||
}
|
||||
host := importPath[:slash]
|
||||
if !strings.Contains(host, ".") {
|
||||
return nil, errors.New("import path doesn't contain a hostname")
|
||||
}
|
||||
urlStr, body, err := httpsOrHTTP(importPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("http/https fetch: %v", err)
|
||||
}
|
||||
defer body.Close()
|
||||
imports, err := parseMetaGoImports(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %v", importPath, err)
|
||||
}
|
||||
metaImport, err := matchGoImport(imports, importPath)
|
||||
if err != nil {
|
||||
if err != errNoMatch {
|
||||
return nil, fmt.Errorf("parse %s: %v", urlStr, err)
|
||||
}
|
||||
return nil, fmt.Errorf("parse %s: no go-import meta tags", urlStr)
|
||||
}
|
||||
if verbose {
|
||||
log.Printf("get %q: found meta tag %#v at %s", importPath, metaImport, urlStr)
|
||||
}
|
||||
// If the import was "uni.edu/bob/project", which said the
|
||||
// prefix was "uni.edu" and the RepoRoot was "evilroot.com",
|
||||
// make sure we don't trust Bob and check out evilroot.com to
|
||||
// "uni.edu" yet (possibly overwriting/preempting another
|
||||
// non-evil student). Instead, first verify the root and see
|
||||
// if it matches Bob's claim.
|
||||
if metaImport.Prefix != importPath {
|
||||
if verbose {
|
||||
log.Printf("get %q: verifying non-authoritative meta tag", importPath)
|
||||
}
|
||||
urlStr0 := urlStr
|
||||
urlStr, body, err = httpsOrHTTP(metaImport.Prefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetch %s: %v", urlStr, err)
|
||||
}
|
||||
imports, err := parseMetaGoImports(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %v", importPath, err)
|
||||
}
|
||||
if len(imports) == 0 {
|
||||
return nil, fmt.Errorf("fetch %s: no go-import meta tag", urlStr)
|
||||
}
|
||||
metaImport2, err := matchGoImport(imports, importPath)
|
||||
if err != nil || metaImport != metaImport2 {
|
||||
return nil, fmt.Errorf("%s and %s disagree about go-import for %s", urlStr0, urlStr, metaImport.Prefix)
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.Contains(metaImport.RepoRoot, "://") {
|
||||
return nil, fmt.Errorf("%s: invalid repo root %q; no scheme", urlStr, metaImport.RepoRoot)
|
||||
}
|
||||
rr := &RepoRoot{
|
||||
VCS: ByCmd(metaImport.VCS),
|
||||
Repo: metaImport.RepoRoot,
|
||||
Root: metaImport.Prefix,
|
||||
}
|
||||
if rr.VCS == nil {
|
||||
return nil, fmt.Errorf("%s: unknown vcs %q", urlStr, metaImport.VCS)
|
||||
}
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
// metaImport represents the parsed <meta name="go-import"
|
||||
// content="prefix vcs reporoot" /> tags from HTML files.
|
||||
type metaImport struct {
|
||||
Prefix, VCS, RepoRoot string
|
||||
}
|
||||
|
||||
// errNoMatch is returned from matchGoImport when there's no applicable match.
|
||||
var errNoMatch = errors.New("no import match")
|
||||
|
||||
// matchGoImport returns the metaImport from imports matching importPath.
|
||||
// An error is returned if there are multiple matches.
|
||||
// errNoMatch is returned if none match.
|
||||
func matchGoImport(imports []metaImport, importPath string) (_ metaImport, err error) {
|
||||
match := -1
|
||||
for i, im := range imports {
|
||||
if !strings.HasPrefix(importPath, im.Prefix) {
|
||||
continue
|
||||
}
|
||||
if match != -1 {
|
||||
err = fmt.Errorf("multiple meta tags match import path %q", importPath)
|
||||
return
|
||||
}
|
||||
match = i
|
||||
}
|
||||
if match == -1 {
|
||||
err = errNoMatch
|
||||
return
|
||||
}
|
||||
return imports[match], nil
|
||||
}
|
||||
|
||||
// expand rewrites s to replace {k} with match[k] for each key k in match.
|
||||
func expand(match map[string]string, s string) string {
|
||||
for k, v := range match {
|
||||
s = strings.Replace(s, "{"+k+"}", v, -1)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// vcsPaths lists the known vcs paths.
|
||||
var vcsPaths = []*vcsPath{
|
||||
// go.googlesource.com
|
||||
{
|
||||
prefix: "go.googlesource.com",
|
||||
re: `^(?P<root>go\.googlesource\.com/[A-Za-z0-9_.\-]+/?)$`,
|
||||
vcs: "git",
|
||||
repo: "https://{root}",
|
||||
check: noVCSSuffix,
|
||||
},
|
||||
|
||||
// Github
|
||||
{
|
||||
prefix: "github.com/",
|
||||
re: `^(?P<root>github\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[\p{L}0-9_.\-]+)*$`,
|
||||
vcs: "git",
|
||||
repo: "https://{root}",
|
||||
check: noVCSSuffix,
|
||||
},
|
||||
|
||||
// Bitbucket
|
||||
{
|
||||
prefix: "bitbucket.org/",
|
||||
re: `^(?P<root>bitbucket\.org/(?P<bitname>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`,
|
||||
repo: "https://{root}",
|
||||
check: bitbucketVCS,
|
||||
},
|
||||
|
||||
// Launchpad
|
||||
{
|
||||
prefix: "launchpad.net/",
|
||||
re: `^(?P<root>launchpad\.net/((?P<project>[A-Za-z0-9_.\-]+)(?P<series>/[A-Za-z0-9_.\-]+)?|~[A-Za-z0-9_.\-]+/(\+junk|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`,
|
||||
vcs: "bzr",
|
||||
repo: "https://{root}",
|
||||
check: launchpadVCS,
|
||||
},
|
||||
|
||||
// Git at OpenStack
|
||||
{
|
||||
prefix: "git.openstack.org",
|
||||
re: `^(?P<root>git\.openstack\.org/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(\.git)?(/[A-Za-z0-9_.\-]+)*$`,
|
||||
vcs: "git",
|
||||
repo: "https://{root}",
|
||||
check: noVCSSuffix,
|
||||
},
|
||||
|
||||
// General syntax for any server.
|
||||
{
|
||||
re: `^(?P<root>(?P<repo>([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?/[A-Za-z0-9_.\-/]*?)\.(?P<vcs>bzr|git|hg|svn))(/[A-Za-z0-9_.\-]+)*$`,
|
||||
ping: true,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// fill in cached regexps.
|
||||
// Doing this eagerly discovers invalid regexp syntax
|
||||
// without having to run a command that needs that regexp.
|
||||
for _, srv := range vcsPaths {
|
||||
srv.regexp = regexp.MustCompile(srv.re)
|
||||
}
|
||||
}
|
||||
|
||||
// noVCSSuffix checks that the repository name does not
|
||||
// end in .foo for any version control system foo.
|
||||
// The usual culprit is ".git".
|
||||
func noVCSSuffix(match map[string]string) error {
|
||||
repo := match["repo"]
|
||||
for _, vcs := range vcsList {
|
||||
if strings.HasSuffix(repo, "."+vcs.Cmd) {
|
||||
return fmt.Errorf("invalid version control suffix in %s path", match["prefix"])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bitbucketVCS determines the version control system for a
|
||||
// Bitbucket repository, by using the Bitbucket API.
|
||||
func bitbucketVCS(match map[string]string) error {
|
||||
if err := noVCSSuffix(match); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
SCM string `json:"scm"`
|
||||
}
|
||||
url := expand(match, "https://api.bitbucket.org/1.0/repositories/{bitname}")
|
||||
data, err := httpGET(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &resp); err != nil {
|
||||
return fmt.Errorf("decoding %s: %v", url, err)
|
||||
}
|
||||
|
||||
if ByCmd(resp.SCM) != nil {
|
||||
match["vcs"] = resp.SCM
|
||||
if resp.SCM == "git" {
|
||||
match["repo"] += ".git"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to detect version control system for bitbucket.org/ path")
|
||||
}
|
||||
|
||||
// launchpadVCS solves the ambiguity for "lp.net/project/foo". In this case,
|
||||
// "foo" could be a series name registered in Launchpad with its own branch,
|
||||
// and it could also be the name of a directory within the main project
|
||||
// branch one level up.
|
||||
func launchpadVCS(match map[string]string) error {
|
||||
if match["project"] == "" || match["series"] == "" {
|
||||
return nil
|
||||
}
|
||||
_, err := httpGET(expand(match, "https://code.launchpad.net/{project}{series}/.bzr/branch-format"))
|
||||
if err != nil {
|
||||
match["root"] = expand(match, "launchpad.net/{project}")
|
||||
match["repo"] = expand(match, "https://{root}")
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue