diff --git a/cmd/dep/gvt_importer.go b/cmd/dep/gvt_importer.go new file mode 100644 index 0000000000..41601bf040 --- /dev/null +++ b/cmd/dep/gvt_importer.go @@ -0,0 +1,117 @@ +// 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 ( + "encoding/json" + "io/ioutil" + "log" + "os" + "path/filepath" + + "github.com/golang/dep" + "github.com/golang/dep/internal/gps" + "github.com/pkg/errors" +) + +const gvtPath = "vendor" + string(os.PathSeparator) + "manifest" + +type gvtImporter struct { + *baseImporter + gvtConfig gvtManifest +} + +func newGvtImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *gvtImporter { + return &gvtImporter{baseImporter: newBaseImporter(logger, verbose, sm)} +} + +type gvtManifest struct { + Deps []gvtPkg `json:"dependencies"` +} + +type gvtPkg struct { + ImportPath string + Repository string + Revision string + Branch string +} + +func (g *gvtImporter) Name() string { + return "gvt" +} + +func (g *gvtImporter) HasDepMetadata(dir string) bool { + y := filepath.Join(dir, gvtPath) + if _, err := os.Stat(y); err != nil { + return false + } + + return true +} + +func (g *gvtImporter) 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) +} + +func (g *gvtImporter) load(projectDir string) error { + g.logger.Println("Detected gvt configuration files...") + j := filepath.Join(projectDir, gvtPath) + if g.verbose { + g.logger.Printf(" Loading %s", j) + } + jb, err := ioutil.ReadFile(j) + if err != nil { + return errors.Wrapf(err, "unable to read %s", j) + } + err = json.Unmarshal(jb, &g.gvtConfig) + if err != nil { + return errors.Wrapf(err, "unable to parse %s", j) + } + + return nil +} + +func (g *gvtImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { + g.logger.Println("Converting from vendor/manifest ...") + + packages := make([]importedPackage, 0, len(g.gvtConfig.Deps)) + for _, pkg := range g.gvtConfig.Deps { + // Validate + if pkg.ImportPath == "" { + err := errors.New("invalid gvt configuration, ImportPath is required") + return nil, nil, err + } + + if pkg.Revision == "" { + err := errors.New("invalid gvt configuration, Revision is required") + return nil, nil, err + } + + var contstraintHint = "" + if pkg.Branch != "master" { + contstraintHint = pkg.Branch + } + + ip := importedPackage{ + Name: pkg.ImportPath, + //TODO: Source: pkg.Repository, + LockHint: pkg.Revision, + ConstraintHint: contstraintHint, + } + packages = append(packages, ip) + } + + err := g.importPackages(packages, true) + if err != nil { + return nil, nil, err + } + + return g.manifest, g.lock, nil +} diff --git a/cmd/dep/gvt_importer_test.go b/cmd/dep/gvt_importer_test.go new file mode 100644 index 0000000000..299d5d9c81 --- /dev/null +++ b/cmd/dep/gvt_importer_test.go @@ -0,0 +1,209 @@ +// 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" + "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/test" + "github.com/pkg/errors" +) + +func TestGvtConfig_Convert(t *testing.T) { + testCases := map[string]struct { + convertTestCase + gvtConfig gvtManifest + }{ + "package without comment": { + convertTestCase{ + wantConstraint: importerTestV1Constraint, + wantRevision: importerTestV1Rev, + wantVersion: importerTestV1Tag, + }, + gvtManifest{ + Deps: []gvtPkg{ + { + ImportPath: importerTestProject, + Revision: importerTestV1Rev, + }, + }, + }, + }, + "package with non-master branch": { + convertTestCase{ + wantConstraint: importerTestV2Branch, + wantRevision: importerTestV2PatchRev, + wantVersion: importerTestV2PatchTag, + }, + gvtManifest{ + Deps: []gvtPkg{ + { + ImportPath: importerTestProject, + Revision: importerTestV2PatchRev, + Branch: importerTestV2Branch, + }, + }, + }, + }, + "missing package name": { + convertTestCase{ + wantConvertErr: true, + }, + gvtManifest{ + Deps: []gvtPkg{{ImportPath: ""}}, + }, + }, + "missing revision": { + convertTestCase{ + wantConvertErr: true, + }, + gvtManifest{ + Deps: []gvtPkg{ + { + ImportPath: importerTestProject, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name := name + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := testCase.Exec(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { + g := newGvtImporter(logger, true, sm) + g.gvtConfig = testCase.gvtConfig + return g.convert(testProjectRoot) + }) + if err != nil { + t.Fatalf("%#v", err) + } + }) + } +} + +func TestGvtConfig_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, gvtPath), "init/gvt/manifest") + + projectRoot := h.Path(testProjectRoot) + sm, err := gps.NewSourceManager(gps.SourceManagerConfig{ + Cachedir: h.Path(cacheDir), + Logger: log.New(test.Writer{t}, "", 0), + }) + h.Must(err) + defer sm.Release() + + // Capture stderr so we can verify output + verboseOutput := &bytes.Buffer{} + logger := log.New(verboseOutput, "", 0) + + g := newGvtImporter(logger, false, sm) // Disable verbose so that we don't print values that change each test run + if !g.HasDepMetadata(projectRoot) { + t.Fatal("Expected the importer to detect gvt 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") + } + + goldenFile := "init/gvt/golden.txt" + got := verboseOutput.String() + want := h.GetTestFileString(goldenFile) + if want != got { + if *test.UpdateGolden { + if err := h.WriteTestFile(goldenFile, got); err != nil { + t.Fatalf("%+v", errors.Wrapf(err, "Unable to write updated golden file %s", goldenFile)) + } + } else { + t.Fatalf("want %s, got %s", want, got) + } + } +} + +func TestGvtConfig_JsonLoad(t *testing.T) { + // This is same as cmd/dep/testdata/init/gvt/manifest + wantConfig := gvtManifest{ + Deps: []gvtPkg{ + { + ImportPath: "github.com/sdboyer/deptest", + Revision: "3f4c3bea144e112a69bbe5d8d01c1b09a544253f", + }, + { + ImportPath: "github.com/sdboyer/deptestdos", + Revision: "5c607206be5decd28e6263ffffdcee067266015e", + }, + { + ImportPath: "github.com/carolynvs/deptest-importers", + Revision: "b79bc9482da8bb7402cdc3e3fd984db250718dd7", + Branch: "v2", + }, + }, + } + + h := test.NewHelper(t) + defer h.Cleanup() + + ctx := newTestContext(h) + + h.TempCopy(filepath.Join(testProjectRoot, gvtPath), "init/gvt/manifest") + + projectRoot := h.Path(testProjectRoot) + + g := newGvtImporter(ctx.Err, true, nil) + err := g.load(projectRoot) + if err != nil { + t.Fatalf("Error while loading... %v", err) + } + + if !gvtEqualImports(g.gvtConfig.Deps, wantConfig.Deps) { + t.Fatalf("Expected imports to be equal. \n\t(GOT): %v\n\t(WNT): %v", g.gvtConfig.Deps, wantConfig.Deps) + } +} + +// gvtEqualImports compares two slices of gvtPkg and checks if they are +// equal. +func gvtEqualImports(a, b []gvtPkg) 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/root_analyzer.go b/cmd/dep/root_analyzer.go index 471b673b33..85146f9c61 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 newGodepImporter(logger, a.ctx.Verbose, a.sm), newVndrImporter(logger, a.ctx.Verbose, a.sm), newGovendImporter(logger, a.ctx.Verbose, a.sm), + newGvtImporter(logger, a.ctx.Verbose, a.sm), } for _, i := range importers { diff --git a/cmd/dep/testdata/init/gvt/godep_importer_test.go b/cmd/dep/testdata/init/gvt/godep_importer_test.go new file mode 100644 index 0000000000..bb231d44ac --- /dev/null +++ b/cmd/dep/testdata/init/gvt/godep_importer_test.go @@ -0,0 +1,207 @@ +// 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" + "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/test" + "github.com/pkg/errors" +) + +const testProjectRoot = "github.com/golang/notexist" + +func TestGodepConfig_Convert(t *testing.T) { + testCases := map[string]struct { + convertTestCase + json godepJSON + }{ + "package without comment": { + convertTestCase{ + wantConstraint: importerTestV1Constraint, + wantRevision: importerTestV1Rev, + wantVersion: importerTestV1Tag, + }, + godepJSON{ + Imports: []godepPackage{ + { + ImportPath: importerTestProject, + Rev: importerTestV1Rev, + }, + }, + }, + }, + "package with comment": { + convertTestCase{ + wantConstraint: importerTestV2Branch, + wantRevision: importerTestV2PatchRev, + wantVersion: importerTestV2PatchTag, + }, + godepJSON{ + Imports: []godepPackage{ + { + ImportPath: importerTestProject, + Rev: importerTestV2PatchRev, + Comment: importerTestV2Branch, + }, + }, + }, + }, + "missing package name": { + convertTestCase{ + wantConvertErr: true, + }, + godepJSON{ + Imports: []godepPackage{{ImportPath: ""}}, + }, + }, + "missing revision": { + convertTestCase{ + wantConvertErr: true, + }, + godepJSON{ + Imports: []godepPackage{ + { + ImportPath: importerTestProject, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name := name + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := testCase.Exec(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { + g := newGodepImporter(logger, true, sm) + g.json = testCase.json + return g.convert(testProjectRoot) + }) + if err != nil { + t.Fatalf("%#v", err) + } + }) + } +} + +func TestGodepConfig_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, godepPath), "init/godep/Godeps.json") + + projectRoot := h.Path(testProjectRoot) + sm, err := gps.NewSourceManager(gps.SourceManagerConfig{ + Cachedir: h.Path(cacheDir), + Logger: log.New(test.Writer{t}, "", 0), + }) + h.Must(err) + defer sm.Release() + + // Capture stderr so we can verify output + verboseOutput := &bytes.Buffer{} + logger := log.New(verboseOutput, "", 0) + + g := newGodepImporter(logger, false, sm) // Disable verbose so that we don't print values that change each test run + if !g.HasDepMetadata(projectRoot) { + t.Fatal("Expected the importer to detect godep 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") + } + + goldenFile := "init/godep/golden.txt" + got := verboseOutput.String() + want := h.GetTestFileString(goldenFile) + if want != got { + if *test.UpdateGolden { + if err := h.WriteTestFile(goldenFile, got); err != nil { + t.Fatalf("%+v", errors.Wrapf(err, "Unable to write updated golden file %s", goldenFile)) + } + } else { + t.Fatalf("want %s, got %s", want, got) + } + } +} + +func TestGodepConfig_JsonLoad(t *testing.T) { + // This is same as cmd/dep/testdata/init/Godeps.json + wantJSON := godepJSON{ + Imports: []godepPackage{ + { + ImportPath: "github.com/sdboyer/deptest", + Rev: "3f4c3bea144e112a69bbe5d8d01c1b09a544253f", + }, + { + ImportPath: "github.com/sdboyer/deptestdos", + Rev: "5c607206be5decd28e6263ffffdcee067266015e", + Comment: "v2.0.0", + }, + }, + } + + h := test.NewHelper(t) + defer h.Cleanup() + + ctx := newTestContext(h) + + h.TempCopy(filepath.Join(testProjectRoot, godepPath), "init/godep/Godeps.json") + + projectRoot := h.Path(testProjectRoot) + + g := newGodepImporter(ctx.Err, true, nil) + err := g.load(projectRoot) + if err != nil { + t.Fatalf("Error while loading... %v", err) + } + + if !equalImports(g.json.Imports, wantJSON.Imports) { + t.Fatalf("Expected imports to be equal. \n\t(GOT): %v\n\t(WNT): %v", g.json.Imports, wantJSON.Imports) + } +} + +// equalImports compares two slices of godepPackage and checks if they are +// equal. +func equalImports(a, b []godepPackage) 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/testdata/init/gvt/golden.txt b/cmd/dep/testdata/init/gvt/golden.txt new file mode 100644 index 0000000000..b7541aa9b7 --- /dev/null +++ b/cmd/dep/testdata/init/gvt/golden.txt @@ -0,0 +1,8 @@ +Detected gvt configuration files... +Converting from vendor/manifest ... + 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 + Using v2 as initial constraint for imported dep github.com/carolynvs/deptest-importers + Trying v2 (b79bc94) as initial lock for imported dep github.com/carolynvs/deptest-importers diff --git a/cmd/dep/testdata/init/gvt/manifest b/cmd/dep/testdata/init/gvt/manifest new file mode 100644 index 0000000000..61ff49a8f1 --- /dev/null +++ b/cmd/dep/testdata/init/gvt/manifest @@ -0,0 +1,17 @@ +{ + "dependencies": [ + { + "importpath": "github.com/sdboyer/deptest", + "revision": "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" + }, + { + "importpath": "github.com/sdboyer/deptestdos", + "revision": "5c607206be5decd28e6263ffffdcee067266015e" + }, + { + "importpath": "github.com/carolynvs/deptest-importers", + "revision": "b79bc9482da8bb7402cdc3e3fd984db250718dd7", + "branch": "v2" + } + ] +}