github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/analyzers/golang/analyze.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/buildtools/dep"
     8  	"github.com/fossas/fossa-cli/buildtools/gdm"
     9  	"github.com/fossas/fossa-cli/buildtools/glide"
    10  	"github.com/fossas/fossa-cli/buildtools/gocmd"
    11  	"github.com/fossas/fossa-cli/buildtools/godep"
    12  	"github.com/fossas/fossa-cli/buildtools/gomodules"
    13  	"github.com/fossas/fossa-cli/buildtools/govendor"
    14  	"github.com/fossas/fossa-cli/buildtools/vndr"
    15  	"github.com/fossas/fossa-cli/errors"
    16  	"github.com/fossas/fossa-cli/graph"
    17  	"github.com/fossas/fossa-cli/pkg"
    18  )
    19  
    20  // Analyze builds a dependency graph using go list and then looks up revisions
    21  // using tool-specific lockfiles.
    22  func (a *Analyzer) Analyze() (graph.Deps, error) {
    23  	m := a.Module
    24  	log.Debugf("%#v", m)
    25  
    26  	// Get Go project.
    27  	project, err := a.Project(m.BuildTarget)
    28  	if err != nil {
    29  		return graph.Deps{}, err
    30  	}
    31  	log.Debugf("Go project: %#v", project)
    32  
    33  	// Read lockfiles to get revisions.
    34  	var r resolver.Resolver
    35  	switch a.Options.Strategy {
    36  	case "manifest:gomodules":
    37  		if a.Options.LockfilePath == "" {
    38  			return graph.Deps{}, errors.New("manifest strategy specified without lockfile path")
    39  		}
    40  		r, err = gomodules.New(a.Options.LockfilePath)
    41  		if err != nil {
    42  			return graph.Deps{}, err
    43  		}
    44  	case "manifest:dep":
    45  		if a.Options.LockfilePath == "" {
    46  			return graph.Deps{}, errors.New("manifest strategy specified without lockfile path")
    47  		}
    48  		r, err = dep.New(a.Options.LockfilePath, a.Options.ManifestPath)
    49  		if err != nil {
    50  			return graph.Deps{}, err
    51  		}
    52  	case "manifest:gdm":
    53  		if a.Options.LockfilePath == "" {
    54  			return graph.Deps{}, errors.New("manifest strategy specified without lockfile path")
    55  		}
    56  		r, err = gdm.FromFile(a.Options.LockfilePath)
    57  		if err != nil {
    58  			return graph.Deps{}, err
    59  		}
    60  	case "manifest:glide":
    61  		if a.Options.LockfilePath == "" {
    62  			return graph.Deps{}, errors.New("manifest strategy specified without lockfile path")
    63  		}
    64  		r, err = glide.FromFile(a.Options.LockfilePath)
    65  		if err != nil {
    66  			return graph.Deps{}, err
    67  		}
    68  	case "manifest:godep":
    69  		if a.Options.LockfilePath == "" {
    70  			return graph.Deps{}, errors.New("manifest strategy specified without lockfile path")
    71  		}
    72  		r, err = godep.FromFile(a.Options.LockfilePath)
    73  		if err != nil {
    74  			return graph.Deps{}, err
    75  		}
    76  	case "manifest:govendor":
    77  		if a.Options.LockfilePath == "" {
    78  			return graph.Deps{}, errors.New("manifest strategy specified without lockfile path")
    79  		}
    80  		r, err = govendor.FromFile(a.Options.LockfilePath)
    81  		if err != nil {
    82  			return graph.Deps{}, err
    83  		}
    84  	case "manifest:vndr":
    85  		if a.Options.LockfilePath == "" {
    86  			return graph.Deps{}, errors.New("manifest strategy specified without lockfile path")
    87  		}
    88  		r, err = vndr.FromFile(a.Options.LockfilePath)
    89  		if err != nil {
    90  			return graph.Deps{}, err
    91  		}
    92  
    93  	// Resolve revisions by traversing the local $GOPATH and calling the package's
    94  	// VCS.
    95  	case "gopath-vcs":
    96  		return graph.Deps{}, errors.ErrNotImplemented
    97  
    98  	// Read revisions from an auto-detected tool manifest.
    99  	default:
   100  		r, err = a.ResolverFromLockfile(project.Tool, project.Manifest)
   101  		if err != nil {
   102  			return graph.Deps{}, err
   103  		}
   104  	}
   105  
   106  	log.Debugf("Resolver: %#v", r)
   107  
   108  	var allImports []pkg.Import
   109  	importMap := make(map[pkg.Import]bool)
   110  	transitiveDeps := make(map[pkg.ID]pkg.Package)
   111  
   112  	for _, buildTag := range a.BuildTags {
   113  		// Use `go list` to get imports and deps of module.
   114  		flags := []string{"-tags", buildTag}
   115  		main, err := a.Go.ListOne(m.BuildTarget, flags)
   116  		if err != nil {
   117  			return graph.Deps{}, err
   118  		}
   119  
   120  		if len(main.Deps) == 0 {
   121  			log.Warnf("No imports found for buid target %+v", m.BuildTarget)
   122  			return graph.Deps{}, nil
   123  		}
   124  
   125  		log.Debugf("Go main package: %#v", main)
   126  		deps, err := a.Go.List(main.Deps, flags)
   127  		if err != nil {
   128  			return graph.Deps{}, err
   129  		}
   130  
   131  		// Construct map of import path to package.
   132  		gopkgs := append(deps, main)
   133  		gopkgMap := make(map[string]gocmd.Package)
   134  		for _, p := range gopkgs {
   135  			gopkgMap[p.ImportPath] = p
   136  		}
   137  		// cgo imports don't have revisions.
   138  		gopkgMap["C"] = gocmd.Package{
   139  			Name:     "C",
   140  			IsStdLib: true, // This is so we don't try to lookup a revision. Maybe there should be a NoRevision bool field?
   141  		}
   142  		log.Debugf("gopkgMap: %#v", gopkgMap)
   143  
   144  		// Construct transitive dependency graph.
   145  		for _, gopkg := range deps {
   146  			log.Debugf("Getting revision for: %#v", gopkg)
   147  
   148  			// Resolve dependency.
   149  			revision, err := a.Revision(project, r, gopkg)
   150  			if err != nil {
   151  				return graph.Deps{}, err
   152  			}
   153  			id := revision.Resolved
   154  
   155  			// Check if the revision has already been scanned.
   156  			if _, ok := transitiveDeps[id]; ok {
   157  				continue
   158  			}
   159  
   160  			// Resolve dependency imports.
   161  			var imports []pkg.Import
   162  			for _, i := range gopkg.Imports {
   163  				_, ok := gopkgMap[i]
   164  				if !ok {
   165  					log.Fatalf("Could not find Go package for %#v, your build may have errors. Try `go list -json <MODULE>`.", i)
   166  				}
   167  				log.Debugf("Resolving import of: %#v", gopkg)
   168  				log.Debugf("Resolving dependency import: %#v", i)
   169  				revision, err := a.Revision(project, r, gopkgMap[i])
   170  				if err != nil {
   171  					return graph.Deps{}, errors.Wrapf(err, "could not resolve %s", i)
   172  				}
   173  				imports = append(imports, revision)
   174  			}
   175  
   176  			transitiveDeps[id] = pkg.Package{
   177  				ID:      id,
   178  				Imports: imports,
   179  			}
   180  		}
   181  
   182  		// Construct direct imports list.
   183  		for _, i := range main.Imports {
   184  			revision, err := a.Revision(project, r, gopkgMap[i])
   185  			if err != nil {
   186  				return graph.Deps{}, err
   187  			}
   188  
   189  			// Check if revision was added by a previous build tag.
   190  			if _, exists := importMap[revision]; !exists {
   191  				allImports = append(allImports, revision)
   192  				importMap[revision] = true
   193  			}
   194  		}
   195  	}
   196  
   197  	m.Deps = transitiveDeps
   198  	m.Imports = allImports
   199  
   200  	return graph.Deps{
   201  		Direct:     allImports,
   202  		Transitive: transitiveDeps,
   203  	}, nil
   204  }