This repository has been archived by the owner on Sep 9, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1k
Standardize the importers #1100
Merged
+1,137
−1,097
Merged
Changes from 5 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
f6e75af
cmd/dep: Standardize import logic for importers
carolynvs b3dff94
cmd/dep: Support new importer rules
carolynvs 42891a6
cmd/dep: Switch to special importer testdata set
carolynvs 2d44886
cmd/dep: Move import test execution into testcase
carolynvs 1407196
cmd/dep: Validate warnings in import testcases
carolynvs 6f6b195
cmd/dep: Document every baseImporter function
carolynvs 58cbb3f
cmd/dep: Simplify pinned constraint check
carolynvs 9356f05
cmd/dep: fix parallel tests
carolynvs 0f33a59
cmd/dep: verify empty locks are skipped
carolynvs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
// 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 ( | ||
"log" | ||
|
||
"github.com/golang/dep" | ||
fb "github.com/golang/dep/internal/feedback" | ||
"github.com/golang/dep/internal/gps" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// baseImporter provides a common implementation for importing from other | ||
// dependency managers. | ||
type baseImporter struct { | ||
logger *log.Logger | ||
verbose bool | ||
sm gps.SourceManager | ||
manifest *dep.Manifest | ||
lock *dep.Lock | ||
} | ||
|
||
// newBaseImporter creates a new baseImporter for embedding in an importer. | ||
func newBaseImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *baseImporter { | ||
return &baseImporter{ | ||
logger: logger, | ||
verbose: verbose, | ||
manifest: dep.NewManifest(), | ||
lock: &dep.Lock{}, | ||
sm: sm, | ||
} | ||
} | ||
|
||
// isTag determines if the specified value is a tag (plain or semver). | ||
func (i *baseImporter) isTag(pi gps.ProjectIdentifier, value string) (bool, gps.Version, error) { | ||
versions, err := i.sm.ListVersions(pi) | ||
if err != nil { | ||
return false, nil, errors.Wrapf(err, "unable to list versions for %s(%s)", pi.ProjectRoot, pi.Source) | ||
} | ||
|
||
for _, version := range versions { | ||
if version.Type() != gps.IsVersion && version.Type() != gps.IsSemver { | ||
continue | ||
} | ||
|
||
if value == version.String() { | ||
return true, version, nil | ||
} | ||
} | ||
|
||
return false, nil, nil | ||
} | ||
|
||
// lookupVersionForLockedProject figures out the appropriate version for a locked | ||
// project based on the locked revision and the constraint from the manifest. | ||
// First try matching the revision to a version, then try the constraint from the | ||
// manifest, then finally the revision. | ||
func (i *baseImporter) lookupVersionForLockedProject(pi gps.ProjectIdentifier, c gps.Constraint, rev gps.Revision) (gps.Version, error) { | ||
// Find the version that goes with this revision, if any | ||
versions, err := i.sm.ListVersions(pi) | ||
if err != nil { | ||
return rev, errors.Wrapf(err, "Unable to lookup the version represented by %s in %s(%s). Falling back to locking the revision only.", rev, pi.ProjectRoot, pi.Source) | ||
} | ||
|
||
var branchConstraint gps.PairedVersion | ||
gps.SortPairedForUpgrade(versions) // Sort versions in asc order | ||
matches := []gps.Version{} | ||
for _, v := range versions { | ||
if v.Revision() == rev { | ||
matches = append(matches, v) | ||
} | ||
if c != nil && v.Type() == gps.IsBranch && v.String() == c.String() { | ||
branchConstraint = v | ||
} | ||
} | ||
|
||
// Try to narrow down the matches with the constraint. Otherwise return the first match. | ||
if len(matches) > 0 { | ||
if c != nil { | ||
for _, v := range matches { | ||
if i.testConstraint(c, v) { | ||
return v, nil | ||
} | ||
} | ||
} | ||
return matches[0], nil | ||
} | ||
|
||
// Use branch constraint from the manifest | ||
if branchConstraint != nil { | ||
return branchConstraint.Unpair().Pair(rev), nil | ||
} | ||
|
||
// Give up and lock only to a revision | ||
return rev, nil | ||
} | ||
|
||
// importedPackage is a common intermediate representation of a package imported | ||
// from an external tool's configuration. | ||
type importedPackage struct { | ||
// Required. The package path, not necessarily the project root. | ||
Name string | ||
|
||
// Required. Text representing a revision or tag. | ||
LockHint string | ||
|
||
// Optional. Alternative source, or fork, for the project. | ||
Source string | ||
|
||
// Optional. Text representing a branch or version. | ||
ConstraintHint string | ||
} | ||
|
||
// importedProject is a consolidated representation of a set of imported packages | ||
// for the same project root. | ||
type importedProject struct { | ||
Root gps.ProjectRoot | ||
importedPackage | ||
} | ||
|
||
// loadPackages consolidates all package references into a set of project roots. | ||
func (i *baseImporter) loadPackages(packages []importedPackage) ([]importedProject, error) { | ||
// preserve the original order of the packages so that messages that | ||
// are printed as they are processed are in a consistent order. | ||
orderedProjects := make([]importedProject, 0, len(packages)) | ||
|
||
projects := make(map[gps.ProjectRoot]*importedProject, len(packages)) | ||
for _, pkg := range packages { | ||
pr, err := i.sm.DeduceProjectRoot(pkg.Name) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "Cannot determine the project root for %s", pkg.Name) | ||
} | ||
pkg.Name = string(pr) | ||
|
||
prj, exists := projects[pr] | ||
if !exists { | ||
prj := importedProject{pr, pkg} | ||
orderedProjects = append(orderedProjects, prj) | ||
projects[pr] = &orderedProjects[len(orderedProjects)-1] | ||
continue | ||
} | ||
|
||
// The config found first "wins", though we allow for incrementally | ||
// setting each field because some importers have a config and lock file. | ||
if prj.Source == "" && pkg.Source != "" { | ||
prj.Source = pkg.Source | ||
} | ||
|
||
if prj.ConstraintHint == "" && pkg.ConstraintHint != "" { | ||
prj.ConstraintHint = pkg.ConstraintHint | ||
} | ||
|
||
if prj.LockHint == "" && pkg.LockHint != "" { | ||
prj.LockHint = pkg.LockHint | ||
} | ||
} | ||
|
||
return orderedProjects, nil | ||
} | ||
|
||
// importPackages loads imported packages into the manifest and lock. | ||
// - defaultConstraintFromLock specifies if a constraint should be defaulted | ||
// based on the locked version when there wasn't a constraint hint. | ||
// | ||
// Rules: | ||
// * When a constraint is ignored, default to *. | ||
// * HEAD revisions default to the matching branch. | ||
// * Semantic versions default to ^VERSION. | ||
// * Revision constraints are ignored. | ||
// * Versions that don't satisfy the constraint, drop the constraint. | ||
// * Untagged revisions ignore non-branch constraint hints. | ||
func (i *baseImporter) importPackages(packages []importedPackage, defaultConstraintFromLock bool) (err error) { | ||
projects, err := i.loadPackages(packages) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, prj := range projects { | ||
pc := gps.ProjectConstraint{ | ||
Ident: gps.ProjectIdentifier{ | ||
ProjectRoot: prj.Root, | ||
Source: prj.Source, | ||
}, | ||
} | ||
|
||
pc.Constraint, err = i.sm.InferConstraint(prj.ConstraintHint, pc.Ident) | ||
if err != nil { | ||
pc.Constraint = gps.Any() | ||
} | ||
|
||
var version gps.Version | ||
if prj.LockHint != "" { | ||
var isTag bool | ||
// Determine if the lock hint is a revision or tag | ||
isTag, version, err = i.isTag(pc.Ident, prj.LockHint) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// If the hint is a revision, check if it is tagged | ||
if !isTag { | ||
revision := gps.Revision(prj.LockHint) | ||
version, err = i.lookupVersionForLockedProject(pc.Ident, pc.Constraint, revision) | ||
if err != nil { | ||
version = nil | ||
i.logger.Println(err) | ||
} | ||
} | ||
|
||
// Default the constraint based on the locked version | ||
if defaultConstraintFromLock && prj.ConstraintHint == "" && version != nil { | ||
props := getProjectPropertiesFromVersion(version) | ||
if props.Constraint != nil { | ||
pc.Constraint = props.Constraint | ||
} | ||
} | ||
} | ||
|
||
// Ignore pinned constraints | ||
if i.isConstraintPinned(pc.Constraint) { | ||
if i.verbose { | ||
i.logger.Printf(" Ignoring pinned constraint %v for %v.\n", pc.Constraint, pc.Ident) | ||
} | ||
pc.Constraint = gps.Any() | ||
} | ||
|
||
// Ignore constraints which conflict with the locked revision, so that | ||
// solve doesn't later change the revision to satisfy the constraint. | ||
if !i.testConstraint(pc.Constraint, version) { | ||
if i.verbose { | ||
i.logger.Printf(" Ignoring constraint %v for %v because it would invalidate the locked version %v.\n", pc.Constraint, pc.Ident, version) | ||
} | ||
pc.Constraint = gps.Any() | ||
} | ||
|
||
i.manifest.Constraints[pc.Ident.ProjectRoot] = gps.ProjectProperties{ | ||
Source: pc.Ident.Source, | ||
Constraint: pc.Constraint, | ||
} | ||
fb.NewConstraintFeedback(pc, fb.DepTypeImported).LogFeedback(i.logger) | ||
|
||
if version != nil { | ||
lp := gps.NewLockedProject(pc.Ident, version, nil) | ||
i.lock.P = append(i.lock.P, lp) | ||
fb.NewLockedProjectFeedback(lp, fb.DepTypeImported).LogFeedback(i.logger) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (i *baseImporter) isConstraintPinned(c gps.Constraint) bool { | ||
if version, isVersion := c.(gps.Version); isVersion { | ||
switch version.Type() { | ||
case gps.IsRevision: | ||
return true | ||
case gps.IsVersion: | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (i *baseImporter) testConstraint(c gps.Constraint, v gps.Version) bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment? |
||
// Assume branch constraints are satisfied | ||
if version, isVersion := c.(gps.Version); isVersion { | ||
if version.Type() == gps.IsBranch { | ||
|
||
return true | ||
} | ||
} | ||
|
||
return c.Matches(v) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I knew there was a better way of doing that! ❤️