Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

Add support importing from govend #1040

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions cmd/dep/govend_importer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"io/ioutil"
"log"
"os"
"path/filepath"

"github.com/go-yaml/yaml"
"github.com/golang/dep"
fb "github.com/golang/dep/internal/feedback"
"github.com/golang/dep/internal/gps"
"github.com/pkg/errors"
)

// ToDo: govend supports json and xml formats as well and we will add support for other formats in next PR - @RaviTezu
// govend don't have a separate lock file.
const govendYAMLName = "vendor.yml"

// govendImporter imports govend configuration in to the dep configuration format.
type govendImporter struct {
yaml govendYAML

logger *log.Logger
verbose bool
sm gps.SourceManager
}

func newGovendImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *govendImporter {
return &govendImporter{
logger: logger,
verbose: verbose,
sm: sm,
}
}

type govendYAML struct {
Imports []govendPackage `yaml:"vendors"`
}

type govendPackage struct {
Path string `yaml:"path"`
Revision string `yaml:"rev"`
}

func (g *govendImporter) Name() string {
return "govend"
}

func (g *govendImporter) HasDepMetadata(dir string) bool {
y := filepath.Join(dir, govendYAMLName)
if _, err := os.Stat(y); err != nil {
return false
}

return true
}

func (g *govendImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) {
err := g.load(dir)
if err != nil {
return nil, nil, err
}

return g.convert(pr)
}

// load the govend configuration files.
func (g *govendImporter) load(projectDir string) error {
g.logger.Println("Detected govend configuration files...")
y := filepath.Join(projectDir, govendYAMLName)
if g.verbose {
g.logger.Printf(" Loading %s", y)
}
yb, err := ioutil.ReadFile(y)
if err != nil {
return errors.Wrapf(err, "Unable to read %s", y)
}
err = yaml.Unmarshal(yb, &g.yaml)
if err != nil {
return errors.Wrapf(err, "Unable to parse %s", y)
}
return nil
}

// convert the govend configuration files into dep configuration files.
func (g *govendImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) {
g.logger.Println("Converting from vendor.yaml...")

manifest := dep.NewManifest()
lock := &dep.Lock{}

for _, pkg := range g.yaml.Imports {
// Path must not be empty
if pkg.Path == "" || pkg.Revision == "" {
return nil, nil, errors.New("Invalid govend configuration, Path and Rev are required")
}

p, err := g.sm.DeduceProjectRoot(pkg.Path)
if err != nil {
return nil, nil, err
}
pkg.Path = string(p)

// Check if the current project is already existing in locked projects.
if projectExistsInLock(lock, p) {
continue
}

pi := gps.ProjectIdentifier{
ProjectRoot: gps.ProjectRoot(pkg.Path),
}
revision := gps.Revision(pkg.Revision)

version, err := lookupVersionForLockedProject(pi, nil, revision, g.sm)
if err != nil {
g.logger.Println(err.Error())
} else {
pp := getProjectPropertiesFromVersion(version)
if pp.Constraint != nil {
pc, err := g.buildProjectConstraint(pkg, pp.Constraint.String())
if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this isn't in the other importers yet, but we'd like to have import keep going even when we can't guess a constraint from the revision. See #907.

So when buildProjectConstraint returns an error, let's log a warning like "Unable to infer a constraint for REVISION", and then skip adding the constraint to the manifest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carolynvs Done. Please take a look.

g.logger.Printf("Unable to infer a constraint for revision %s for package %s: %s", pkg.Revision, pkg.Path, err.Error())
continue
}
manifest.Constraints[pc.Ident.ProjectRoot] = gps.ProjectProperties{Constraint: pc.Constraint}
}
}

lp := g.buildLockedProject(pkg, manifest)
lock.P = append(lock.P, lp)
}

return manifest, lock, nil
}

func (g *govendImporter) buildProjectConstraint(pkg govendPackage, constraint string) (pc gps.ProjectConstraint, err error) {
pc.Ident = gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot(pkg.Path)}
pc.Constraint, err = g.sm.InferConstraint(constraint, pc.Ident)
if err != nil {
return
}

f := fb.NewConstraintFeedback(pc, fb.DepTypeImported)
f.LogFeedback(g.logger)

return

}

func (g *govendImporter) buildLockedProject(pkg govendPackage, manifest *dep.Manifest) gps.LockedProject {
p := gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot(pkg.Path)}
revision := gps.Revision(pkg.Revision)
pp := manifest.Constraints[p.ProjectRoot]

version, err := lookupVersionForLockedProject(p, pp.Constraint, revision, g.sm)
if err != nil {
g.logger.Println(err.Error())
}

lp := gps.NewLockedProject(p, version, nil)
f := fb.NewLockedProjectFeedback(lp, fb.DepTypeImported)
f.LogFeedback(g.logger)

return lp
}
192 changes: 192 additions & 0 deletions cmd/dep/govend_importer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"bytes"
"log"
"path/filepath"
"testing"

"github.com/golang/dep/internal/gps"
"github.com/golang/dep/internal/test"
"github.com/pkg/errors"
)

