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

     1  // Package php implements analyzers for PHP.
     2  //
     3  // A `BuildTarget` for PHP is the path to the `composer.json`.
     4  package php
     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/composer"
    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  	Composer composer.Composer
    25  
    26  	Module  module.Module
    27  	Options Options
    28  }
    29  
    30  // TODO: strategies:
    31  // - composer show
    32  // - read lockfile
    33  // - read manifest
    34  // - read components
    35  
    36  type Options struct {
    37  	Strategy string `mapstructure:"strategy"`
    38  }
    39  
    40  func New(m module.Module) (*Analyzer, error) {
    41  	log.WithField("options", m.Options).Debug("constructing analyzer")
    42  	// Set Bower context variables
    43  	composerCmd, _, err := exec.Which("--version", os.Getenv("COMPOSER_BINARY"), "composer")
    44  	if err != nil {
    45  		return nil, errors.Wrap(err, "could not find Composer binary (try setting $COMPOSER_BINARY)")
    46  	}
    47  
    48  	// Decode options
    49  	var options Options
    50  	err = mapstructure.Decode(m.Options, &options)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	analyzer := Analyzer{
    56  		Composer: composer.NewComposer(composerCmd),
    57  
    58  		Module:  m,
    59  		Options: options,
    60  	}
    61  
    62  	log.Debugf("analyzer: %#v", analyzer)
    63  	return &analyzer, nil
    64  }
    65  
    66  // Discover finds `composer.json`s not a /vendor/ folder
    67  func Discover(dir string, options map[string]interface{}) ([]module.Module, error) {
    68  	var modules []module.Module
    69  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    70  		if err != nil {
    71  			log.WithError(err).WithField("path", path).Debug("error while walking for discovery")
    72  			return err
    73  		}
    74  
    75  		// Skip the /vendor/ folder
    76  		if info.IsDir() && info.Name() == "vendor" {
    77  			log.Debugf("skipping `vendor` directory: %s", info.Name())
    78  			return filepath.SkipDir
    79  		}
    80  
    81  		if !info.IsDir() && info.Name() == "composer.json" {
    82  			dir := filepath.Dir(path)
    83  			name := filepath.Base(dir)
    84  
    85  			// Parse from composer.json and set name if successful
    86  			var composerPackage composer.Manifest
    87  			if err := files.ReadJSON(&composerPackage, path); err == nil {
    88  				name = composerPackage.Name
    89  			}
    90  
    91  			log.Debugf("found Composer package: %s (%s)", path, name)
    92  			modules = append(modules, module.Module{
    93  				Name:        name,
    94  				Type:        pkg.Composer,
    95  				BuildTarget: path,
    96  				Dir:         dir,
    97  			})
    98  		}
    99  		return nil
   100  	})
   101  
   102  	if err != nil {
   103  		return nil, fmt.Errorf("could not find Composer package manifests: %s", err.Error())
   104  	}
   105  
   106  	return modules, nil
   107  }
   108  
   109  func (a *Analyzer) Clean() error {
   110  	return files.Rm(a.Module.Dir, "vendor")
   111  }
   112  
   113  func (a *Analyzer) Build() error {
   114  	return composer.Install(a.Module.Dir, a.Composer)
   115  }
   116  
   117  func (a *Analyzer) IsBuilt() (bool, error) {
   118  	_, err := composer.Show(a.Module.Dir, a.Composer)
   119  	return err == nil, nil
   120  }
   121  
   122  func (a *Analyzer) Analyze() (graph.Deps, error) {
   123  	imports, deps, err := composer.Dependencies(a.Module.Dir, a.Composer)
   124  	if err != nil {
   125  		return graph.Deps{}, err
   126  	}
   127  
   128  	var pkgImports []pkg.Import
   129  	for _, i := range imports {
   130  		pkgImports = append(pkgImports, pkg.Import{
   131  			Resolved: pkg.ID{
   132  				Type:     pkg.Composer,
   133  				Name:     i.Name,
   134  				Revision: i.Version,
   135  			},
   136  		})
   137  	}
   138  
   139  	g := make(map[pkg.ID]pkg.Package)
   140  	for parent, children := range deps {
   141  		id := pkg.ID{
   142  			Type:     pkg.Composer,
   143  			Name:     parent.Name,
   144  			Revision: parent.Version,
   145  		}
   146  		var parentImports []pkg.Import
   147  		for _, child := range children {
   148  			parentImports = append(parentImports, pkg.Import{
   149  				Resolved: pkg.ID{
   150  					Type:     pkg.Composer,
   151  					Name:     child.Name,
   152  					Revision: child.Version,
   153  				},
   154  			})
   155  		}
   156  		g[id] = pkg.Package{
   157  			ID:      id,
   158  			Imports: parentImports,
   159  		}
   160  	}
   161  
   162  	return graph.Deps{
   163  		Direct:     pkgImports,
   164  		Transitive: g,
   165  	}, nil
   166  }