Skip to content

Commit

Permalink
Use Gradle extractor 5 in Gradle >= 6.8.1
Browse files Browse the repository at this point in the history
  • Loading branch information
yahavi committed Aug 21, 2023
1 parent 52ff32c commit 8772d83
Show file tree
Hide file tree
Showing 29 changed files with 1,345 additions and 89 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ build-info-go.exe
bi
bi.exe

# Dependency directories (remove the comment below to include it)
# vendor/
# Gradle
.gradle
180 changes: 104 additions & 76 deletions build/gradle.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
package build

import (
"bytes"
_ "embed"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

"github.com/jfrog/build-info-go/utils"
"github.com/jfrog/gofrog/version"
)

const (
extractorPropsDir = "BUILDINFO_PROPFILE"
GradleExtractorFileName = "build-info-extractor-gradle-%s-uber.jar"
gradleInitScriptTemplate = "gradle.init"
GradleExtractorRemotePath = "org/jfrog/buildinfo/build-info-extractor-gradle/%s"
GradleExtractorDependencyVersion = "4.32.0"
extractorPropsDir = "BUILDINFO_PROPFILE"
gradleExtractorFileName = "build-info-extractor-gradle-%s-uber.jar"
gradleInitScriptTemplate = "gradle.init"
gradleExtractorRemotePath = "org/jfrog/buildinfo/build-info-extractor-gradle/%s"
gradleExtractor4DependencyVersion = "4.32.0"
gradleExtractor5DependencyVersion = "5.1.0"
)

var versionRegex = regexp.MustCompile(`Gradle (\d+\.\d+(?:\.\d+|-\w+-\d+)?)`)

//go:embed init-gradle-extractor-4.gradle
var gradleInitScriptExtractor4 string

//go:embed init-gradle-extractor-5.gradle
var gradleInitScriptExtractor5 string

