github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/maven/maven.go (about) 1 package maven 2 3 import ( 4 "os" 5 "path/filepath" 6 "regexp" 7 "strings" 8 9 "github.com/apex/log" 10 "github.com/pkg/errors" 11 12 "github.com/fossas/fossa-cli/exec" 13 "github.com/fossas/fossa-cli/files" 14 "github.com/fossas/fossa-cli/graph" 15 "github.com/fossas/fossa-cli/pkg" 16 ) 17 18 type Maven struct { 19 Cmd string 20 } 21 22 func (m *Maven) Clean(dir string) error { 23 _, _, err := exec.Run(exec.Cmd{ 24 Name: m.Cmd, 25 Argv: []string{"clean", "--batch-mode"}, 26 Dir: dir, 27 }) 28 return err 29 } 30 31 func (m *Maven) Compile(dir string) error { 32 _, _, err := exec.Run(exec.Cmd{ 33 Name: m.Cmd, 34 Argv: []string{"compile", "-DskipTests", "-Drat.skip=true", "--batch-mode"}, 35 Dir: dir, 36 }) 37 return err 38 } 39 40 // A MvnModule can identify a Maven project with the target path. 41 type MvnModule struct { 42 // Name is taken from the module's POM file. 43 Name string 44 45 // Target is the relative path from the root of the FOSSA project to the module's manifest file or to 46 // the directory containing the module's "pom.xml". The target may name a file other than "pom.xml" and 47 // it may even be the groupId:artifactId string identifying the "Maven project". 48 Target string 49 50 // Dir is the relative path from the root of the FOSSA project to either the module or to a parent module 51 // under which this one is listed. This is where `mvn` should be run to get the dependency tree. 52 Dir string 53 } 54 55 // Modules returns a list of all Maven modules at the directory of pomFilePath and below. 56 func Modules(pomFilePath string, reactorDir string, checked map[string]bool) ([]MvnModule, error) { 57 absPath, err := filepath.Abs(pomFilePath) 58 if err != nil { 59 return nil, errors.Wrapf(err, "could not get absolute path of %q", pomFilePath) 60 } 61 62 if checked[absPath] { 63 return nil, nil 64 } 65 66 checked[absPath] = true 67 68 var pom Manifest 69 if err := files.ReadXML(&pom, absPath); err != nil { 70 return nil, errors.Wrapf(err, "could not read POM file %q", absPath) 71 } 72 73 modules := make([]MvnModule, 1, 1+len(pom.Modules)) 74 75 name := pom.Name 76 if name == "" { 77 name = pom.ArtifactID 78 } 79 80 pomRelative, err := filepath.Rel(reactorDir, pomFilePath) 81 if err != nil { 82 return nil, errors.Wrapf(err, "could not determine relative path of %q from %q", pomFilePath, reactorDir) 83 } 84 85 modules[0] = MvnModule{Name: name, Target: pomRelative, Dir: reactorDir} 86 87 pomDir := filepath.Dir(pomFilePath) 88 89 for _, module := range pom.Modules { 90 childPath := filepath.Join(pomDir, module) 91 childStat, err := os.Stat(childPath) 92 if err != nil { 93 return nil, errors.Wrapf(err, "could not check type of %q", childPath) 94 } 95 if childStat.IsDir() { 96 // Assume the listed module uses the standard name for the manifest file. 97 childPath = filepath.Join(childPath, "pom.xml") 98 } 99 children, err := Modules(childPath, pomDir, checked) 100 if err != nil { 101 return nil, errors.Wrapf(err, "could not read child module at %q", childPath) 102 } 103 modules = append(modules, children...) 104 } 105 106 return modules, nil 107 } 108 109 // DependencyList runs Maven's dependency:list goal for the specified project. 110 func (m *Maven) DependencyList(dir, buildTarget string) (string, error) { 111 return m.tryDependencyCommands("list", dir, buildTarget) 112 } 113 114 // DependencyTree runs Maven's dependency:tree goal for the specified project. 115 func (m *Maven) DependencyTree(dir, buildTarget string) (graph.Deps, error) { 116 output, err := m.tryDependencyCommands("tree", dir, buildTarget) 117 if err != nil { 118 return graph.Deps{}, err 119 } 120 return ParseDependencyTree(output) 121 } 122 123 func (m *Maven) tryDependencyCommands(subGoal, dir, buildTarget string) (stdout string, err error) { 124 goalArgs := []string{"dependency:" + subGoal, "--batch-mode"} 125 cmd := exec.Cmd{Name: m.Cmd, Dir: dir} 126 127 // First, we try with the buildTarget because we have no idea what it is or whether the reactor already 128 // knows about the module. 129 cmd.Argv = append(goalArgs, "--projects", buildTarget) 130 output, _, err := exec.Run(cmd) 131 if err != nil { 132 // Now we try to identify the groupId:artifactId identifier for the module and specify the path to 133 // the manifest file directly. 134 pom, err2 := ResolveManifestFromTarget(buildTarget, dir) 135 if err2 != nil { 136 // Using buildTarget as a module ID or as a path to a manifest did not work. 137 // Return just the error from running the mvn goal the first time. 138 return "", errors.Wrap(err, "could not use Maven to list dependencies") 139 } 140 141 // At this point we still don't know if buildTarget is the path to a directory or to a manifest file, 142 // so create pomFilePath based on the file extension. 143 pomFilePath := buildTarget 144 if !strings.HasSuffix(pomFilePath, ".xml") { 145 pomFilePath = filepath.Join(pomFilePath, "pom.xml") 146 } 147 148 cmd.Argv = append(goalArgs, "--projects", pom.GroupID+":"+pom.ArtifactID, "--file", pomFilePath) 149 150 output, _, err2 = exec.Run(cmd) 151 err = errors.Wrapf(err2, "could not run %s (original error: %v)", goalArgs[0], err) 152 } 153 return output, err 154 } 155 156 //go:generate bash -c "genny -in=$GOPATH/src/github.com/fossas/fossa-cli/graph/readtree.go gen 'Generic=Dependency' | sed -e 's/package graph/package maven/' > readtree_generated.go" 157 158 func ParseDependencyTree(stdin string) (graph.Deps, error) { 159 var filteredLines []string 160 start := regexp.MustCompile("^\\[INFO\\] --- .*? ---$") 161 started := false 162 r := regexp.MustCompile("^\\[INFO\\] ([ `+\\\\|-]*)([^ `+\\\\|-].+)$") 163 splitReg := regexp.MustCompile("\r?\n") 164 for _, line := range splitReg.Split(stdin, -1) { 165 if line == "[INFO] " || line == "[INFO] ------------------------------------------------------------------------" { 166 started = false 167 } 168 if strings.HasPrefix(line, "[INFO] Downloading ") { 169 continue 170 } 171 if started { 172 filteredLines = append(filteredLines, line) 173 } 174 if start.MatchString(line) { 175 started = true 176 } 177 } 178 179 // Remove first line, which is just the direct dependency. 180 if len(filteredLines) == 0 { 181 return graph.Deps{}, errors.New("error parsing lines") 182 } 183 filteredLines = filteredLines[1:] 184 185 depRegex := regexp.MustCompile("([^:]+):([^:]+):([^:]*):([^:]+)") 186 imports, deps, err := ReadDependencyTree(filteredLines, func(line string) (int, Dependency, error) { 187 log.WithField("line", line).Debug("parsing output line") 188 matches := r.FindStringSubmatch(line) 189 depth := len(matches[1]) 190 if depth%3 != 0 { 191 // Sanity check 192 log.WithField("depth", depth).Fatal("bad depth") 193 } 194 level := depth / 3 195 depMatches := depRegex.FindStringSubmatch(matches[2]) 196 revision := depMatches[4] 197 failed := false 198 if strings.HasSuffix(revision, " FAILED") { 199 revision = strings.TrimSuffix(revision, " FAILED") 200 failed = true 201 } 202 203 dep := Dependency{GroupId: depMatches[1], ArtifactId: depMatches[2], Version: revision, Failed: failed} 204 return level, dep, nil 205 }) 206 if err != nil { 207 return graph.Deps{}, err 208 } 209 return graph.Deps{ 210 Direct: depsListToImports(imports), 211 Transitive: depsMapToPkgGraph(deps), 212 }, nil 213 } 214 215 func depsListToImports(d []Dependency) []pkg.Import { 216 i := make([]pkg.Import, 0, len(d)) 217 for _, dep := range d { 218 i = append(i, pkg.Import{ 219 Resolved: pkg.ID{ 220 Type: pkg.Maven, 221 Name: dep.ID(), 222 Revision: dep.Version, 223 }, 224 }) 225 } 226 return i 227 } 228 229 func depsMapToPkgGraph(deps map[Dependency][]Dependency) map[pkg.ID]pkg.Package { 230 pkgGraph := make(map[pkg.ID]pkg.Package) 231 for parent, children := range deps { 232 pack := pkg.Package{ 233 ID: pkg.ID{ 234 Type: pkg.Maven, 235 Name: parent.ID(), 236 Revision: parent.Version, 237 }, 238 Imports: make([]pkg.Import, 0, len(children)), 239 } 240 for _, child := range children { 241 pack.Imports = append(pack.Imports, pkg.Import{ 242 Target: child.Version, 243 Resolved: pkg.ID{ 244 Type: pkg.Maven, 245 Name: child.ID(), 246 Revision: child.Version, 247 }, 248 }) 249 } 250 pkgGraph[pack.ID] = pack 251 } 252 return pkgGraph 253 }