code-intelligence.com/cifuzz@v0.40.0/internal/build/maven/maven.go (about) 1 package maven 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 12 "github.com/pkg/errors" 13 14 "code-intelligence.com/cifuzz/internal/build" 15 "code-intelligence.com/cifuzz/internal/cmdutils" 16 "code-intelligence.com/cifuzz/pkg/log" 17 "code-intelligence.com/cifuzz/util/fileutil" 18 ) 19 20 type ParallelOptions struct { 21 Enabled bool 22 NumJobs uint 23 } 24 25 type BuilderOptions struct { 26 ProjectDir string 27 Parallel ParallelOptions 28 Stdout io.Writer 29 Stderr io.Writer 30 } 31 32 func (opts *BuilderOptions) Validate() error { 33 // Check that the project dir is set 34 if opts.ProjectDir == "" { 35 return errors.New("ProjectDir is not set") 36 } 37 // Check that the project dir exists and can be accessed 38 _, err := os.Stat(opts.ProjectDir) 39 if err != nil { 40 return errors.WithStack(err) 41 } 42 return nil 43 } 44 45 type Builder struct { 46 *BuilderOptions 47 } 48 49 func NewBuilder(opts *BuilderOptions) (*Builder, error) { 50 err := opts.Validate() 51 if err != nil { 52 return nil, err 53 } 54 55 b := &Builder{BuilderOptions: opts} 56 57 return b, err 58 } 59 60 func (b *Builder) Build(targetClass string, targetMethod string) (*build.Result, error) { 61 var flags []string 62 if b.Parallel.Enabled { 63 flags = append(flags, "-T") 64 if b.Parallel.NumJobs != 0 { 65 flags = append(flags, fmt.Sprint(b.Parallel.NumJobs)) 66 } else { 67 // Use one thread per cpu core 68 flags = append(flags, "1C") 69 } 70 } 71 args := append(flags, "test-compile") 72 73 err := b.runMaven(args, b.Stderr, b.Stderr) 74 if err != nil { 75 return nil, err 76 } 77 78 deps, err := b.getExternalDependencies() 79 if err != nil { 80 return nil, err 81 } 82 83 localDeps, err := b.getLocalDependencies() 84 if err != nil { 85 return nil, err 86 } 87 88 deps = append(deps, localDeps...) 89 90 buildDir, err := GetBuildDirectory(b.ProjectDir) 91 if err != nil { 92 return nil, err 93 } 94 result := &build.Result{ 95 Name: targetClass, 96 TargetMethod: targetMethod, 97 BuildDir: buildDir, 98 ProjectDir: b.ProjectDir, 99 RuntimeDeps: deps, 100 } 101 102 return result, nil 103 } 104 105 func (b *Builder) getExternalDependencies() ([]string, error) { 106 tempDir, err := os.MkdirTemp("", "cifuzz-maven-dependencies-*") 107 if err != nil { 108 return nil, errors.WithStack(err) 109 } 110 defer fileutil.Cleanup(tempDir) 111 112 outputPath := filepath.Join(tempDir, "cp") 113 outputFlag := "-Dmdep.outputFile=" + outputPath 114 115 args := []string{ 116 "dependency:build-classpath", 117 outputFlag, 118 } 119 120 err = b.runMaven(args, b.Stderr, b.Stderr) 121 if err != nil { 122 return nil, err 123 } 124 125 output, err := os.ReadFile(outputPath) 126 if err != nil { 127 return nil, errors.WithStack(err) 128 } 129 130 deps := strings.Split(strings.TrimSpace(string(output)), string(os.PathListSeparator)) 131 return deps, nil 132 } 133 134 func (b *Builder) getLocalDependencies() ([]string, error) { 135 args := []string{ 136 "help:evaluate", 137 "-Dexpression=project", 138 "-DforceStdout", 139 "--quiet", 140 } 141 stdout := new(bytes.Buffer) 142 err := b.runMaven(args, stdout, stdout) 143 if err != nil { 144 return nil, err 145 } 146 147 project, err := parseXML(stdout) 148 if err != nil { 149 return nil, err 150 } 151 // Append local dependencies which are not listed by "mvn dependency:build-classpath" 152 // These directories are configurable 153 localDeps := []string{ 154 project.Build.OutputDirectory, 155 project.Build.TestOutputDirectory, 156 } 157 158 return localDeps, nil 159 } 160 161 func (b *Builder) runMaven(args []string, stdout, stderr io.Writer) error { 162 // always run it with the cifuzz profile 163 args = append(args, "-Pcifuzz") 164 // remove color from output 165 args = append(args, "-B") 166 cmd := exec.Command( 167 "mvn", 168 args..., 169 ) 170 // Redirect the command's stdout to stderr to only have 171 // reports printed to stdout 172 cmd.Stdout = stdout 173 cmd.Stderr = stderr 174 cmd.Dir = b.ProjectDir 175 log.Debugf("Working directory: %s", cmd.Dir) 176 log.Debugf("Command: %s", cmd.String()) 177 err := cmd.Run() 178 if err != nil { 179 return cmdutils.WrapExecError(errors.WithStack(err), cmd) 180 } 181 182 return nil 183 } 184 185 func GetBuildDirectory(projectDir string) (string, error) { 186 cmd := exec.Command("mvn", 187 "help:evaluate", 188 "-Dexpression=project.build.directory", 189 "--quiet", 190 "-DforceStdout", 191 ) 192 cmd.Dir = projectDir 193 log.Debugf("Working directory: %s", cmd.Dir) 194 log.Debugf("Command: %s", cmd.String()) 195 output, err := cmd.Output() 196 if err != nil { 197 return "", cmdutils.WrapExecError(errors.WithStack(err), cmd) 198 } 199 200 return string(output), nil 201 }