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

     1  // Package bower implements analyzers for the Bower package manager.
     2  //
     3  // A `BuildTarget` for bower is the path to the `bower.json` of a project.
     4  package bower
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/apex/log"
    12  	"github.com/mitchellh/mapstructure"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/fossas/fossa-cli/buildtools/bower"
    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  	BowerCmd     string
    25  	BowerVersion string
    26  
    27  	Bower bower.Bower
    28  
    29  	Module  module.Module
    30  	Options Options
    31  }
    32  
    33  // TODO: strategies:
    34  // - bower list
    35  // - read components
    36  // - read manifest
    37  
    38  type Options struct {
    39  	Strategy      string `mapstructure:"strategy"`
    40  	ComponentsDir string `mapstructure:"components"`
    41  }
    42  
    43  func New(m module.Module) (*Analyzer, error) {
    44  	log.WithField("module", m).Debug("constructing Bower analyzer")
    45  	// Set Bower context variables
    46  	bowerCmd, bowerVersion, err := exec.Which("-v", os.Getenv("BOWER_BINARY"), "bower")
    47  	if err != nil {
    48  		return nil, errors.Wrap(err, "could not find Bower binary (try setting $BOWER_BINARY)")
    49  	}
    50  
    51  	// Decode options
    52  	var options Options
    53  	err = mapstructure.Decode(m.Options, &options)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	b, err := bower.New(bowerCmd, m.Dir)
    59  	if err != nil {
    60  		return nil, errors.Wrap(err, "could not set up Bower")
    61  	}
    62  
    63  	analyzer := Analyzer{
    64  		BowerCmd:     bowerCmd,
    65  		BowerVersion: bowerVersion,
    66  		Bower:        *b,
    67  
    68  		Module:  m,
    69  		Options: options,
    70  	}
    71  
    72  	log.WithField("analyzer", analyzer).Debug("constructed Bower analyzer")
    73  	return &analyzer, nil
    74  }
    75  
    76  // Discover finds any `bower.json`s not in `node_modules` or `bower_components`
    77  // folders.
    78  func Discover(dir string, options map[string]interface{}) ([]module.Module, error) {
    79  	var moduleConfigs []module.Module
    80  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    81  		if err != nil {
    82  			log.WithError(err).WithField("path", path).Debug("error while walking")
    83  			return err
    84  		}
    85  		// Skip **/node_modules and **/bower_components directories
    86  		if info.IsDir() && (info.Name() == "node_modules" || info.Name() == "bower_components") {
    87  			log.WithField("dir", info.Name()).Debug("skipping directory")
    88  			return filepath.SkipDir
    89  		}
    90  
    91  		if !info.IsDir() && info.Name() == "bower.json" {
    92  			dir := filepath.Dir(path)
    93  			name := filepath.Base(dir)
    94  
    95  			// Parse from bower.json and set name if successful
    96  			manifest, err := bower.ReadManifest(path)
    97  			if err == nil {
    98  				name = manifest.Name
    99  			}
   100  
   101  			log.WithFields(log.Fields{
   102  				"path": path,
   103  				"name": name,
   104  			}).Debug("discovered Bower module")
   105  			moduleConfigs = append(moduleConfigs, module.Module{
   106  				Name:        name,
   107  				Type:        pkg.Bower,
   108  				BuildTarget: path,
   109  				Dir:         dir,
   110  			})
   111  		}
   112  		return nil
   113  	})
   114  
   115  	if err != nil {
   116  		return nil, fmt.Errorf("could not find bower package manifests: %s", err.Error())
   117  	}
   118  
   119  	return moduleConfigs, nil
   120  }
   121  
   122  func (a *Analyzer) Clean() error {
   123  	return a.Bower.Clean()
   124  }
   125  
   126  // Build runs `bower install --production`
   127  func (a *Analyzer) Build() error {
   128  	return a.Bower.Install(true)
   129  }
   130  
   131  // IsBuilt checks for the existence of a components folder.
   132  func (a *Analyzer) IsBuilt() (bool, error) {
   133  	config, err := bower.ReadConfig(a.Module.Dir)
   134  	if err != nil {
   135  		return false, err
   136  	}
   137  	// TODO: Check if the installed modules are consistent with what's in the
   138  	// actual manifest.
   139  	bowerComponentsDir := config.Directory
   140  	if !filepath.IsAbs(bowerComponentsDir) {
   141  		bowerComponentsDir = filepath.Join(a.Module.Dir, config.Directory)
   142  	}
   143  	isBuilt, err := files.ExistsFolder(bowerComponentsDir)
   144  	if err != nil {
   145  		return false, err
   146  	}
   147  
   148  	log.WithField("isBuilt", isBuilt).Debug("done checking Bower build")
   149  	return isBuilt, nil
   150  }
   151  
   152  func (a *Analyzer) Analyze() (graph.Deps, error) {
   153  	p, err := a.Bower.List()
   154  	if err != nil {
   155  		return graph.Deps{}, err
   156  	}
   157  
   158  	var imports []pkg.Import
   159  	for _, dep := range p.Dependencies {
   160  		imports = append(imports, pkg.Import{
   161  			Target: dep.PkgMeta.TargetName + "@" + dep.PkgMeta.TargetVersion,
   162  			Resolved: pkg.ID{
   163  				Type:     pkg.Bower,
   164  				Name:     dep.PkgMeta.Name,
   165  				Revision: dep.PkgMeta.Version,
   166  				Location: dep.Endpoint.Source,
   167  			},
   168  		})
   169  	}
   170  
   171  	deps := make(map[pkg.ID]pkg.Package)
   172  	recurseDeps(deps, p)
   173  
   174  	return graph.Deps{
   175  		Direct:     imports,
   176  		Transitive: deps,
   177  	}, nil
   178  }
   179  
   180  func recurseDeps(pkgMap map[pkg.ID]pkg.Package, p bower.Package) {
   181  	for name, dep := range p.Dependencies {
   182  		// Construct ID.
   183  		id := pkg.ID{
   184  			Type:     pkg.Bower,
   185  			Name:     name,
   186  			Revision: dep.PkgMeta.Version,
   187  			Location: dep.Endpoint.Source,
   188  		}
   189  		// Don't process duplicates.
   190  		_, ok := pkgMap[id]
   191  		if ok {
   192  			continue
   193  		}
   194  		// Get direct imports.
   195  		var imports []pkg.Import
   196  		for name, i := range dep.Dependencies {
   197  			imports = append(imports, pkg.Import{
   198  				Target: i.PkgMeta.TargetName + "@" + i.PkgMeta.TargetVersion,
   199  				Resolved: pkg.ID{
   200  					Type:     pkg.Bower,
   201  					Name:     name,
   202  					Revision: i.PkgMeta.Version,
   203  					Location: i.Endpoint.Source,
   204  				},
   205  			})
   206  		}
   207  		// Update map.
   208  		pkgMap[id] = pkg.Package{
   209  			ID:      id,
   210  			Imports: imports,
   211  		}
   212  		// Recurse in imports.
   213  		recurseDeps(pkgMap, dep)
   214  	}
   215  }