From a7bc9b343082c582836fd7b4816092853198fc4e Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Sat, 17 Aug 2024 20:18:16 +1000 Subject: [PATCH] feat: add support for Gradle (#2411) Co-authored-by: Alec Thomas --- bin/.gradle-8.9.pkg | 1 + bin/gradle | 1 + internal/buildengine/build_java.go | 11 +++-- internal/buildengine/discover_test.go | 6 +++ internal/moduleconfig/moduleconfig.go | 49 +++++++++++++++---- jvm-runtime/jvm_integration_test.go | 11 +++++ .../testdata/java/gradle/build.gradle.kts | 36 ++++++++++++++ jvm-runtime/testdata/java/gradle/ftl.toml | 2 + .../testdata/java/gradle/gradle.properties | 7 +++ .../testdata/java/gradle/settings.gradle.kts | 13 +++++ .../src/main/java/org/acme/EchoVerb.java | 13 +++++ 11 files changed, 136 insertions(+), 14 deletions(-) create mode 120000 bin/.gradle-8.9.pkg create mode 120000 bin/gradle create mode 100644 jvm-runtime/testdata/java/gradle/build.gradle.kts create mode 100644 jvm-runtime/testdata/java/gradle/ftl.toml create mode 100644 jvm-runtime/testdata/java/gradle/gradle.properties create mode 100644 jvm-runtime/testdata/java/gradle/settings.gradle.kts create mode 100644 jvm-runtime/testdata/java/gradle/src/main/java/org/acme/EchoVerb.java diff --git a/bin/.gradle-8.9.pkg b/bin/.gradle-8.9.pkg new file mode 120000 index 0000000000..383f4511d4 --- /dev/null +++ b/bin/.gradle-8.9.pkg @@ -0,0 +1 @@ +hermit \ No newline at end of file diff --git a/bin/gradle b/bin/gradle new file mode 120000 index 0000000000..9f2f4341a0 --- /dev/null +++ b/bin/gradle @@ -0,0 +1 @@ +.gradle-8.9.pkg \ No newline at end of file diff --git a/internal/buildengine/build_java.go b/internal/buildengine/build_java.go index 98e449f525..879c8af4f8 100644 --- a/internal/buildengine/build_java.go +++ b/internal/buildengine/build_java.go @@ -10,14 +10,17 @@ import ( "github.com/TBD54566975/ftl" "github.com/TBD54566975/ftl/internal/exec" "github.com/TBD54566975/ftl/internal/log" + "github.com/TBD54566975/ftl/internal/moduleconfig" ) func buildJavaModule(ctx context.Context, module Module) error { logger := log.FromContext(ctx) - if err := SetPOMProperties(ctx, module.Config.Dir); err != nil { - // This is not a critical error, things will probably work fine - // TBH updating the pom is maybe not the best idea anyway - logger.Warnf("unable to update ftl.version in %s: %s", module.Config.Dir, err.Error()) + if module.Config.Java.BuildTool == moduleconfig.JavaBuildToolMaven { + if err := SetPOMProperties(ctx, module.Config.Dir); err != nil { + // This is not a critical error, things will probably work fine + // TBH updating the pom is maybe not the best idea anyway + logger.Warnf("unable to update ftl.version in %s: %s", module.Config.Dir, err.Error()) + } } logger.Infof("Using build command '%s'", module.Config.Build) command := exec.Command(ctx, log.Debug, module.Config.Dir, "bash", "-c", module.Config.Build) diff --git a/internal/buildengine/discover_test.go b/internal/buildengine/discover_test.go index 916488f0b0..d8c1377b65 100644 --- a/internal/buildengine/discover_test.go +++ b/internal/buildengine/discover_test.go @@ -87,6 +87,9 @@ func TestDiscoverModules(t *testing.T) { "src/**", "target/generated-sources", }, + Java: moduleconfig.ModuleJavaConfig{ + BuildTool: "maven", + }, }, }, { @@ -129,6 +132,9 @@ func TestDiscoverModules(t *testing.T) { "src/**", "target/generated-sources", }, + Java: moduleconfig.ModuleJavaConfig{ + BuildTool: "maven", + }, }, }, { diff --git a/internal/moduleconfig/moduleconfig.go b/internal/moduleconfig/moduleconfig.go index 9b137ca8a4..09a701e313 100644 --- a/internal/moduleconfig/moduleconfig.go +++ b/internal/moduleconfig/moduleconfig.go @@ -22,7 +22,12 @@ type ModuleGoConfig struct{} type ModuleKotlinConfig struct{} // ModuleJavaConfig is language-specific configuration for Java modules. -type ModuleJavaConfig struct{} +type ModuleJavaConfig struct { + BuildTool string `toml:"build-tool"` +} + +const JavaBuildToolMaven string = "maven" +const JavaBuildToolGradle string = "gradle" // ModuleConfig is the configuration for an FTL module. // @@ -130,21 +135,40 @@ func setConfigDefaults(moduleDir string, config *ModuleConfig) error { } switch config.Language { case "kotlin", "java": + if config.Build == "" { - config.Build = "mvn -B package" - } - if config.DeployDir == "" { - config.DeployDir = "target" + pom := filepath.Join(moduleDir, "pom.xml") + buildGradle := filepath.Join(moduleDir, "build.gradle") + buildGradleKts := filepath.Join(moduleDir, "build.gradle.kts") + if config.Java.BuildTool == JavaBuildToolMaven || fileExists(pom) { + config.Java.BuildTool = JavaBuildToolMaven + config.Build = "mvn -B package" + if config.DeployDir == "" { + config.DeployDir = "target" + } + if len(config.Watch) == 0 { + config.Watch = []string{"pom.xml", "src/**", "target/generated-sources"} + } + } else if config.Java.BuildTool == JavaBuildToolGradle || fileExists(buildGradle) || fileExists(buildGradleKts) { + config.Java.BuildTool = JavaBuildToolGradle + config.Build = "gradle build" + if config.DeployDir == "" { + config.DeployDir = "build" + } + if len(config.Watch) == 0 { + config.Watch = []string{"pom.xml", "src/**", "build/generated"} + } + } else { + return fmt.Errorf("could not find JVM build file in %s", moduleDir) + } } + if config.GeneratedSchemaDir == "" { config.GeneratedSchemaDir = "src/main/ftl-module-schema" } if len(config.Deploy) == 0 { config.Deploy = []string{"main", "quarkus-app"} } - if len(config.Watch) == 0 { - config.Watch = []string{"pom.xml", "src/**", "target/generated-sources"} - } case "go": if config.DeployDir == "" { config.DeployDir = ".ftl" @@ -178,17 +202,22 @@ func setConfigDefaults(moduleDir string, config *ModuleConfig) error { // Do some validation. if !isBeneath(moduleDir, config.DeployDir) { - return fmt.Errorf("deploy-dir must be relative to the module directory") + return fmt.Errorf("deploy-dir %s must be relative to the module directory %s", config.DeployDir, moduleDir) } for _, deploy := range config.Deploy { if !isBeneath(moduleDir, deploy) { - return fmt.Errorf("deploy files must be relative to the module directory") + return fmt.Errorf("deploy %s files must be relative to the module directory %s", deploy, moduleDir) } } return nil } +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + func isBeneath(moduleDir, path string) bool { resolved := filepath.Clean(filepath.Join(moduleDir, path)) return strings.HasPrefix(resolved, strings.TrimSuffix(moduleDir, "/")+"/") diff --git a/jvm-runtime/jvm_integration_test.go b/jvm-runtime/jvm_integration_test.go index ff291b26ee..31f2e48e43 100644 --- a/jvm-runtime/jvm_integration_test.go +++ b/jvm-runtime/jvm_integration_test.go @@ -130,6 +130,17 @@ func TestJVMToGoCall(t *testing.T) { ) } +func TestGradle(t *testing.T) { + in.Run(t, + in.WithLanguages("java"), + in.CopyModule("gradle"), + in.Deploy("gradle"), + in.Call("gradle", "echo", "Bob", func(t testing.TB, response string) { + assert.Equal(t, "Hello, Bob!", response) + }), + ) +} + func PairedTest(name string, testFunc func(module string) in.Action) []in.SubTest { return []in.SubTest{ { diff --git a/jvm-runtime/testdata/java/gradle/build.gradle.kts b/jvm-runtime/testdata/java/gradle/build.gradle.kts new file mode 100644 index 0000000000..72ab5b7c04 --- /dev/null +++ b/jvm-runtime/testdata/java/gradle/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + java + id("io.quarkus") +} + +repositories { + mavenCentral() + mavenLocal() +} + +val quarkusPlatformGroupId: String by project +val quarkusPlatformArtifactId: String by project +val quarkusPlatformVersion: String by project + +dependencies { + implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")) + implementation("xyz.block.ftl:ftl-java-runtime:1.0-SNAPSHOT") + testImplementation("io.quarkus:quarkus-junit5") + testImplementation("io.rest-assured:rest-assured") +} + +group = "org.acme" +version = "1.0.0-SNAPSHOT" + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.withType { + systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager") +} +tasks.withType { + options.encoding = "UTF-8" + options.compilerArgs.add("-parameters") +} diff --git a/jvm-runtime/testdata/java/gradle/ftl.toml b/jvm-runtime/testdata/java/gradle/ftl.toml new file mode 100644 index 0000000000..0c3d4fa25f --- /dev/null +++ b/jvm-runtime/testdata/java/gradle/ftl.toml @@ -0,0 +1,2 @@ +module = "gradle" +language = "java" diff --git a/jvm-runtime/testdata/java/gradle/gradle.properties b/jvm-runtime/testdata/java/gradle/gradle.properties new file mode 100644 index 0000000000..c835dc8968 --- /dev/null +++ b/jvm-runtime/testdata/java/gradle/gradle.properties @@ -0,0 +1,7 @@ +# Gradle properties + +quarkusPluginId=io.quarkus +quarkusPluginVersion=3.13.2 +quarkusPlatformGroupId=io.quarkus.platform +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformVersion=3.13.2 diff --git a/jvm-runtime/testdata/java/gradle/settings.gradle.kts b/jvm-runtime/testdata/java/gradle/settings.gradle.kts new file mode 100644 index 0000000000..0b1b805116 --- /dev/null +++ b/jvm-runtime/testdata/java/gradle/settings.gradle.kts @@ -0,0 +1,13 @@ +pluginManagement { + val quarkusPluginVersion: String by settings + val quarkusPluginId: String by settings + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } + plugins { + id(quarkusPluginId) version quarkusPluginVersion + } +} +rootProject.name="gradle" diff --git a/jvm-runtime/testdata/java/gradle/src/main/java/org/acme/EchoVerb.java b/jvm-runtime/testdata/java/gradle/src/main/java/org/acme/EchoVerb.java new file mode 100644 index 0000000000..592ece0da3 --- /dev/null +++ b/jvm-runtime/testdata/java/gradle/src/main/java/org/acme/EchoVerb.java @@ -0,0 +1,13 @@ +package org.acme; + +import xyz.block.ftl.Export; +import xyz.block.ftl.Verb; + +public class EchoVerb { + + @Export + @Verb + public String echo(String request) { + return "Hello, " + request + "!"; + } +} \ No newline at end of file