type GradleModule struct {
// The build which contains the gradle module.
containingBuild *Build
Expand Down Expand Up @@ -47,21 +62,15 @@ type gradleExtractorDetails struct {

// Add a new Gradle module to a given build.
func newGradleModule(containingBuild *Build, srcPath string) (*GradleModule, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
extractorLocalPath := filepath.Join(home, dependenciesDirName, "gradle", GradleExtractorDependencyVersion)
return &GradleModule{
srcPath: srcPath,
containingBuild: containingBuild,
gradleExtractorDetails: &gradleExtractorDetails{
localPath: extractorLocalPath,
tasks: []string{"artifactoryPublish"},
propsDir: filepath.Join(containingBuild.tempDirPath, PropertiesTempFolderName),
props: map[string]string{},
tasks: []string{"artifactoryPublish"},
propsDir: filepath.Join(containingBuild.tempDirPath, PropertiesTempFolderName),
props: map[string]string{},
},
}, err
}, nil
}

func (gm *GradleModule) SetExtractorDetails(localExtractorPath, extractorPropsDir string, tasks []string, useWrapper, usePlugin bool, downloadExtractorFunc func(downloadTo, downloadFrom string) error, props map[string]string) *GradleModule {
Expand All @@ -78,35 +87,94 @@ func (gm *GradleModule) SetExtractorDetails(localExtractorPath, extractorPropsDi
// Generates Gradle build-info.
func (gm *GradleModule) CalcDependencies() (err error) {
gm.containingBuild.logger.Info("Running gradle...")
if gm.srcPath == "" {
if gm.srcPath, err = os.Getwd(); err != nil {
wd, err := os.Getwd()
if err != nil {
return err
}
if gm.srcPath != "" {
if err = os.Chdir(gm.srcPath); err != nil {
return
}
defer func() {
err = errors.Join(err, os.Chdir(wd))
}()
}

if err = downloadGradleDependencies(gm.gradleExtractorDetails.localPath, gm.gradleExtractorDetails.downloadExtractorFunc, gm.containingBuild.logger); err != nil {
return err
gradleExecPath, err := GetGradleExecPath(gm.gradleExtractorDetails.useWrapper)
if err != nil {
return
}
if !gm.gradleExtractorDetails.usePlugin {
gradlePluginFilename := fmt.Sprintf(GradleExtractorFileName, GradleExtractorDependencyVersion)

gm.gradleExtractorDetails.initScript, err = getInitScript(gm.gradleExtractorDetails.localPath, gradlePluginFilename)
if err != nil {
if err := gm.downloadGradleExtractor(gradleExecPath); err != nil {
return err
}
}
gradleRunConfig, err := gm.createGradleRunConfig()
gradleRunConfig, err := gm.createGradleRunConfig(gradleExecPath)
if err != nil {
return err
}
return gradleRunConfig.runCmd()
return gradleRunConfig.runCmd(os.Stdout, os.Stderr)
}

func (gm *GradleModule) createGradleRunConfig() (*gradleRunConfig, error) {
gradleExecPath, err := GetGradleExecPath(gm.gradleExtractorDetails.useWrapper)
func (gm *GradleModule) downloadGradleExtractor(gradleExecPath string) (err error) {
gradleExtractorVersion, initScriptPattern, err := gm.getExtractorVersionAndInitScript(gradleExecPath)
if err != nil {
return nil, err
return err
}
gm.containingBuild.logger.Debug("Using Gradle build-info extractor", gradleExtractorVersion)

dependencyLocalPath := filepath.Join(gm.gradleExtractorDetails.localPath, gradleExtractorVersion)
if err = downloadGradleDependencies(dependencyLocalPath, gradleExtractorVersion, gm.gradleExtractorDetails.downloadExtractorFunc, gm.containingBuild.logger); err != nil {
return err
}
gradlePluginFilename := fmt.Sprintf(gradleExtractorFileName, gradleExtractorVersion)

gm.gradleExtractorDetails.initScript, err = getInitScript(initScriptPattern, dependencyLocalPath, gradlePluginFilename)
return
}

// Return the Gradle extractor version and the relevant init script according to the Gradle version:
// For Gradle >= 6.8.1 use Gradle extractor 5
// For Gradle < 6.8.1 use Gradle extractor 4
// gradleExecPath - The Gradle binary path
func (gm *GradleModule) getExtractorVersionAndInitScript(gradleExecPath string) (string, string, error) {
gradleRunConfig := &gradleRunConfig{
gradle: gradleExecPath,
tasks: "--version",
logger: gm.containingBuild.logger,
}

outBuffer := new(bytes.Buffer)
errBuffer := new(bytes.Buffer)
if err := gradleRunConfig.runCmd(outBuffer, errBuffer); err != nil {
return "", "", err
}
if errBuffer.Len() > 0 {
return "", "", errors.New("unexpected error occurred during attempt to get the Gradle version: " + errBuffer.String())
}

gradleVersion, err := parseGradleVersion(outBuffer.String())
if err != nil {
return "", "", err
}
gm.containingBuild.logger.Info("Using Gradle version:", gradleVersion)
if gradleVersion.AtLeast("6.8.1") {
return gradleExtractor5DependencyVersion, gradleInitScriptExtractor5, nil
}
return gradleExtractor4DependencyVersion, gradleInitScriptExtractor4, nil
}

// Parse the 'gradle --version' output and return the Gradle version.
// versionOutput - The 'gradle --version' output
func parseGradleVersion(versionOutput string) (*version.Version, error) {
match := versionRegex.FindStringSubmatch(versionOutput)
if len(match) == 0 {
return nil, errors.New("couldn't parse the Gradle version: " + versionOutput)
}
return version.NewVersion(match[1]), nil
}

func (gm *GradleModule) createGradleRunConfig(gradleExecPath string) (*gradleRunConfig, error) {
buildInfoPath, err := createEmptyBuildInfoFile(gm.containingBuild)
if err != nil {
return nil, err
Expand All @@ -125,13 +193,13 @@ func (gm *GradleModule) createGradleRunConfig() (*gradleRunConfig, error) {
}, nil
}

func downloadGradleDependencies(downloadTo string, downloadExtractorFunc func(downloadTo, downloadPath string) error, logger utils.Log) error {
filename := fmt.Sprintf(GradleExtractorFileName, GradleExtractorDependencyVersion)
filePath := fmt.Sprintf(GradleExtractorRemotePath, GradleExtractorDependencyVersion)
func downloadGradleDependencies(downloadTo, gradleExtractorVersion string, downloadExtractorFunc func(downloadTo, downloadPath string) error, logger utils.Log) error {
filename := fmt.Sprintf(gradleExtractorFileName, gradleExtractorVersion)
filePath := fmt.Sprintf(gradleExtractorRemotePath, gradleExtractorVersion)
return utils.DownloadDependencies(downloadTo, filename, filePath, downloadExtractorFunc, logger)
}

func getInitScript(gradleDependenciesDir, gradlePluginFilename string) (string, error) {
func getInitScript(initScriptPattern, gradleDependenciesDir, gradlePluginFilename string) (string, error) {
gradleDependenciesDir, err := filepath.Abs(gradleDependenciesDir)
if err != nil {
return "", err
Expand All @@ -145,7 +213,7 @@ func getInitScript(gradleDependenciesDir, gradlePluginFilename string) (string,

gradlePluginPath := filepath.Join(gradleDependenciesDir, gradlePluginFilename)
gradlePluginPath = strings.ReplaceAll(gradlePluginPath, "\\", "\\\\")
initScriptContent := strings.ReplaceAll(GradleInitScript, "${pluginLibDir}", gradlePluginPath)
initScriptContent := strings.ReplaceAll(initScriptPattern, "${pluginLibDir}", gradlePluginPath)
if !utils.IsPathExists(gradleDependenciesDir) {
err = os.MkdirAll(gradleDependenciesDir, 0777)
if err != nil {
Expand Down Expand Up @@ -192,54 +260,14 @@ func (config *gradleRunConfig) GetCmd() *exec.Cmd {
return exec.Command(cmd[0], cmd[1:]...)
}

func (config *gradleRunConfig) runCmd() error {
func (config *gradleRunConfig) runCmd(stdout, stderr io.Writer) error {
command := config.GetCmd()
command.Env = os.Environ()
for k, v := range config.env {
command.Env = append(command.Env, k+"="+v)
}
command.Env = append(command.Env, extractorPropsDir+"="+config.extractorPropsFile)
command.Stderr = os.Stderr
command.Stdout = os.Stderr
command.Stderr = stderr
command.Stdout = stdout
return command.Run()
}

const GradleInitScript = `import org.jfrog.gradle.plugin.artifactory.ArtifactoryPlugin
import org.jfrog.gradle.plugin.artifactory.task.ArtifactoryTask
initscript {
dependencies {
classpath fileTree('${pluginLibDir}')
}
}
addListener(new BuildInfoPluginListener())
class BuildInfoPluginListener extends BuildAdapter {
def void projectsLoaded(Gradle gradle) {
Map<String, String> projectProperties = new HashMap<String, String>(gradle.startParameter.getProjectProperties())
projectProperties.put("build.start", Long.toString(System.currentTimeMillis()))
gradle.startParameter.setProjectProperties(projectProperties)
Project root = gradle.getRootProject()
root.logger.debug("Artifactory plugin: projectsEvaluated: ${root.name}")
if (!"buildSrc".equals(root.name)) {
root.allprojects {
apply {
apply plugin: ArtifactoryPlugin
}
}
}
// Set the "mavenJava" and "ivyJava" publications or
// "archives" configuration to all Artifactory tasks.
for (Project p : root.getAllprojects()) {
Task t = p.getTasks().findByName(ArtifactoryTask.ARTIFACTORY_PUBLISH_TASK_NAME)
if (t != null) {
ArtifactoryTask task = (ArtifactoryTask) t
task.setCiServerBuild()
}
}
}
}
`
99 changes: 88 additions & 11 deletions build/gradle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,99 @@ import (
"testing"

"github.com/jfrog/build-info-go/utils"
"github.com/jfrog/gofrog/version"
"github.com/stretchr/testify/assert"
)

const gradleVersionPattern = `------------------------------------------------------------
Gradle %s
------------------------------------------------------------
Build time: 2019-11-01 20:42:00 UTC
Revision: dd870424f9bd8e195d614dc14bb140f43c22da98
Kotlin: 1.3.41
Groovy: 2.5.4
Ant: Apache Ant(TM) version 1.9.14 compiled on March 12 2019
JVM: 11.0.10 (AdoptOpenJDK 11.0.10+9)
OS: Mac OS X 10.16 x86_64
`

var downloadExtractorsFromReleasesCases = []struct {
extractorVersion string
}{
{extractorVersion: gradleExtractor4DependencyVersion},
{extractorVersion: gradleExtractor5DependencyVersion},
}

func TestDownloadExtractorsFromReleases(t *testing.T) {
tempDirPath, err := utils.CreateTempDir()
assert.NoError(t, err)
defer func() {
assert.NoError(t, utils.RemoveTempDir(tempDirPath))
assert.NoError(t, utils.CleanOldDirs())
}()
for _, testCase := range downloadExtractorsFromReleasesCases {
t.Run(testCase.extractorVersion, func(t *testing.T) {
tempDirPath, err := utils.CreateTempDir()
assert.NoError(t, err)
defer func() {
assert.NoError(t, utils.RemoveTempDir(tempDirPath))
assert.NoError(t, utils.CleanOldDirs())
}()

// Download JAR
err = downloadGradleDependencies(tempDirPath, testCase.extractorVersion, nil, &utils.NullLog{})
assert.NoError(t, err)

// Make sure the Gradle build-info extractor JAR exist
expectedJarPath := filepath.Join(tempDirPath, fmt.Sprintf(gradleExtractorFileName, testCase.extractorVersion))
assert.FileExists(t, expectedJarPath)
})
}
}

var getExtractorVersionAndInitScriptCases = []struct {
projectName string
expectedExtractorVersion string
expectedInitScriptPattern string
}{
{projectName: "gradle-6.8", expectedExtractorVersion: gradleExtractor4DependencyVersion, expectedInitScriptPattern: gradleInitScriptExtractor4},
{projectName: "gradle-6.8.1", expectedExtractorVersion: gradleExtractor5DependencyVersion, expectedInitScriptPattern: gradleInitScriptExtractor5},
{projectName: "gradle-7.0", expectedExtractorVersion: gradleExtractor5DependencyVersion, expectedInitScriptPattern: gradleInitScriptExtractor5},
}

// Download JAR
err = downloadGradleDependencies(tempDirPath, nil, &utils.NullLog{})
func TestGetExtractorVersionAndInitScript(t *testing.T) {
gradleModule := &GradleModule{containingBuild: &Build{logger: &utils.NullLog{}}}
gradleExe, err := GetGradleExecPath(true)
assert.NoError(t, err)
for _, testCase := range getExtractorVersionAndInitScriptCases {
t.Run(testCase.projectName, func(t *testing.T) {
projectPath := filepath.Join("testdata", "gradle", testCase.projectName, gradleExe)
gradleExtractorVersion, initScriptPattern, err := gradleModule.getExtractorVersionAndInitScript(projectPath)
assert.NoError(t, err)
assert.Equal(t, testCase.expectedExtractorVersion, gradleExtractorVersion)
assert.Equal(t, testCase.expectedInitScriptPattern, initScriptPattern)
})
}
}

func TestGetGradlePluginVersionError(t *testing.T) {
gradleModule := &GradleModule{containingBuild: &Build{logger: &utils.NullLog{}}}
_, _, err := gradleModule.getExtractorVersionAndInitScript("non-exist")
assert.ErrorContains(t, err, "executable file not found")
}

var parseGradleVersionCases = []struct {
versionOutput string
expectedVersion *version.Version
}{
{versionOutput: "1.2", expectedVersion: version.NewVersion("1.2")},
{versionOutput: "1.2.3", expectedVersion: version.NewVersion("1.2.3")},
{versionOutput: "1.23.4", expectedVersion: version.NewVersion("1.23.4")},
{versionOutput: "1.2-rc-1", expectedVersion: version.NewVersion("1.2-rc-1")},
}

// Make sure the Gradle build-info extractor JAR exist
expectedJarPath := filepath.Join(tempDirPath, fmt.Sprintf(GradleExtractorFileName, GradleExtractorDependencyVersion))
assert.FileExists(t, expectedJarPath)
func TestParseGradleVersion(t *testing.T) {
for _, testCase := range parseGradleVersionCases {
t.Run(testCase.expectedVersion.GetVersion(), func(t *testing.T) {
actualVersion, err := parseGradleVersion(fmt.Sprintf(gradleVersionPattern, testCase.versionOutput))
assert.NoError(t, err)
assert.Equal(t, testCase.expectedVersion, actualVersion)
})
}
}
Loading

0 comments on commit 8772d83

Please sign in to comment.