From 02b1898be8d3a2a8652c304243cab23f02a1fade Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Sat, 17 Aug 2024 16:23:23 +1000 Subject: [PATCH] feat: JVM scaffolding --- Justfile | 2 +- bin/.ftl@latest.pkg | 1 + bin/ftl | 1 + cmd/ftl/cmd_new.go | 70 ++++++++++++++++--- jvm-runtime/java/devel.go | 12 ++++ jvm-runtime/java/release.go | 21 ++++++ .../{{ .Name | camel | lower }}/ftl.toml | 2 + .../{{ .Name | camel | lower }}/pom.xml.tmpl | 14 ++++ .../main/java/{{ .PackageDir }}/EchoVerb.java | 13 ++++ jvm-runtime/jvm_integration_test.go | 16 +++++ jvm-runtime/kotlin/devel.go | 12 ++++ jvm-runtime/kotlin/release.go | 21 ++++++ .../{{ .Name | camel | lower }}/ftl.toml | 2 + .../{{ .Name | camel | lower }}/pom.xml.tmpl | 14 ++++ .../main/kotlin/{{ .PackageDir }}/EchoVerb.kt | 9 +++ 15 files changed, 199 insertions(+), 11 deletions(-) create mode 120000 bin/.ftl@latest.pkg create mode 120000 bin/ftl create mode 100644 jvm-runtime/java/devel.go create mode 100644 jvm-runtime/java/release.go create mode 100644 jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/ftl.toml create mode 100644 jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/pom.xml.tmpl create mode 100644 jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/src/main/java/{{ .PackageDir }}/EchoVerb.java create mode 100644 jvm-runtime/kotlin/devel.go create mode 100644 jvm-runtime/kotlin/release.go create mode 100644 jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/ftl.toml create mode 100644 jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/pom.xml.tmpl create mode 100644 jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/src/main/kotlin/{{ .PackageDir }}/EchoVerb.kt diff --git a/Justfile b/Justfile index 40d51ec90d..355402b84e 100644 --- a/Justfile +++ b/Justfile @@ -7,7 +7,7 @@ VERSION := `git describe --tags --always | sed -e 's/^v//'` RUNNER_TEMPLATE_ZIP := "backend/controller/scaling/localscaling/template.zip" TIMESTAMP := `date +%s` SCHEMA_OUT := "backend/protos/xyz/block/ftl/v1/schema/schema.proto" -ZIP_DIRS := "go-runtime/compile/build-template go-runtime/compile/external-module-template go-runtime/compile/main-work-template internal/projectinit/scaffolding go-runtime/scaffolding" +ZIP_DIRS := "go-runtime/compile/build-template go-runtime/compile/external-module-template go-runtime/compile/main-work-template internal/projectinit/scaffolding go-runtime/scaffolding jvm-runtime/java/scaffolding jvm-runtime/kotlin/scaffolding" FRONTEND_OUT := "frontend/dist/index.html" EXTENSION_OUT := "extensions/vscode/dist/extension.js" PROTOS_IN := "backend/protos/xyz/block/ftl/v1/schema/schema.proto backend/protos/xyz/block/ftl/v1/console/console.proto backend/protos/xyz/block/ftl/v1/ftl.proto backend/protos/xyz/block/ftl/v1/schema/runtime.proto" diff --git a/bin/.ftl@latest.pkg b/bin/.ftl@latest.pkg new file mode 120000 index 0000000000..383f4511d4 --- /dev/null +++ b/bin/.ftl@latest.pkg @@ -0,0 +1 @@ +hermit \ No newline at end of file diff --git a/bin/ftl b/bin/ftl new file mode 120000 index 0000000000..47611de9b6 --- /dev/null +++ b/bin/ftl @@ -0,0 +1 @@ +.ftl@latest.pkg \ No newline at end of file diff --git a/cmd/ftl/cmd_new.go b/cmd/ftl/cmd_new.go index 82dde183e4..4b204217fa 100644 --- a/cmd/ftl/cmd_new.go +++ b/cmd/ftl/cmd_new.go @@ -21,10 +21,13 @@ import ( "github.com/TBD54566975/ftl/internal/exec" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/projectconfig" + "github.com/TBD54566975/ftl/jvm-runtime/java" + "github.com/TBD54566975/ftl/jvm-runtime/kotlin" ) type newCmd struct { Go newGoCmd `cmd:"" help:"Initialize a new FTL Go module."` + Java newJavaCmd `cmd:"" help:"Initialize a new FTL Java module."` Kotlin newKotlinCmd `cmd:"" help:"Initialize a new FTL Kotlin module."` } @@ -35,12 +38,18 @@ type newGoCmd struct { GoVersion string } +type newJavaCmd struct { + Dir string `arg:"" help:"Directory to initialize the module in."` + Name string `arg:"" help:"Name of the FTL module to create underneath the base directory."` + Group string `help:"The Maven groupId of the project." default:"com.example"` +} type newKotlinCmd struct { - Dir string `arg:"" help:"Directory to initialize the module in."` - Name string `arg:"" help:"Name of the FTL module to create underneath the base directory."` + Dir string `arg:"" help:"Directory to initialize the module in."` + Name string `arg:"" help:"Name of the FTL module to create underneath the base directory."` + Group string `help:"The Maven groupId of the project." default:"com.example"` } -func (i newGoCmd) Run(ctx context.Context) error { +func (i newGoCmd) Run(ctx context.Context, config projectconfig.Config) error { name, path, err := validateModule(i.Dir, i.Name) if err != nil { return err @@ -51,11 +60,6 @@ func (i newGoCmd) Run(ctx context.Context) error { return fmt.Errorf("module name %q must be a valid Go module name and not a reserved keyword", name) } - config, err := projectconfig.Load(ctx, "") - if err != nil { - return fmt.Errorf("failed to load project config: %w", err) - } - logger := log.FromContext(ctx) logger.Debugf("Creating FTL Go module %q in %s", name, path) @@ -84,8 +88,54 @@ func (i newGoCmd) Run(ctx context.Context) error { return nil } -func (i newKotlinCmd) Run(ctx context.Context) error { - return fmt.Errorf("kotlin scaffolinging temporarily removed") +func (i newJavaCmd) Run(ctx context.Context, config projectconfig.Config) error { + return RunJvmScaffolding(ctx, config, i.Dir, i.Name, i.Group, java.Files()) +} + +func (i newKotlinCmd) Run(ctx context.Context, config projectconfig.Config) error { + return RunJvmScaffolding(ctx, config, i.Dir, i.Name, i.Group, kotlin.Files()) +} + +func RunJvmScaffolding(ctx context.Context, config projectconfig.Config, dir string, name string, group string, source *zip.Reader) error { + name, path, err := validateModule(dir, name) + if err != nil { + return err + } + + logger := log.FromContext(ctx) + logger.Debugf("Creating FTL module %q in %s", name, path) + + packageDir := strings.ReplaceAll(group, ".", "/") + + javaContext := struct { + Dir string + Name string + Group string + PackageDir string + }{ + Dir: dir, + Name: name, + Group: group, + PackageDir: packageDir, + } + + if err := scaffold(ctx, config.Hermit, source, dir, javaContext); err != nil { + return err + } + + _, ok := internal.GitRoot(dir).Get() + if !config.NoGit && ok { + logger.Debugf("Adding files to git") + if config.Hermit { + if err := maybeGitAdd(ctx, dir, "bin/*"); err != nil { + return err + } + } + if err := maybeGitAdd(ctx, dir, filepath.Join(name, "*")); err != nil { + return err + } + } + return nil } func validateModule(dir string, name string) (string, string, error) { diff --git a/jvm-runtime/java/devel.go b/jvm-runtime/java/devel.go new file mode 100644 index 0000000000..6c09d47307 --- /dev/null +++ b/jvm-runtime/java/devel.go @@ -0,0 +1,12 @@ +//go:build !release + +package java + +import ( + "archive/zip" + + "github.com/TBD54566975/ftl/internal" +) + +// Files is the FTL Go runtime scaffolding files. +func Files() *zip.Reader { return internal.ZipRelativeToCaller("scaffolding") } diff --git a/jvm-runtime/java/release.go b/jvm-runtime/java/release.go new file mode 100644 index 0000000000..87bae32b32 --- /dev/null +++ b/jvm-runtime/java/release.go @@ -0,0 +1,21 @@ +//go:build release + +package java + +import ( + "archive/zip" + "bytes" + _ "embed" +) + +//go:embed scaffolding.zip +var archive []byte + +// Files is the FTL Go runtime scaffolding files. +func Files() *zip.Reader { + zr, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive))) + if err != nil { + panic(err) + } + return zr +} diff --git a/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/ftl.toml b/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/ftl.toml new file mode 100644 index 0000000000..828a8b57ce --- /dev/null +++ b/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/ftl.toml @@ -0,0 +1,2 @@ +module = "{{ .Name }}" +language = "java" diff --git a/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/pom.xml.tmpl b/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/pom.xml.tmpl new file mode 100644 index 0000000000..cf8d370b0c --- /dev/null +++ b/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/pom.xml.tmpl @@ -0,0 +1,14 @@ + + + 4.0.0 + ftl.{{ .Group }} + {{ .Name }} + 1.0-SNAPSHOT + + + xyz.block.ftl + ftl-build-parent-java + 1.0-SNAPSHOT + + + diff --git a/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/src/main/java/{{ .PackageDir }}/EchoVerb.java b/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/src/main/java/{{ .PackageDir }}/EchoVerb.java new file mode 100644 index 0000000000..29cd33c304 --- /dev/null +++ b/jvm-runtime/java/scaffolding/{{ .Name | camel | lower }}/src/main/java/{{ .PackageDir }}/EchoVerb.java @@ -0,0 +1,13 @@ +package {{ .Group }}; + +import xyz.block.ftl.Export; +import xyz.block.ftl.Verb; + +public class EchoVerb { + + @Export + @Verb + public String echo(String request) { + return "Hello, " + request + "!"; + } +} diff --git a/jvm-runtime/jvm_integration_test.go b/jvm-runtime/jvm_integration_test.go index e7411f31f2..ff291b26ee 100644 --- a/jvm-runtime/jvm_integration_test.go +++ b/jvm-runtime/jvm_integration_test.go @@ -14,6 +14,22 @@ import ( "github.com/alecthomas/repr" ) +func TestLifecycle(t *testing.T) { + in.Run(t, + in.WithLanguages("java", "kotlin"), + in.GitInit(), + in.Exec("rm", "ftl-project.toml"), + in.IfLanguage("kotlin", in.Exec("rm", "-r", "echo")), //horrible, but we need to do cleanup, I wonder if we should be running each test in a separate directory + in.Exec("ftl", "init", "test", "."), + in.IfLanguage("java", in.Exec("ftl", "new", "java", ".", "echo")), + in.IfLanguage("kotlin", in.Exec("ftl", "new", "kotlin", ".", "echo")), + in.Deploy("echo"), + in.Call("echo", "echo", "Bob", func(t testing.TB, response string) { + assert.Equal(t, "Hello, Bob!", response) + }), + ) +} + func TestJVMToGoCall(t *testing.T) { exampleObject := TestObject{ diff --git a/jvm-runtime/kotlin/devel.go b/jvm-runtime/kotlin/devel.go new file mode 100644 index 0000000000..b0cf1bb01f --- /dev/null +++ b/jvm-runtime/kotlin/devel.go @@ -0,0 +1,12 @@ +//go:build !release + +package kotlin + +import ( + "archive/zip" + + "github.com/TBD54566975/ftl/internal" +) + +// Files is the FTL Go runtime scaffolding files. +func Files() *zip.Reader { return internal.ZipRelativeToCaller("scaffolding") } diff --git a/jvm-runtime/kotlin/release.go b/jvm-runtime/kotlin/release.go new file mode 100644 index 0000000000..b757334241 --- /dev/null +++ b/jvm-runtime/kotlin/release.go @@ -0,0 +1,21 @@ +//go:build release + +package kotlin + +import ( + "archive/zip" + "bytes" + _ "embed" +) + +//go:embed scaffolding.zip +var archive []byte + +// Files is the FTL Go runtime scaffolding files. +func Files() *zip.Reader { + zr, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive))) + if err != nil { + panic(err) + } + return zr +} diff --git a/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/ftl.toml b/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/ftl.toml new file mode 100644 index 0000000000..c9bc601c3e --- /dev/null +++ b/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/ftl.toml @@ -0,0 +1,2 @@ +module = "{{ .Name }}" +language = "kotlin" diff --git a/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/pom.xml.tmpl b/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/pom.xml.tmpl new file mode 100644 index 0000000000..a4ea755f42 --- /dev/null +++ b/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/pom.xml.tmpl @@ -0,0 +1,14 @@ + + + 4.0.0 + ftl.{{ .Group }} + {{ .Name }} + 1.0-SNAPSHOT + + + xyz.block.ftl + ftl-build-parent-kotlin + 1.0-SNAPSHOT + + + diff --git a/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/src/main/kotlin/{{ .PackageDir }}/EchoVerb.kt b/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/src/main/kotlin/{{ .PackageDir }}/EchoVerb.kt new file mode 100644 index 0000000000..595bba72d7 --- /dev/null +++ b/jvm-runtime/kotlin/scaffolding/{{ .Name | camel | lower }}/src/main/kotlin/{{ .PackageDir }}/EchoVerb.kt @@ -0,0 +1,9 @@ +package {{ .Group }} + +import xyz.block.ftl.Export +import xyz.block.ftl.Verb + + +@Export +@Verb +fun echo(req: String): String = "Hello, $req!"