code-intelligence.com/cifuzz@v0.40.0/internal/build/gradle/gradle.go (about)

     1  package gradle
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"os/exec"
     7  	"regexp"
     8  	"runtime"
     9  	"strings"
    10  
    11  	"github.com/pkg/errors"
    12  
    13  	"code-intelligence.com/cifuzz/internal/build"
    14  	"code-intelligence.com/cifuzz/internal/cmdutils"
    15  	"code-intelligence.com/cifuzz/internal/config"
    16  	"code-intelligence.com/cifuzz/pkg/log"
    17  	"code-intelligence.com/cifuzz/pkg/messaging"
    18  	"code-intelligence.com/cifuzz/pkg/runfiles"
    19  	"code-intelligence.com/cifuzz/util/fileutil"
    20  )
    21  
    22  var (
    23  	classpathRegex = regexp.MustCompile("(?m)^cifuzz.test.classpath=(?P<classpath>.*)$")
    24  	buildDirRegex  = regexp.MustCompile("(?m)^cifuzz.buildDir=(?P<buildDir>.*)$")
    25  )
    26  
    27  func FindGradleWrapper(projectDir string) (string, error) {
    28  	wrapper := "gradlew"
    29  	if runtime.GOOS == "windows" {
    30  		wrapper = "gradlew.bat"
    31  	}
    32  
    33  	return fileutil.SearchFileBackwards(projectDir, wrapper)
    34  }
    35  
    36  type ParallelOptions struct {
    37  	Enabled bool
    38  	NumJobs uint
    39  }
    40  
    41  type BuilderOptions struct {
    42  	ProjectDir string
    43  	Parallel   ParallelOptions
    44  	Stdout     io.Writer
    45  	Stderr     io.Writer
    46  }
    47  
    48  func (opts *BuilderOptions) Validate() error {
    49  	// Check that the project dir is set
    50  	if opts.ProjectDir == "" {
    51  		return errors.New("ProjectDir is not set")
    52  	}
    53  	// Check that the project dir exists and can be accessed
    54  	_, err := os.Stat(opts.ProjectDir)
    55  	if err != nil {
    56  		return errors.WithStack(err)
    57  	}
    58  	return nil
    59  }
    60  
    61  type Builder struct {
    62  	*BuilderOptions
    63  }
    64  
    65  func NewBuilder(opts *BuilderOptions) (*Builder, error) {
    66  	err := opts.Validate()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	b := &Builder{BuilderOptions: opts}
    72  
    73  	return b, err
    74  }
    75  
    76  func (b *Builder) Build(targetClass string, targetMethod string) (*build.Result, error) {
    77  	gradleBuildLanguage, err := config.DetermineGradleBuildLanguage(b.ProjectDir)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	version, err := b.GradlePluginVersion()
    83  	if err != nil {
    84  		log.Error(errors.New("No cifuzz gradle plugin found"))
    85  		log.Print(messaging.Instructions(string(gradleBuildLanguage)))
    86  		return nil, cmdutils.WrapSilentError(err)
    87  	}
    88  	log.Debugf("Found gradle plugin version: %s", version)
    89  
    90  	deps, err := b.getDependencies()
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	buildDir, err := GetBuildDirectory(b.ProjectDir)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	result := &build.Result{
   100  		Name:         targetClass,
   101  		TargetMethod: targetMethod,
   102  		BuildDir:     buildDir,
   103  		ProjectDir:   b.ProjectDir,
   104  		RuntimeDeps:  deps,
   105  	}
   106  
   107  	return result, nil
   108  }
   109  
   110  func (b *Builder) GradlePluginVersion() (string, error) {
   111  	cmd, err := buildGradleCommand(b.ProjectDir, []string{"cifuzzPrintPluginVersion", "-q"})
   112  	if err != nil {
   113  		return "", errors.WithStack(err)
   114  	}
   115  	log.Debugf("Command: %s", cmd.String())
   116  	output, err := cmd.Output()
   117  	if err != nil {
   118  		return "", errors.WithStack(err)
   119  	}
   120  
   121  	return strings.TrimPrefix(string(output), "cifuzz.plugin.version="), nil
   122  }
   123  
   124  func (b *Builder) getDependencies() ([]string, error) {
   125  	cmd, err := buildGradleCommand(b.ProjectDir, []string{"cifuzzPrintTestClasspath", "-q"})
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	log.Debugf("Command: %s", cmd.String())
   130  	output, err := cmd.Output()
   131  	if err != nil {
   132  		return nil, cmdutils.WrapExecError(errors.WithStack(err), cmd)
   133  	}
   134  	classpath := classpathRegex.FindStringSubmatch(string(output))
   135  	deps := strings.Split(strings.TrimSpace(classpath[1]), string(os.PathListSeparator))
   136  
   137  	return deps, nil
   138  }
   139  
   140  // GetGradleCommand returns the name of the gradle command.
   141  // The gradle wrapper is preferred to use and gradle
   142  // acts as a fallback command.
   143  func GetGradleCommand(projectDir string) (string, error) {
   144  	wrapper, err := FindGradleWrapper(projectDir)
   145  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   146  		return "", err
   147  	}
   148  	if wrapper != "" {
   149  		return wrapper, nil
   150  	}
   151  
   152  	gradleCmd, err := runfiles.Finder.GradlePath()
   153  	if err != nil {
   154  		return "", errors.WithStack(err)
   155  	}
   156  	return gradleCmd, nil
   157  }
   158  
   159  func buildGradleCommand(projectDir string, args []string) (*exec.Cmd, error) {
   160  	gradleCmd, err := GetGradleCommand(projectDir)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	cmd := exec.Command(gradleCmd, args...)
   166  	cmd.Dir = projectDir
   167  
   168  	return cmd, nil
   169  }
   170  
   171  func GetBuildDirectory(projectDir string) (string, error) {
   172  	cmd, err := buildGradleCommand(projectDir, []string{"cifuzzPrintBuildDir", "-q"})
   173  	if err != nil {
   174  		return "", nil
   175  	}
   176  
   177  	log.Debugf("Command: %s", cmd.String())
   178  	output, err := cmd.Output()
   179  	if err != nil {
   180  		return "", cmdutils.WrapExecError(errors.WithStack(err), cmd)
   181  	}
   182  	result := buildDirRegex.FindStringSubmatch(string(output))
   183  	if result == nil {
   184  		return "", errors.New("Unable to parse gradle build directory from init script.")
   185  	}
   186  	buildDir := strings.TrimSpace(result[1])
   187  
   188  	return buildDir, nil
   189  }
   190  
   191  func GetTestSourceSets(projectDir string) ([]string, error) {
   192  	cmd, err := buildGradleCommand(projectDir, []string{"cifuzzPrintTestSourceFolders", "-q"})
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	log.Debugf("Command: %s", cmd.String())
   198  	output, err := cmd.Output()
   199  	if err != nil {
   200  		return nil, cmdutils.WrapExecError(errors.WithStack(err), cmd)
   201  	}
   202  	paths := strings.Split(
   203  		strings.TrimSpace(
   204  			strings.ReplaceAll(string(output), "cifuzz.test.source-folders=", ""),
   205  		),
   206  		string(os.PathListSeparator))
   207  
   208  	log.Debugf("found gradle test sources at: %s", paths)
   209  	return paths, nil
   210  }