github.com/golang/dep@v0.5.4/project.go (about) 1 // Copyright 2016 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 dep 6 7 import ( 8 "fmt" 9 "os" 10 "path/filepath" 11 "sort" 12 "sync" 13 14 "github.com/golang/dep/gps" 15 "github.com/golang/dep/gps/pkgtree" 16 "github.com/golang/dep/gps/verify" 17 "github.com/golang/dep/internal/fs" 18 "github.com/pkg/errors" 19 ) 20 21 var ( 22 errProjectNotFound = fmt.Errorf("could not find project %s, use dep init to initiate a manifest", ManifestName) 23 errVendorBackupFailed = fmt.Errorf("failed to create vendor backup. File with same name exists") 24 ) 25 26 // findProjectRoot searches from the starting directory upwards looking for a 27 // manifest file until we get to the root of the filesystem. 28 func findProjectRoot(from string) (string, error) { 29 for { 30 mp := filepath.Join(from, ManifestName) 31 32 _, err := os.Stat(mp) 33 if err == nil { 34 return from, nil 35 } 36 if !os.IsNotExist(err) { 37 // Some err other than non-existence - return that out 38 return "", err 39 } 40 41 parent := filepath.Dir(from) 42 if parent == from { 43 return "", errProjectNotFound 44 } 45 from = parent 46 } 47 } 48 49 // checkGopkgFilenames validates filename case for the manifest and lock files. 50 // 51 // This is relevant on case-insensitive file systems like the defaults in Windows and 52 // macOS. 53 // 54 // If manifest file is not found, it returns an error indicating the project could not be 55 // found. If it is found but the case does not match, an error is returned. If a lock 56 // file is not found, no error is returned as lock file is optional. If it is found but 57 // the case does not match, an error is returned. 58 func checkGopkgFilenames(projectRoot string) error { 59 // ReadActualFilenames is actually costly. Since the check to validate filename case 60 // for Gopkg filenames is not relevant to case-sensitive filesystems like 61 // ext4(linux), try for an early return. 62 caseSensitive, err := fs.IsCaseSensitiveFilesystem(projectRoot) 63 if err != nil { 64 return errors.Wrap(err, "could not check validity of configuration filenames") 65 } 66 if caseSensitive { 67 return nil 68 } 69 70 actualFilenames, err := fs.ReadActualFilenames(projectRoot, []string{ManifestName, LockName}) 71 72 if err != nil { 73 return errors.Wrap(err, "could not check validity of configuration filenames") 74 } 75 76 actualMfName, found := actualFilenames[ManifestName] 77 if !found { 78 // Ideally this part of the code won't ever be executed if it is called after 79 // `findProjectRoot`. But be thorough and handle it anyway. 80 return errProjectNotFound 81 } 82 if actualMfName != ManifestName { 83 return fmt.Errorf("manifest filename %q does not match %q", actualMfName, ManifestName) 84 } 85 86 // If a file is not found, the string map returned by `fs.ReadActualFilenames` will 87 // not have an entry for the given filename. Since the lock file is optional, we 88 // should check for equality only if it was found. 89 actualLfName, found := actualFilenames[LockName] 90 if found && actualLfName != LockName { 91 return fmt.Errorf("lock filename %q does not match %q", actualLfName, LockName) 92 } 93 94 return nil 95 } 96 97 // A Project holds a Manifest and optional Lock for a project. 98 type Project struct { 99 // AbsRoot is the absolute path to the root directory of the project. 100 AbsRoot string 101 // ResolvedAbsRoot is the resolved absolute path to the root directory of the project. 102 // If AbsRoot is not a symlink, then ResolvedAbsRoot should equal AbsRoot. 103 ResolvedAbsRoot string 104 // ImportRoot is the import path of the project's root directory. 105 ImportRoot gps.ProjectRoot 106 // The Manifest, as read from Gopkg.toml on disk. 107 Manifest *Manifest 108 // The Lock, as read from Gopkg.lock on disk. 109 Lock *Lock // Optional 110 // The above Lock, with changes applied to it. There are two possible classes of 111 // changes: 112 // 1. Changes to InputImports 113 // 2. Changes to per-project prune options 114 ChangedLock *Lock 115 // The PackageTree representing the project, with hidden and ignored 116 // packages already trimmed. 117 RootPackageTree pkgtree.PackageTree 118 // Oncer to manage access to initial check of vendor. 119 CheckVendor sync.Once 120 // The result of calling verify.CheckDepTree against the current lock and 121 // vendor dir. 122 VendorStatus map[string]verify.VendorStatus 123 // The error, if any, from checking vendor. 124 CheckVendorErr error 125 } 126 127 // VerifyVendor checks the vendor directory against the hash digests in 128 // Gopkg.lock. 129 // 130 // This operation is overseen by the sync.Once in CheckVendor. This is intended 131 // to facilitate running verification in the background while solving, then 132 // having the results ready later. 133 func (p *Project) VerifyVendor() (map[string]verify.VendorStatus, error) { 134 p.CheckVendor.Do(func() { 135 p.VendorStatus = make(map[string]verify.VendorStatus) 136 vendorDir := filepath.Join(p.AbsRoot, "vendor") 137 138 var lps []gps.LockedProject 139 if p.Lock != nil { 140 lps = p.Lock.Projects() 141 } 142 143 sums := make(map[string]verify.VersionedDigest) 144 for _, lp := range lps { 145 sums[string(lp.Ident().ProjectRoot)] = lp.(verify.VerifiableProject).Digest 146 } 147 148 p.VendorStatus, p.CheckVendorErr = verify.CheckDepTree(vendorDir, sums) 149 }) 150 151 return p.VendorStatus, p.CheckVendorErr 152 } 153 154 // SetRoot sets the project AbsRoot and ResolvedAbsRoot. If root is not a symlink, ResolvedAbsRoot will be set to root. 155 func (p *Project) SetRoot(root string) error { 156 rroot, err := filepath.EvalSymlinks(root) 157 if err != nil { 158 return err 159 } 160 161 p.ResolvedAbsRoot, p.AbsRoot = rroot, root 162 return nil 163 } 164 165 // MakeParams is a simple helper to create a gps.SolveParameters without setting 166 // any nils incorrectly. 167 func (p *Project) MakeParams() gps.SolveParameters { 168 params := gps.SolveParameters{ 169 RootDir: p.AbsRoot, 170 ProjectAnalyzer: Analyzer{}, 171 RootPackageTree: p.RootPackageTree, 172 } 173 174 if p.Manifest != nil { 175 params.Manifest = p.Manifest 176 } 177 178 // It should be impossible for p.ChangedLock to be nil if p.Lock is non-nil; 179 // we always want to use the former for solving. 180 if p.ChangedLock != nil { 181 params.Lock = p.ChangedLock 182 } 183 184 return params 185 } 186 187 // parseRootPackageTree analyzes the root project's disk contents to create a 188 // PackageTree, trimming out packages that are not relevant for root projects 189 // along the way. 190 // 191 // The resulting tree is cached internally at p.RootPackageTree. 192 func (p *Project) parseRootPackageTree() (pkgtree.PackageTree, error) { 193 if p.RootPackageTree.Packages == nil { 194 ptree, err := pkgtree.ListPackages(p.ResolvedAbsRoot, string(p.ImportRoot)) 195 if err != nil { 196 return pkgtree.PackageTree{}, errors.Wrap(err, "analysis of current project's packages failed") 197 } 198 // We don't care about (unreachable) hidden packages for the root project, 199 // so drop all of those. 200 var ig *pkgtree.IgnoredRuleset 201 if p.Manifest != nil { 202 ig = p.Manifest.IgnoredPackages() 203 } 204 p.RootPackageTree = ptree.TrimHiddenPackages(true, true, ig) 205 } 206 return p.RootPackageTree, nil 207 } 208 209 // GetDirectDependencyNames returns the set of unique Project Roots that are the 210 // direct dependencies of this Project. 211 // 212 // A project is considered a direct dependency if at least one of its packages 213 // is named in either this Project's required list, or if there is at least one 214 // non-ignored import statement from a non-ignored package in the current 215 // project's package tree. 216 // 217 // The returned map of Project Roots contains only boolean true values; this 218 // makes a "false" value always indicate an absent key, which makes conditional 219 // checks against the map more ergonomic. 220 // 221 // This function will correctly utilize ignores and requireds from an existing 222 // manifest, if one is present, but will also do the right thing without a 223 // manifest. 224 func (p *Project) GetDirectDependencyNames(sm gps.SourceManager) (map[gps.ProjectRoot]bool, error) { 225 var reach []string 226 if p.ChangedLock != nil { 227 reach = p.ChangedLock.InputImports() 228 } else { 229 ptree, err := p.parseRootPackageTree() 230 if err != nil { 231 return nil, err 232 } 233 reach = externalImportList(ptree, p.Manifest) 234 } 235 236 directDeps := map[gps.ProjectRoot]bool{} 237 for _, ip := range reach { 238 pr, err := sm.DeduceProjectRoot(ip) 239 if err != nil { 240 return nil, err 241 } 242 directDeps[pr] = true 243 } 244 245 return directDeps, nil 246 } 247 248 // FindIneffectualConstraints looks for constraint rules expressed in the 249 // manifest that will have no effect during solving, as they are specified for 250 // projects that are not direct dependencies of the Project. 251 // 252 // "Direct dependency" here is as implemented by GetDirectDependencyNames(); 253 // it correctly incorporates all "ignored" and "required" rules. 254 func (p *Project) FindIneffectualConstraints(sm gps.SourceManager) []gps.ProjectRoot { 255 if p.Manifest == nil { 256 return nil 257 } 258 259 dd, err := p.GetDirectDependencyNames(sm) 260 if err != nil { 261 return nil 262 } 263 264 var ineff []gps.ProjectRoot 265 for pr := range p.Manifest.DependencyConstraints() { 266 if !dd[pr] { 267 ineff = append(ineff, pr) 268 } 269 } 270 271 sort.Slice(ineff, func(i, j int) bool { 272 return ineff[i] < ineff[j] 273 }) 274 return ineff 275 } 276 277 // BackupVendor looks for existing vendor directory and if it's not empty, 278 // creates a backup of it to a new directory with the provided suffix. 279 func BackupVendor(vpath, suffix string) (string, error) { 280 // Check if there's a non-empty vendor directory 281 vendorExists, err := fs.IsNonEmptyDir(vpath) 282 if err != nil && !os.IsNotExist(err) { 283 return "", err 284 } 285 if vendorExists { 286 // vpath is a full filepath. We need to split it to prefix the backup dir 287 // with an "_" 288 vpathDir, name := filepath.Split(vpath) 289 vendorbak := filepath.Join(vpathDir, "_"+name+"-"+suffix) 290 // Check if a directory with same name exists 291 if _, err = os.Stat(vendorbak); os.IsNotExist(err) { 292 // Copy existing vendor to vendor-{suffix} 293 if err := fs.CopyDir(vpath, vendorbak); err != nil { 294 return "", err 295 } 296 return vendorbak, nil 297 } 298 return "", errVendorBackupFailed 299 } 300 301 return "", nil 302 }