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

     1  // Package scala implements Scala analysis.
     2  //
     3  // A `BuildTarget` for scala is project:configuration.
     4  package scala
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/apex/log"
    12  	"github.com/mitchellh/mapstructure"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/fossas/fossa-cli/buildtools/sbt"
    16  	"github.com/fossas/fossa-cli/exec"
    17  	"github.com/fossas/fossa-cli/files"
    18  	"github.com/fossas/fossa-cli/graph"
    19  	"github.com/fossas/fossa-cli/module"
    20  	"github.com/fossas/fossa-cli/pkg"
    21  )
    22  
    23  type Analyzer struct {
    24  	SBTCmd     string
    25  	SBTVersion string
    26  
    27  	JavaCmd     string
    28  	JavaVersion string
    29  
    30  	SBT     sbt.SBT
    31  	Module  module.Module
    32  	Options Options
    33  }
    34  
    35  type Options struct{}
    36  
    37  func New(m module.Module) (*Analyzer, error) {
    38  	// Set Java context variables
    39  	javaCmd, javaVersion, err := exec.Which("-version", os.Getenv("JAVA_BINARY"), "java")
    40  	if err != nil {
    41  		// Is JAVA_HOME set?
    42  		// Is $FOSSA_JAVA_BINARY set?
    43  		log.WithError(err).Warn("could not find Java binary")
    44  	}
    45  
    46  	// Set SBT context variables
    47  	sbtCmd, sbtVersion, err := exec.WhichArgs([]string{"-no-colors", "about"}, os.Getenv("SBT_BINARY"), "sbt")
    48  	if err != nil {
    49  		// Is FOSSA_SBT_BINARY set?
    50  		log.WithError(err).Warn("could not find SBT binary")
    51  	}
    52  
    53  	// Parse and validate options.
    54  	var options Options
    55  	err = mapstructure.Decode(m.Options, &options)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	log.WithField("options", options).Debug("decoded SBT analyzer options")
    60  
    61  	analyzer := Analyzer{
    62  		JavaCmd:     javaCmd,
    63  		JavaVersion: javaVersion,
    64  
    65  		SBTCmd:     sbtCmd,
    66  		SBTVersion: sbtVersion,
    67  
    68  		SBT: sbt.SBT{
    69  			Bin: sbtCmd,
    70  		},
    71  		Module:  m,
    72  		Options: options,
    73  	}
    74  
    75  	log.WithField("analyzer", analyzer).Debug("constructed SBT analyzer")
    76  	return &analyzer, nil
    77  }
    78  
    79  func Discover(dir string, options map[string]interface{}) ([]module.Module, error) {
    80  	log.WithField("dir", dir).Debug("discovering modules")
    81  
    82  	// Construct SBT instance (for listing projects).
    83  	sbtCmd, _, err := exec.WhichArgs([]string{"-no-colors", "about"}, os.Getenv("SBT_BINARY"), "sbt")
    84  	if err != nil {
    85  		return nil, nil
    86  	}
    87  	sbt := sbt.SBT{
    88  		Bin: sbtCmd,
    89  	}
    90  
    91  	var modules []module.Module
    92  	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    93  		log.WithField("path", path).Debug("discovering modules")
    94  
    95  		if err != nil {
    96  			log.WithError(err).WithField("path", path).Debug("error while walking for discovery")
    97  			return err
    98  		}
    99  
   100  		if info.IsDir() {
   101  			log.Debug("path is folder")
   102  			ok, err := files.Exists(path, "build.sbt")
   103  			if err != nil {
   104  				return err
   105  			}
   106  			if !ok {
   107  				return nil
   108  			}
   109  			log.Debug("Path has build.sbt")
   110  
   111  			dir := filepath.Dir(path)
   112  			projects, err := sbt.Projects(dir)
   113  			if err != nil {
   114  				log.WithError(err).Debug("could not get modules at path")
   115  				return err
   116  			}
   117  
   118  			for _, p := range projects {
   119  				modules = append(modules, module.Module{
   120  					Name:        p,
   121  					Type:        pkg.Scala,
   122  					BuildTarget: p + ":compile",
   123  					Dir:         dir,
   124  				})
   125  			}
   126  			log.WithField("path", path).Debug("skipping")
   127  			// Don't continue recursing, because anything else is probably a
   128  			// subproject.
   129  			return filepath.SkipDir
   130  		}
   131  
   132  		return nil
   133  	})
   134  
   135  	if err != nil {
   136  		return nil, errors.Wrap(err, "could not find Scala projects")
   137  	}
   138  
   139  	return modules, nil
   140  }
   141  
   142  func (a *Analyzer) Clean() error {
   143  	project, configuration := ParseTarget(a.Module.BuildTarget)
   144  	return a.SBT.Clean(a.Module.Dir, project, configuration)
   145  }
   146  
   147  func (a *Analyzer) Build() error {
   148  	project, configuration := ParseTarget(a.Module.BuildTarget)
   149  	return a.SBT.Compile(a.Module.Dir, project, configuration)
   150  }
   151  
   152  // IsBuilt checks whether `mvn dependency:list` produces an error.
   153  func (a *Analyzer) IsBuilt() (bool, error) {
   154  	project, configuration := ParseTarget(a.Module.BuildTarget)
   155  	output, err := a.SBT.DependencyList(a.Module.Dir, project, configuration)
   156  	if err != nil {
   157  		if strings.Contains(output, "Could not find artifact") {
   158  			return false, nil
   159  		}
   160  		return false, err
   161  	}
   162  	return output != "", nil
   163  }
   164  
   165  func (a *Analyzer) Analyze() (graph.Deps, error) {
   166  	log.WithField("module", a.Module).Debug("analyzing module")
   167  
   168  	project, configuration := ParseTarget(a.Module.BuildTarget)
   169  	imports, deps, err := a.SBT.DependencyTree(a.Module.Dir, project, configuration)
   170  	if err != nil {
   171  		return graph.Deps{}, err
   172  	}
   173  
   174  	return graph.Deps{
   175  		Direct:     imports,
   176  		Transitive: deps,
   177  	}, nil
   178  }
   179  
   180  func ParseTarget(target string) (project string, configuration string) {
   181  	splits := strings.Split(target, ":")
   182  	project = splits[0]
   183  	configuration = splits[1]
   184  	return project, configuration
   185  }