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

Commit

Permalink
Initial gb project importer
Browse files Browse the repository at this point in the history
  • Loading branch information
avidal committed Jul 15, 2017
1 parent 911cd22 commit 4916449
Show file tree
Hide file tree
Showing 5 changed files with 367 additions and 0 deletions.
169 changes: 169 additions & 0 deletions cmd/dep/gb_importer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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"
fb "github.com/golang/dep/internal/feedback"
"github.com/golang/dep/internal/gps"
"github.com/pkg/errors"
)

// gbImporter imports gb configuration into the dep configuration format.
type gbImporter struct {
manifest gbManifest
logger *log.Logger
verbose bool
sm gps.SourceManager
}

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

// gbManifest represents the manifest file for GB projects
type gbManifest struct {
Version int `json:"version"`
Dependencies []gbDependency `json:"dependencies"`
}

type gbDependency struct {
Importpath string `json:"importpath"`
Repository string `json:"repository"`

// All gb vendored dependencies have a specific revision
Revision string `json:"revision"`

// Branch may be HEAD or an actual branch. In the case of HEAD, that means
// the user vendored a dependency by specifying a tag or a specific revision
// which results in a detached HEAD
Branch string `json:"branch"`

// Path is used when the actual used package exists in a subpath
// For importing purposes, this isn't necessary as dep will figure
// out which packages are actually being used during resolving
Path string `json:"path,omitempty"`
}

func (i *gbImporter) Name() string {
return "gb"
}

func (i *gbImporter) HasDepMetadata(dir string) bool {
// gb stores the manifest in the vendor tree
var m = filepath.Join(dir, "vendor", "manifest")
if _, err := os.Stat(m); err != nil {
return false
}

return true
}

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

return i.convert(pr)
}

// load the gb manifest
func (i *gbImporter) load(projectDir string) error {
i.logger.Println("Detected gb manifest file...")
var err error
var mf = filepath.Join(projectDir, "vendor", "manifest")
if i.verbose {
i.logger.Printf(" Loading %s", mf)
}

var buf []byte
if buf, err = ioutil.ReadFile(mf); err != nil {
return errors.Wrapf(err, "Unable to read %s", mf)
}
if err = json.Unmarshal(buf, &i.manifest); err != nil {
return errors.Wrapf(err, "Unable to parse %s", mf)
}

return nil
}

// convert the gb manifest into dep configuration files.
func (i *gbImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) {
i.logger.Println("Converting from gb manifest...")

manifest := &dep.Manifest{
Constraints: make(gps.ProjectConstraints),
}

lock := &dep.Lock{}

for _, pkg := range i.manifest.Dependencies {
pc, lp, err := i.convertOne(pkg)
if err != nil {
return nil, nil, err
}
manifest.Constraints[pc.Ident.ProjectRoot] = gps.ProjectProperties{Source: pc.Ident.Source, Constraint: pc.Constraint}
lock.P = append(lock.P, lp)
}

return manifest, lock, nil
}

func (i *gbImporter) convertOne(pkg gbDependency) (pc gps.ProjectConstraint, lp gps.LockedProject, err error) {
if pkg.Importpath == "" {
err = errors.New("Invalid gb configuration, package import path is required")
return
}

/*
gb's vendor plugin (gb vendor), which manages the vendor tree and manifest
file, supports fetching by a specific tag or revision, but if you specify
either of those it's a detached checkout and the "branch" field is HEAD.
The only time the "branch" field is not "HEAD" is if you do not specify a
tag or revision, otherwise it's either "master" or the value of the -branch
flag
This means that, generally, the only possible "constraint" we can really specify is
the branch name if the branch name is not HEAD. Otherwise, it's a specific revision.
However, if we can infer a tag that points to the revision or the branch, we may be able
to use that as the constraint
*/

pc.Ident = gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot(pkg.Importpath), Source: pkg.Repository}

// The default constraint is just a revision
var revision = gps.Revision(pkg.Revision)

// See if we can get a version from that constraint
version, err := lookupVersionForLockedProject(pc.Ident, revision, revision, i.sm)
if err != nil {
return
}

// And now try to infer a constraint from the returned version
pc.Constraint, err = i.sm.InferConstraint(version.String(), pc.Ident)
if err != nil {
return
}

lp = gps.NewLockedProject(pc.Ident, version, nil)

fb.NewLockedProjectFeedback(lp, fb.DepTypeImported).LogFeedback(i.logger)
fb.NewConstraintFeedback(pc, fb.DepTypeImported).LogFeedback(i.logger)

return
}
165 changes: 165 additions & 0 deletions cmd/dep/gb_importer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// 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"
)

const testGbProjectRoot = "github.com/golang/notexist"

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

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

