github.com/golang/dep@v0.5.4/internal/importers/base/importer.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package base 6 7 import ( 8 "log" 9 "strings" 10 11 "github.com/golang/dep" 12 "github.com/golang/dep/gps" 13 fb "github.com/golang/dep/internal/feedback" 14 "github.com/pkg/errors" 15 ) 16 17 // Importer provides a common implementation for importing from other 18 // dependency managers. 19 type Importer struct { 20 SourceManager gps.SourceManager 21 Logger *log.Logger 22 Verbose bool 23 Manifest *dep.Manifest 24 Lock *dep.Lock 25 } 26 27 // NewImporter creates a new Importer for embedding in an importer. 28 func NewImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *Importer { 29 return &Importer{ 30 Logger: logger, 31 Verbose: verbose, 32 Manifest: dep.NewManifest(), 33 Lock: &dep.Lock{}, 34 SourceManager: sm, 35 } 36 } 37 38 // isTag determines if the specified value is a tag (plain or semver). 39 func (i *Importer) isTag(pi gps.ProjectIdentifier, value string) (bool, gps.Version, error) { 40 versions, err := i.SourceManager.ListVersions(pi) 41 if err != nil { 42 return false, nil, errors.Wrapf(err, "unable to list versions for %s(%s)", pi.ProjectRoot, pi.Source) 43 } 44 45 for _, version := range versions { 46 if version.Type() != gps.IsVersion && version.Type() != gps.IsSemver { 47 continue 48 } 49 50 if value == version.String() { 51 return true, version, nil 52 } 53 } 54 55 return false, nil, nil 56 } 57 58 // lookupVersionForLockedProject figures out the appropriate version for a locked 59 // project based on the locked revision and the constraint from the manifest. 60 // First try matching the revision to a version, then try the constraint from the 61 // manifest, then finally the revision. 62 func (i *Importer) lookupVersionForLockedProject(pi gps.ProjectIdentifier, c gps.Constraint, rev gps.Revision) (gps.Version, error) { 63 // Find the version that goes with this revision, if any 64 versions, err := i.SourceManager.ListVersions(pi) 65 if err != nil { 66 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) 67 } 68 69 var branchConstraint gps.PairedVersion 70 gps.SortPairedForUpgrade(versions) // Sort versions in asc order 71 var matches []gps.Version 72 for _, v := range versions { 73 if v.Revision() == rev { 74 matches = append(matches, v) 75 } 76 if c != nil && v.Type() == gps.IsBranch && v.String() == c.String() { 77 branchConstraint = v 78 } 79 } 80 81 // Try to narrow down the matches with the constraint. Otherwise return the first match. 82 if len(matches) > 0 { 83 if c != nil { 84 for _, v := range matches { 85 if i.testConstraint(c, v) { 86 return v, nil 87 } 88 } 89 } 90 return matches[0], nil 91 } 92 93 // Use branch constraint from the manifest 94 if branchConstraint != nil { 95 return branchConstraint.Unpair().Pair(rev), nil 96 } 97 98 // Give up and lock only to a revision 99 return rev, nil 100 } 101 102 // ImportedPackage is a common intermediate representation of a package imported 103 // from an external tool's configuration. 104 type ImportedPackage struct { 105 // Required. The package path, not necessarily the project root. 106 Name string 107 108 // Required. Text representing a revision or tag. 109 LockHint string 110 111 // Optional. Alternative source, or fork, for the project. 112 Source string 113 114 // Optional. Text representing a branch or version. 115 ConstraintHint string 116 } 117 118 // importedProject is a consolidated representation of a set of imported packages 119 // for the same project root. 120 type importedProject struct { 121 Root gps.ProjectRoot 122 ImportedPackage 123 } 124 125 // loadPackages consolidates all package references into a set of project roots. 126 func (i *Importer) loadPackages(packages []ImportedPackage) []importedProject { 127 // preserve the original order of the packages so that messages that 128 // are printed as they are processed are in a consistent order. 129 orderedProjects := make([]importedProject, 0, len(packages)) 130 131 projects := make(map[gps.ProjectRoot]*importedProject, len(packages)) 132 for _, pkg := range packages { 133 pr, err := i.SourceManager.DeduceProjectRoot(pkg.Name) 134 if err != nil { 135 i.Logger.Printf( 136 " Warning: Skipping project. Cannot determine the project root for %s: %s\n", 137 pkg.Name, err, 138 ) 139 continue 140 } 141 pkg.Name = string(pr) 142 143 prj, exists := projects[pr] 144 if !exists { 145 prj := importedProject{pr, pkg} 146 orderedProjects = append(orderedProjects, prj) 147 projects[pr] = &orderedProjects[len(orderedProjects)-1] 148 continue 149 } 150 151 // The config found first "wins", though we allow for incrementally 152 // setting each field because some importers have a config and lock file. 153 if prj.Source == "" && pkg.Source != "" { 154 prj.Source = pkg.Source 155 } 156 157 if prj.ConstraintHint == "" && pkg.ConstraintHint != "" { 158 prj.ConstraintHint = pkg.ConstraintHint 159 } 160 161 if prj.LockHint == "" && pkg.LockHint != "" { 162 prj.LockHint = pkg.LockHint 163 } 164 } 165 166 return orderedProjects 167 } 168 169 // ImportPackages loads imported packages into the manifest and lock. 170 // - defaultConstraintFromLock specifies if a constraint should be defaulted 171 // based on the locked version when there wasn't a constraint hint. 172 // 173 // Rules: 174 // * When a constraint is ignored, default to *. 175 // * HEAD revisions default to the matching branch. 176 // * Semantic versions default to ^VERSION. 177 // * Revision constraints are ignored. 178 // * Versions that don't satisfy the constraint, drop the constraint. 179 // * Untagged revisions ignore non-branch constraint hints. 180 func (i *Importer) ImportPackages(packages []ImportedPackage, defaultConstraintFromLock bool) { 181 projects := i.loadPackages(packages) 182 183 for _, prj := range projects { 184 source := prj.Source 185 if len(source) > 0 { 186 isDefault, err := i.isDefaultSource(prj.Root, source) 187 if err != nil { 188 i.Logger.Printf(" Ignoring imported source %s for %s: %s", source, prj.Root, err.Error()) 189 source = "" 190 } else if isDefault { 191 source = "" 192 } else if strings.Contains(source, "/vendor/") { 193 i.Logger.Printf(" Ignoring imported source %s for %s because vendored sources aren't supported", source, prj.Root) 194 source = "" 195 } 196 } 197 198 pc := gps.ProjectConstraint{ 199 Ident: gps.ProjectIdentifier{ 200 ProjectRoot: prj.Root, 201 Source: source, 202 }, 203 } 204 205 var err error 206 pc.Constraint, err = i.SourceManager.InferConstraint(prj.ConstraintHint, pc.Ident) 207 if err != nil { 208 pc.Constraint = gps.Any() 209 } 210 211 var version gps.Version 212 if prj.LockHint != "" { 213 var isTag bool 214 // Determine if the lock hint is a revision or tag 215 isTag, version, err = i.isTag(pc.Ident, prj.LockHint) 216 if err != nil { 217 i.Logger.Printf( 218 " Warning: Skipping project. Unable to import lock %q for %v: %s\n", 219 prj.LockHint, pc.Ident, err, 220 ) 221 continue 222 } 223 // If the hint is a revision, check if it is tagged 224 if !isTag { 225 revision := gps.Revision(prj.LockHint) 226 version, err = i.lookupVersionForLockedProject(pc.Ident, pc.Constraint, revision) 227 if err != nil { 228 version = nil 229 i.Logger.Println(err) 230 } 231 } 232 233 // Default the constraint based on the locked version 234 if defaultConstraintFromLock && prj.ConstraintHint == "" && version != nil { 235 c := i.convertToConstraint(version) 236 if c != nil { 237 pc.Constraint = c 238 } 239 } 240 } 241 242 // Ignore pinned constraints 243 if i.isConstraintPinned(pc.Constraint) { 244 if i.Verbose { 245 i.Logger.Printf(" Ignoring pinned constraint %v for %v.\n", pc.Constraint, pc.Ident) 246 } 247 pc.Constraint = gps.Any() 248 } 249 250 // Ignore constraints which conflict with the locked revision, so that 251 // solve doesn't later change the revision to satisfy the constraint. 252 if !i.testConstraint(pc.Constraint, version) { 253 if i.Verbose { 254 i.Logger.Printf(" Ignoring constraint %v for %v because it would invalidate the locked version %v.\n", pc.Constraint, pc.Ident, version) 255 } 256 pc.Constraint = gps.Any() 257 } 258 259 // Add constraint to manifest that is not empty (has a branch, version or source) 260 if !gps.IsAny(pc.Constraint) || pc.Ident.Source != "" { 261 i.Manifest.Constraints[pc.Ident.ProjectRoot] = gps.ProjectProperties{ 262 Source: pc.Ident.Source, 263 Constraint: pc.Constraint, 264 } 265 fb.NewConstraintFeedback(pc, fb.DepTypeImported).LogFeedback(i.Logger) 266 } 267 268 if version != nil { 269 lp := gps.NewLockedProject(pc.Ident, version, nil) 270 i.Lock.P = append(i.Lock.P, lp) 271 fb.NewLockedProjectFeedback(lp, fb.DepTypeImported).LogFeedback(i.Logger) 272 } 273 } 274 } 275 276 // isConstraintPinned returns if a constraint is pinned to a specific revision. 277 func (i *Importer) isConstraintPinned(c gps.Constraint) bool { 278 if version, isVersion := c.(gps.Version); isVersion { 279 switch version.Type() { 280 case gps.IsRevision, gps.IsVersion: 281 return true 282 } 283 } 284 return false 285 } 286 287 // testConstraint verifies that the constraint won't invalidate the locked version. 288 func (i *Importer) testConstraint(c gps.Constraint, v gps.Version) bool { 289 // Assume branch constraints are satisfied 290 if version, isVersion := c.(gps.Version); isVersion { 291 if version.Type() == gps.IsBranch { 292 293 return true 294 } 295 } 296 297 return c.Matches(v) 298 } 299 300 // convertToConstraint turns a version into a constraint. 301 // Semver tags are converted to a range with the caret operator. 302 func (i *Importer) convertToConstraint(v gps.Version) gps.Constraint { 303 if v.Type() == gps.IsSemver { 304 c, err := gps.NewSemverConstraintIC(v.String()) 305 if err != nil { 306 // This should never fail, because the type is semver. 307 // If it does fail somehow, don't let that impact the import. 308 return nil 309 } 310 return c 311 } 312 return v 313 } 314 315 func (i *Importer) isDefaultSource(projectRoot gps.ProjectRoot, sourceURL string) (bool, error) { 316 // this condition is mainly for gopkg.in imports, 317 // as some importers specify the repository url as https://gopkg.in/..., 318 // but SourceManager.SourceURLsForPath() returns https://github.com/... urls for gopkg.in 319 if sourceURL == "https://"+string(projectRoot) { 320 return true, nil 321 } 322 323 sourceURLs, err := i.SourceManager.SourceURLsForPath(string(projectRoot)) 324 if err != nil { 325 return false, err 326 } 327 // The first url in the slice will be the default one (usually https://...) 328 if len(sourceURLs) > 0 && sourceURL == sourceURLs[0].String() { 329 return true, nil 330 } 331 332 return false, nil 333 }