github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/analyzers/golang/project.go (about)

     1  package golang
     2  
     3  import (
     4  	"github.com/apex/log"
     5  
     6  	"github.com/fossas/fossa-cli/analyzers/golang/resolver"
     7  	"github.com/fossas/fossa-cli/vcs"
     8  )
     9  
    10  // A Project is a single folder that forms a coherent "project" for a developer
    11  // and is versioned as a single unit. It may contain multiple Go packages.
    12  type Project struct {
    13  	Tool       resolver.Type // Name of the dependency management tool used by the project, if any.
    14  	Manifest   string        // Absolute path to the tool's manifest file for this project, if any.
    15  	Dir        string        // Absolute path of the first-party code folder, if any.
    16  	ImportPath string        // Import path prefix of project code.
    17  }
    18  
    19  // Project calculates the project containing any Go package.
    20  //
    21  // This function searches upwards from the Go package's directory, looking for
    22  // lockfiles of supported dependency management tools. If none are found, it
    23  // fails.
    24  //
    25  // The rationale for this design is that the packages in a "project" are
    26  // versioned together. There are two reasonable ways to capture the notion of
    27  // "versioned together":
    28  //
    29  //   1. The nearest lockfile. The nearest lockfile to the package probably locks
    30  //      the dependencies of the package.
    31  //   2. The nearest VCS repository. The nearest VCS repository probably contains
    32  //      the current "project" being worked on. The only common exception to this
    33  //      is monorepos, in which case all the contents of the repository are
    34  //      probably internal, so allowing packages within the repository to be
    35  //      unresolved is probably acceptable.
    36  //
    37  // There are a couple issues with both of these:
    38  //
    39  //   1. The nearest lockfile is not guaranteed to exist. When it does, it's not
    40  //      guaranteed to be _the_ semantic lockfile for the package -- this is
    41  //      merely a very common convention, not a requirement.
    42  //   2. The package is not guaranteed to be in a VCS repository. When it is, the
    43  //      repository might be extremely weird. One example of this is a repository
    44  //      containing the entire $GOPATH (which is a reasonable convention that
    45  //      some early adopters of Go used).
    46  //
    47  // This function tries its best to mitigate both of these issues:
    48  //
    49  //   1. The nearest lockfile is used for resolving versions. This is a very
    50  //      strong convention.
    51  //   2. The nearest VCS repository is used for determining allowed unresolved
    52  //      import paths. This is also a very strong convention.
    53  //
    54  // Both of these assumptions can be overridden by the user.
    55  func (a *Analyzer) Project(pkg string) (Project, error) {
    56  	log.Debugf("%#v", pkg)
    57  
    58  	// Check for a cached project.
    59  	cached, ok := a.projectCache[pkg]
    60  	if ok {
    61  		return cached, nil
    62  	}
    63  
    64  	// Get the package directory.
    65  	dir, err := a.Dir(pkg)
    66  	if err != nil {
    67  		return Project{}, err
    68  	}
    69  
    70  	// Find the nearest lockfile.
    71  	tool, manifestDir, err := NearestLockfile(dir)
    72  	if err != nil {
    73  		return Project{}, err
    74  	}
    75  
    76  	// Find the nearest VCS repository.
    77  	_, repoRoot, err := vcs.Nearest(dir)
    78  	if err != nil {
    79  		return Project{}, err
    80  	}
    81  
    82  	// Compute the project import path prefix.
    83  	importPrefix, err := ImportPath(repoRoot)
    84  	if err != nil {
    85  		return Project{}, err
    86  	}
    87  
    88  	// Cache the computed project.
    89  	project := Project{
    90  		Tool:       tool,
    91  		Manifest:   manifestDir,
    92  		Dir:        repoRoot,
    93  		ImportPath: importPrefix,
    94  	}
    95  	a.projectCache[pkg] = project
    96  	return project, nil
    97  }