h.TempDir(filepath.Join("src", testGbProjectRoot, "vendor"))
h.TempCopy(filepath.Join(testGbProjectRoot, "vendor", "manifest"), "gb/manifest")
projectRoot := h.Path(testGbProjectRoot)

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

g := newGbImporter(ctx.Err, 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 the gb manifest file")
}

m, l, err := g.Import(projectRoot, testGbProjectRoot)
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 := "gb/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("expected %s, got %s", want, got)
}
}
}

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

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

pkg := "github.com/sdboyer/deptest"
repo := "https://github.com/sdboyer/deptest.git"

g := newGbImporter(ctx.Err, true, sm)
g.manifest = gbManifest{
Dependencies: []gbDependency{
{
Importpath: pkg,
Repository: repo,
Revision: "ff2948a2ac8f538c4ecd55962e919d1e13e74baf",
},
},
}

manifest, lock, err := g.convert(testGbProjectRoot)
if err != nil {
t.Fatal(err)
}

d, ok := manifest.Constraints[gps.ProjectRoot(pkg)]
if !ok {
t.Fatal("Expected the manifest to have a dependency for 'github.com/sdboyer/deptest' but got none")
}

wantC := "^1.0.0"
gotC := d.Constraint.String()
if gotC != wantC {
t.Fatalf("Expected manifest constraint to be %s, got %s", wantC, gotC)
}

gotS := d.Source
if gotS != repo {
t.Fatalf("Expected manifest source to be %s, got %s", repo, gotS)
}

wantP := 1
gotP := len(lock.P)
if gotP != 1 {
t.Fatalf("Expected the lock to contain %d project but got %d", wantP, gotP)
}

p := lock.P[0]
gotPr := string(p.Ident().ProjectRoot)
if gotPr != pkg {
t.Fatalf("Expected the lock to have a project for %s but got '%s'", pkg, gotPr)
}

gotS = p.Ident().Source
if gotS != repo {
t.Fatalf("Expected locked source to be %s, got '%s'", repo, gotS)
}

lv := p.Version()
lpv, ok := lv.(gps.PairedVersion)
if !ok {
t.Fatalf("Expected locked version to be a PairedVersion but got %T", lv)
}

wantRev := "ff2948a2ac8f538c4ecd55962e919d1e13e74baf"
gotRev := lpv.Revision().String()
if gotRev != wantRev {
t.Fatalf("Expected locked revision to be %s, got %s", wantRev, gotRev)
}

wantV := "v1.0.0"
gotV := lpv.String()
if gotV != wantV {
t.Fatalf("Expected locked version to be %s, got %s", wantV, gotV)
}
}

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

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

g := newGbImporter(ctx.Err, true, sm)
g.manifest = gbManifest{
Dependencies: []gbDependency{{Importpath: ""}},
}

_, _, err = g.convert(testGbProjectRoot)
if err == nil {
t.Fatal("Expected conversion to fail because the package name is empty")
}
}
1 change: 1 addition & 0 deletions cmd/dep/root_analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func (a *rootAnalyzer) importManifestAndLock(dir string, pr gps.ProjectRoot, sup
importers := []importer{
newGlideImporter(logger, a.ctx.Verbose, a.sm),
newGodepImporter(logger, a.ctx.Verbose, a.sm),
newGbImporter(logger, a.ctx.Verbose, a.sm),
}

for _, i := range importers {
Expand Down
7 changes: 7 additions & 0 deletions cmd/dep/testdata/gb/golden.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Detected gb manifest file...
Converting from gb manifest...
Trying master (2788f0d) as initial lock for imported dep github.com/kr/fs
Using master as initial constraint for imported dep github.com/kr/fs
Using f234c3c6540c0358b1802f7fd90c0879af9232eb as initial hint for imported dep github.com/pkg/sftp
Trying v1.0.0 (ff2948a) as initial lock for imported dep github.com/sdboyer/deptest
Using ^1.0.0 as initial constraint for imported dep github.com/sdboyer/deptest
25 changes: 25 additions & 0 deletions cmd/dep/testdata/gb/manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"version": 0,
"dependencies": [
{
"importpath": "github.com/kr/fs",
"repository": "https://github.com/kr/fs",
"revision": "2788f0dbd16903de03cb8186e5c7d97b69ad387b",
"branch": "master",
"path": ""
},
{
"importpath": "github.com/pkg/sftp",
"repository": "https://github.com/pkg/sftp",
"revision": "f234c3c6540c0358b1802f7fd90c0879af9232eb",
"branch": "master",
"path": ""
},
{
"importpath": "github.com/sdboyer/deptest",
"repository": "https://github.com/sdboyer/deptest",
"revision": "ff2948a2ac8f538c4ecd55962e919d1e13e74baf",
"branch": "HEAD"
}
]
}

0 comments on commit 4916449

Please sign in to comment.