func TestGovendConfig_Convert(t *testing.T) {
testCases := map[string]struct {
*convertTestCase
yaml govendYAML
}{
"project": {
yaml: govendYAML{
Imports: []govendPackage{
{
Path: "github.com/sdboyer/deptest",
Revision: "ff2948a2ac8f538c4ecd55962e919d1e13e74baf",
},
},
},
convertTestCase: &convertTestCase{
projectRoot: gps.ProjectRoot("github.com/sdboyer/deptest"),
wantConstraint: "^1.0.0",
wantLockCount: 1,
wantRevision: gps.Revision("ff2948a2ac8f538c4ecd55962e919d1e13e74baf"),
wantVersion: "v1.0.0",
},
},
"bad input - empty package name": {
yaml: govendYAML{
Imports: []govendPackage{
{
Path: "",
},
},
},
convertTestCase: &convertTestCase{
wantConvertErr: true,
},
},

"bad input - empty revision": {
yaml: govendYAML{
Imports: []govendPackage{
{
Path: "github.com/sdboyer/deptest",
},
},
},
convertTestCase: &convertTestCase{
wantConvertErr: true,
},
},
}

h := test.NewHelper(t)
defer h.Cleanup()

ctx := newTestContext(h)
sm, err := ctx.SourceManager()
h.Must(err)
defer sm.Release()

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
g := newGovendImporter(discardLogger, true, sm)
g.yaml = testCase.yaml

manifest, lock, convertErr := g.convert(testCase.projectRoot)
err = validateConvertTestCase(testCase.convertTestCase, manifest, lock, convertErr)
if err != nil {
t.Fatalf("%#v", err)
}
})
}
}

func TestGovendConfig_Import(t *testing.T) {
h := test.NewHelper(t)
defer h.Cleanup()

cacheDir := "gps-repocache"
h.TempDir(cacheDir)
h.TempDir("src")
h.TempDir(filepath.Join("src", testProjectRoot))
h.TempCopy(filepath.Join(testProjectRoot, govendYAMLName), "govend/vendor.yml")

projectRoot := h.Path(testProjectRoot)
sm, err := gps.NewSourceManager(h.Path(cacheDir))
h.Must(err)
defer sm.Release()

// Capture stderr so we can verify the import output
verboseOutput := &bytes.Buffer{}
logger := log.New(verboseOutput, "", 0)

// Disable verbose so that we don't print values that change each test run
g := newGovendImporter(logger, false, sm)
if !g.HasDepMetadata(projectRoot) {
t.Fatal("Expected the importer to detect govend configuration file")
}

m, l, err := g.Import(projectRoot, testProjectRoot)
h.Must(err)

if m == nil {
t.Fatal("Expected the manifest to be generated")
}

if l == nil {
t.Fatal("Expected the lock to be generated")
}

govendImportOutputFile := "govend/expected_govend_import_output.txt"
got := verboseOutput.String()
want := h.GetTestFileString(govendImportOutputFile)
if want != got {
if *test.UpdateGolden {
if err := h.WriteTestFile(govendImportOutputFile, got); err != nil {
t.Fatalf("%+v", errors.Wrapf(err, "Unable to write updated golden file %s", govendImportOutputFile))
}
} else {
t.Fatalf("want %s, got %s", want, got)
}
}

}

func TestGovendConfig_YAMLLoad(t *testing.T) {
// This is same as cmd/testdata/govend/vendor.yml
wantYAML := govendYAML{
Imports: []govendPackage{
{
Path: "github.com/sdboyer/deptest",
Revision: "3f4c3bea144e112a69bbe5d8d01c1b09a544253f",
},
{
Path: "github.com/sdboyer/deptestdos",
Revision: "5c607206be5decd28e6263ffffdcee067266015e",
},
},
}
h := test.NewHelper(t)
defer h.Cleanup()

ctx := newTestContext(h)
h.TempCopy(filepath.Join(testProjectRoot, govendYAMLName), "govend/vendor.yml")

projectRoot := h.Path(testProjectRoot)

g := newGovendImporter(ctx.Err, true, nil)
err := g.load(projectRoot)
if err != nil {
t.Fatalf("Error while loading %v", err)
}

if !equalGovendImports(g.yaml.Imports, wantYAML.Imports) {
t.Fatalf("Expected import to be equal. \n\t(GOT): %v\n\t(WNT): %v", g.yaml.Imports, wantYAML.Imports)
}
}

func equalGovendImports(a, b []govendPackage) bool {
if a == nil && b == nil {
return true
}

if a == nil || b == nil {
return false
}

if len(a) != len(b) {
return false
}

for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
2 changes: 1 addition & 1 deletion cmd/dep/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ specified, use the current directory.
When configuration for another dependency management tool is detected, it is
imported into the initial manifest and lock. Use the -skip-tools flag to
disable this behavior. The following external tools are supported:
glide, godep, vndr.
glide, godep, vndr, govend.

Any dependencies that are not constrained by external configuration use the
GOPATH analysis below.
Expand Down
1 change: 1 addition & 0 deletions cmd/dep/root_analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func (a *rootAnalyzer) importManifestAndLock(dir string, pr gps.ProjectRoot, sup
newGlideImporter(logger, a.ctx.Verbose, a.sm),
newGodepImporter(logger, a.ctx.Verbose, a.sm),
newVndrImporter(logger, a.ctx.Verbose, a.sm),
newGovendImporter(logger, a.ctx.Verbose, a.sm),
}

for _, i := range importers {
Expand Down
6 changes: 6 additions & 0 deletions cmd/dep/testdata/govend/expected_govend_import_output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Detected govend configuration files...
Converting from vendor.yaml...
Using ^0.8.1 as initial constraint for imported dep github.com/sdboyer/deptest
Trying v0.8.1 (3f4c3be) as initial lock for imported dep github.com/sdboyer/deptest
Using ^2.0.0 as initial constraint for imported dep github.com/sdboyer/deptestdos
Trying v2.0.0 (5c60720) as initial lock for imported dep github.com/sdboyer/deptestdos
6 changes: 6 additions & 0 deletions cmd/dep/testdata/govend/vendor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
vendors:
- path: github.com/sdboyer/deptest
rev: 3f4c3bea144e112a69bbe5d8d01c1b09a544253f
- path: github.com/sdboyer/deptestdos
rev: 5c607206be5decd28e6263ffffdcee067266015e

Loading