diff --git a/internal/importers/gvt/importer.go b/internal/importers/gvt/importer.go new file mode 100644 index 0000000000..a8c966875a --- /dev/null +++ b/internal/importers/gvt/importer.go @@ -0,0 +1,124 @@ +// 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 gvt + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "path/filepath" + + "github.com/golang/dep" + "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/base" + "github.com/pkg/errors" +) + +const gvtPath = "vendor" + string(os.PathSeparator) + "manifest" + +// Importer imports gvt configuration into the dep configuration format. +type Importer struct { + *base.Importer + gvtConfig gvtManifest +} + +// NewImporter for gvt. +func NewImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *Importer { + return &Importer{Importer: base.NewImporter(logger, verbose, sm)} +} + +type gvtManifest struct { + Deps []gvtPkg `json:"dependencies"` +} + +type gvtPkg struct { + ImportPath string + Repository string + Revision string + Branch string +} + +// Name of the importer. +func (g *Importer) Name() string { + return "gvt" +} + +// HasDepMetadata checks if a directory contains config that the importer can handle. +func (g *Importer) HasDepMetadata(dir string) bool { + y := filepath.Join(dir, gvtPath) + if _, err := os.Stat(y); err != nil { + return false + } + + return true +} + +// Import the config found in the directory. +func (g *Importer) 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 *Importer) 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 *Importer) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { + g.Logger.Println("Converting from vendor/manifest ...") + + packages := make([]base.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 := base.ImportedPackage{ + Name: pkg.ImportPath, + //TODO: temporarly ignore .Repository. see https://github.com/golang/dep/pull/1166 + // 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/internal/importers/gvt/importer_test.go b/internal/importers/gvt/importer_test.go new file mode 100644 index 0000000000..f9d5a12e7c --- /dev/null +++ b/internal/importers/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 gvt + +import ( + "bytes" + "log" + "path/filepath" + "testing" + + "github.com/golang/dep" + "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/importertest" + "github.com/golang/dep/internal/test" + "github.com/pkg/errors" +) + +func TestGvtConfig_Convert(t *testing.T) { + testCases := map[string]struct { + importertest.TestCase + gvtConfig gvtManifest + }{ + "package with master branch": { + importertest.TestCase{ + WantConstraint: importertest.V1Constraint, + WantRevision: importertest.V1Rev, + WantVersion: importertest.V1Tag, + }, + gvtManifest{ + Deps: []gvtPkg{ + { + ImportPath: importertest.Project, + Revision: importertest.V1Rev, + Branch: "master", + }, + }, + }, + }, + "package with non-master branch": { + importertest.TestCase{ + WantConstraint: importertest.V2Branch, + WantRevision: importertest.V2PatchRev, + WantVersion: importertest.V2PatchTag, + }, + gvtManifest{ + Deps: []gvtPkg{ + { + ImportPath: importertest.Project, + Revision: importertest.V2PatchRev, + Branch: importertest.V2Branch, + }, + }, + }, + }, + "missing package name": { + importertest.TestCase{ + WantConvertErr: true, + }, + gvtManifest{ + Deps: []gvtPkg{{ImportPath: ""}}, + }, + }, + "missing revision": { + importertest.TestCase{ + WantConvertErr: true, + }, + gvtManifest{ + Deps: []gvtPkg{ + { + ImportPath: importertest.Project, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name := name + testCase := testCase + t.Run(name, func(t *testing.T) { + err := testCase.Execute(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { + g := NewImporter(logger, true, sm) + g.gvtConfig = testCase.gvtConfig + return g.convert(importertest.RootProject) + }) + 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", importertest.RootProject)) + h.TempCopy(filepath.Join(importertest.RootProject, gvtPath), "manifest") + + projectRoot := h.Path(importertest.RootProject) + sm, err := gps.NewSourceManager(gps.SourceManagerConfig{ + Cachedir: h.Path(cacheDir), + Logger: log.New(test.Writer{TB: t}, "", 0), + }) + h.Must(err) + defer sm.Release() + + // Capture stderr so we can verify output + verboseOutput := &bytes.Buffer{} + logger := log.New(verboseOutput, "", 0) + + g := NewImporter(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, importertest.RootProject) + 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 := "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 testdata/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 := importertest.NewTestContext(h) + + h.TempCopy(filepath.Join(importertest.RootProject, gvtPath), "manifest") + + projectRoot := h.Path(importertest.RootProject) + + g := NewImporter(ctx.Err, true, nil) + err := g.load(projectRoot) + if err != nil { + t.Fatalf("Error while loading... %v", err) + } + + if !equalImports(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) + } +} + +// equalImports compares two slices of gvtPkg and checks if they are +// equal. +func equalImports(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/internal/importers/gvt/testdata/golden.txt b/internal/importers/gvt/testdata/golden.txt new file mode 100644 index 0000000000..b7541aa9b7 --- /dev/null +++ b/internal/importers/gvt/testdata/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/internal/importers/gvt/testdata/manifest b/internal/importers/gvt/testdata/manifest new file mode 100644 index 0000000000..61ff49a8f1 --- /dev/null +++ b/internal/importers/gvt/testdata/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" + } + ] +} diff --git a/internal/importers/importers.go b/internal/importers/importers.go index 7cc7cfac78..ad669b55bd 100644 --- a/internal/importers/importers.go +++ b/internal/importers/importers.go @@ -12,6 +12,7 @@ import ( "github.com/golang/dep/internal/importers/glide" "github.com/golang/dep/internal/importers/godep" "github.com/golang/dep/internal/importers/govend" + "github.com/golang/dep/internal/importers/gvt" "github.com/golang/dep/internal/importers/vndr" ) @@ -35,5 +36,6 @@ func BuildAll(logger *log.Logger, verbose bool, sm gps.SourceManager) []Importer godep.NewImporter(logger, verbose, sm), vndr.NewImporter(logger, verbose, sm), govend.NewImporter(logger, verbose, sm), + gvt.NewImporter(logger, verbose, sm), } }