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  }