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

     1  // Package maven implements Maven analysis.
     2  //
     3  // A `BuildTarget` for Maven is either a Maven project ID (groupId:artifactId), or a path to a directory in
     4  // which there is a "pom.xml" file, or a path to a POM file.
     5  package maven
     6  
     7  import (
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/apex/log"
    13  	"github.com/mitchellh/mapstructure"
    14  	"github.com/pkg/errors"
    15  
    16  	"github.com/fossas/fossa-cli/buildtools/maven"
    17  	"github.com/fossas/fossa-cli/exec"
    18  	"github.com/fossas/fossa-cli/files"
    19  	"github.com/fossas/fossa-cli/graph"
    20  	"github.com/fossas/fossa-cli/module"
    21  	"github.com/fossas/fossa-cli/pkg"
    22  )
    23  
    24  type Analyzer struct {
    25  	Maven   maven.Maven
    26  	Module  module.Module
    27  	Options Options
    28  }
    29  
    30  type Options struct {
    31  	Binary  string `mapstructure:"bin"`
    32  	Command string `mapstructure:"cmd"`
    33  	// Strategy can be "pom-file", "maven-tree", or empty.
    34  	Strategy string `mapstructure:"strategy"`
    35  }
    36  
    37  func New(m module.Module) (*Analyzer, error) {
    38  	log.Debugf("%#v", m.Options)
    39  
    40  	// Decode options.
    41  	var options Options
    42  	err := mapstructure.Decode(m.Options, &options)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	// Get Maven binary.
    48  	mvnBin, _, err := exec.Which("--version", options.Binary, os.Getenv("MAVEN_BINARY"), "mvn")
    49  	if err != nil {
    50  		log.Warnf("Could not find Maven binary: %s", err.Error())
    51  	}
    52  
    53  	analyzer := Analyzer{
    54  		Maven: maven.Maven{
    55  			Cmd: mvnBin,
    56  		},
    57  		Module:  m,
    58  		Options: options,
    59  	}
    60  
    61  	log.WithField("analyzer", analyzer).Debug("constructed analyzer")
    62  	return &analyzer, nil
    63  }
    64  
    65  func Discover(dir string, options map[string]interface{}) ([]module.Module, error) {
    66  	log.WithField("dir", dir).Debug("discovering modules")
    67  	var modules []module.Module
    68  	checked := make(map[string]bool)
    69  
    70  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    71  		if err != nil {
    72  			log.WithError(err).WithField("path", path).Debug("error while walking for discovery")
    73  			return err
    74  		}
    75  
    76  		if info.IsDir() {
    77  			ok, err := files.Exists(path, "pom.xml")
    78  			if err != nil {
    79  				return err
    80  			}
    81  			if !ok {
    82  				return nil
    83  			}
    84  
    85  			submodules, err := maven.Modules(filepath.Join(path, "pom.xml"), path, checked)
    86  			if err != nil {
    87  				log.WithError(err).Debugf("could not get modules at path %s", path)
    88  				return err
    89  			}
    90  
    91  			for _, m := range submodules {
    92  				modules = append(modules, module.Module{
    93  					Name:        m.Name,
    94  					Type:        pkg.Maven,
    95  					BuildTarget: m.Target,
    96  					Dir:         m.Dir,
    97  				})
    98  			}
    99  			// Continue recursing because there may be modules that are not declared under the current module.
   100  		}
   101  
   102  		return nil
   103  	})
   104  
   105  	if err != nil {
   106  		return nil, errors.Wrap(err, "could not find Maven projects")
   107  	}
   108  
   109  	return modules, nil
   110  }
   111  
   112  func (a *Analyzer) Clean() error {
   113  	return a.Maven.Clean(a.Module.Dir)
   114  }
   115  
   116  func (a *Analyzer) Build() error {
   117  	return a.Maven.Compile(a.Module.Dir)
   118  }
   119  
   120  // IsBuilt checks whether `mvn dependency:list` returns without error.
   121  func (a *Analyzer) IsBuilt() (bool, error) {
   122  	output, err := a.Maven.DependencyList(a.Module.Dir, a.Module.BuildTarget)
   123  	if err != nil {
   124  		if strings.Contains(output, "Could not find artifact") {
   125  			return false, nil
   126  		}
   127  		return false, err
   128  	}
   129  	return output != "", nil
   130  }
   131  
   132  func (a *Analyzer) Analyze() (graph.Deps, error) {
   133  	log.WithField("module", a.Module).Debug("analyzing module")
   134  
   135  	switch a.Options.Strategy {
   136  	case "pom-file":
   137  		return maven.PomFileGraph(a.Module.BuildTarget, a.Module.Dir)
   138  	case "maven-tree":
   139  		return a.Maven.DependencyTree(a.Module.Dir, a.Module.BuildTarget)
   140  	default:
   141  		if a.Options.Command != "" {
   142  			output, _, err := exec.Shell(exec.Cmd{
   143  				Command: a.Options.Command,
   144  			})
   145  			if err != nil {
   146  				// Because this was a custom shell command, we do not fall back to any other strategies.
   147  				return graph.Deps{}, err
   148  			}
   149  			return maven.ParseDependencyTree(output)
   150  		}
   151  
   152  		deps, err := a.Maven.DependencyTree(a.Module.Dir, a.Module.BuildTarget)
   153  		if err != nil {
   154  			log.Warnf(
   155  				"Could not use Maven to determine dependencies for %q: %v. Falling back to use manifest file.",
   156  				a.Module.Name,
   157  				err,
   158  			)
   159  		} else if len(deps.Direct) == 0 {
   160  			log.Warnf("Maven did not find dependencies for %q. Falling back to use manifest file.",
   161  				a.Module.Name)
   162  		} else {
   163  			return deps, nil
   164  		}
   165  
   166  		return maven.PomFileGraph(a.Module.BuildTarget, a.Module.Dir)
   167  	}
   168  }