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 }