Skip to content

Commit

Permalink
feat: JVM scaffolding (#2410)
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartwdouglas authored Aug 17, 2024
1 parent 54a18d5 commit 186d079
Show file tree
Hide file tree
Showing 89 changed files with 407 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
70 changes: 60 additions & 10 deletions cmd/ftl/cmd_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -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."`
}

Expand All @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions jvm-runtime/java/devel.go
Original file line number Diff line number Diff line change
@@ -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") }
21 changes: 21 additions & 0 deletions jvm-runtime/java/release.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/[email protected]
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/.maven-3.9.8.pkg
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/[email protected]
7 changes: 7 additions & 0 deletions jvm-runtime/java/scaffolding/bin/README.hermit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Hermit environment

This is a [Hermit](https://github.com/cashapp/hermit) bin directory.

The symlinks in this directory are managed by Hermit and will automatically
download and install Hermit itself as well as packages. These packages are
local to this environment.
21 changes: 21 additions & 0 deletions jvm-runtime/java/scaffolding/bin/activate-hermit
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash
# This file must be used with "source bin/activate-hermit" from bash or zsh.
# You cannot run it directly
#
# THIS FILE IS GENERATED; DO NOT MODIFY

if [ "${BASH_SOURCE-}" = "$0" ]; then
echo "You must source this script: \$ source $0" >&2
exit 33
fi

BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")"
if "${BIN_DIR}/hermit" noop > /dev/null; then
eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")"

if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then
hash -r 2>/dev/null
fi

echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated"
fi
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/ftl
43 changes: 43 additions & 0 deletions jvm-runtime/java/scaffolding/bin/hermit
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
#
# THIS FILE IS GENERATED; DO NOT MODIFY

set -eo pipefail

export HERMIT_USER_HOME=~

if [ -z "${HERMIT_STATE_DIR}" ]; then
case "$(uname -s)" in
Darwin)
export HERMIT_STATE_DIR="${HERMIT_USER_HOME}/Library/Caches/hermit"
;;
Linux)
export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HERMIT_USER_HOME}/.cache}/hermit"
;;
esac
fi

export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}"
HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")"
export HERMIT_CHANNEL
export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit}

if [ ! -x "${HERMIT_EXE}" ]; then
echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2
INSTALL_SCRIPT="$(mktemp)"
# This value must match that of the install script
INSTALL_SCRIPT_SHA256="180e997dd837f839a3072a5e2f558619b6d12555cd5452d3ab19d87720704e38"
if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then
curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}"
else
# Install script is versioned by its sha256sum value
curl -fsSL "${HERMIT_DIST_URL}/install-${INSTALL_SCRIPT_SHA256}.sh" -o "${INSTALL_SCRIPT}"
# Verify install script's sha256sum
openssl dgst -sha256 "${INSTALL_SCRIPT}" | \
awk -v EXPECTED="$INSTALL_SCRIPT_SHA256" \
'$2!=EXPECTED {print "Install script sha256 " $2 " does not match " EXPECTED; exit 1}'
fi
/bin/bash "${INSTALL_SCRIPT}" 1>&2
fi

exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@"
Empty file.
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jar
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jarsigner
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/java
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/javac
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/javadoc
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/javap
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jcmd
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jconsole
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jdb
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jdeprscan
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jdeps
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jfr
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jhsdb
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jimage
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jinfo
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jlink
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jmap
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jmod
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jpackage
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jps
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jrunscript
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jshell
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jstack
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jstat
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/jstatd
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/keytool
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/mvn
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/mvnDebug
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/rmiregistry
1 change: 1 addition & 0 deletions jvm-runtime/java/scaffolding/bin/serialver
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module = "{{ .Name }}"
language = "java"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ftl.{{ .Group }}</groupId>
<artifactId>{{ .Name }}</artifactId>
<version>1.0-SNAPSHOT</version>

<parent>
<groupId>xyz.block.ftl</groupId>
<artifactId>ftl-build-parent-java</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

</project>
Original file line number Diff line number Diff line change
@@ -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 + "!";
}
}
16 changes: 16 additions & 0 deletions jvm-runtime/jvm_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
12 changes: 12 additions & 0 deletions jvm-runtime/kotlin/devel.go
Original file line number Diff line number Diff line change
@@ -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") }
Loading

0 comments on commit 186d079

Please sign in to comment.