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

Commit

Permalink
Add support importing from govend (#1040)
Browse files Browse the repository at this point in the history
  • Loading branch information
RaviTezu authored and carolynvs committed Aug 24, 2017
1 parent 1f6d6bb commit 7c04369
Show file tree
Hide file tree
Showing 12 changed files with 436 additions and 2 deletions.
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 {
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

0 comments on commit 7c04369

Please sign in to comment.