diff --git a/cmd/dep/govend_importer.go b/cmd/dep/govend_importer.go new file mode 100644 index 0000000000..0b1d7531ed --- /dev/null +++ b/cmd/dep/govend_importer.go @@ -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 +} diff --git a/cmd/dep/govend_importer_test.go b/cmd/dep/govend_importer_test.go new file mode 100644 index 0000000000..eedb2e113c --- /dev/null +++ b/cmd/dep/govend_importer_test.go @@ -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 +} diff --git a/cmd/dep/init.go b/cmd/dep/init.go index ec36251136..0f4efa0ba8 100644 --- a/cmd/dep/init.go +++ b/cmd/dep/init.go @@ -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. diff --git a/cmd/dep/root_analyzer.go b/cmd/dep/root_analyzer.go index 75b010c070..e8d8b25727 100644 --- a/cmd/dep/root_analyzer.go +++ b/cmd/dep/root_analyzer.go @@ -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 { diff --git a/cmd/dep/testdata/govend/expected_govend_import_output.txt b/cmd/dep/testdata/govend/expected_govend_import_output.txt new file mode 100644 index 0000000000..e77c76ab5f --- /dev/null +++ b/cmd/dep/testdata/govend/expected_govend_import_output.txt @@ -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 diff --git a/cmd/dep/testdata/govend/vendor.yml b/cmd/dep/testdata/govend/vendor.yml new file mode 100644 index 0000000000..0545b10125 --- /dev/null +++ b/cmd/dep/testdata/govend/vendor.yml @@ -0,0 +1,6 @@ +vendors: +- path: github.com/sdboyer/deptest + rev: 3f4c3bea144e112a69bbe5d8d01c1b09a544253f +- path: github.com/sdboyer/deptestdos + rev: 5c607206be5decd28e6263ffffdcee067266015e + diff --git a/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.lock new file mode 100644 index 0000000000..ac445c05d2 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.lock @@ -0,0 +1,21 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/sdboyer/deptest" + packages = ["."] + revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" + version = "v0.8.1" + +[[projects]] + name = "github.com/sdboyer/deptestdos" + packages = ["."] + revision = "5c607206be5decd28e6263ffffdcee067266015e" + version = "v2.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "1ed417a0bec57ffe988fae1cba8f3d49994fb893394d61844e0b3c96d69573fe" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.toml new file mode 100644 index 0000000000..aaf78303fa --- /dev/null +++ b/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.toml @@ -0,0 +1,4 @@ + +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "2.0.0" diff --git a/cmd/dep/testdata/harness_tests/init/govend/case1/initial/main.go b/cmd/dep/testdata/harness_tests/init/govend/case1/initial/main.go new file mode 100644 index 0000000000..2b2c7c396e --- /dev/null +++ b/cmd/dep/testdata/harness_tests/init/govend/case1/initial/main.go @@ -0,0 +1,16 @@ +// 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 ( + "fmt" + + "github.com/sdboyer/deptestdos" +) + +func main() { + var x deptestdos.Bar + fmt.Println(x) +} diff --git a/cmd/dep/testdata/harness_tests/init/govend/case1/initial/vendor.yml b/cmd/dep/testdata/harness_tests/init/govend/case1/initial/vendor.yml new file mode 100644 index 0000000000..c5c933f61f --- /dev/null +++ b/cmd/dep/testdata/harness_tests/init/govend/case1/initial/vendor.yml @@ -0,0 +1,5 @@ +vendors: +- path: github.com/sdboyer/deptest + rev: 3f4c3bea144e112a69bbe5d8d01c1b09a544253f +- path: github.com/sdboyer/deptestdos + rev: 5c607206be5decd28e6263ffffdcee067266015e diff --git a/cmd/dep/testdata/harness_tests/init/govend/case1/testcase.json b/cmd/dep/testdata/harness_tests/init/govend/case1/testcase.json new file mode 100644 index 0000000000..017dc4cd55 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/init/govend/case1/testcase.json @@ -0,0 +1,13 @@ +{ + "commands": [ + ["init", "-no-examples"] + ], + "error-expected": "", + "gopath-initial": { + "github.com/sdboyer/deptest": "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" + }, + "vendor-final": [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] +} diff --git a/docs/FAQ.md b/docs/FAQ.md index 4f5f138c00..24ee7f9b97 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -195,7 +195,7 @@ about what's going on. During `dep init` configuration from other dependency managers is detected and imported, unless `-skip-tools` is specified. -The following tools are supported: `glide`, `godep` and `vndr`. +The following tools are supported: `glide`, `godep`, `vndr` and `govend`. See [#186](https://github.com/golang/dep/issues/186#issuecomment-306363441) for how to add support for another tool.