Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: JVM can make local verb calls via generated clients #3299

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/buildengine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,10 @@ func (e *Engine) buildWithCallback(ctx context.Context, callback buildCallback,
return err
}
}
err = WriteGenericSchemaFiles(newSchemas, metasMap)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this approach will write out the generated schema after the build, which then results in clients being generated?

Won't this also generate new copies of things like Data classes that will then be duplicated?

I guess with this approach you have to check the schema in, as projects won't be buildable otherwise.

Copy link
Contributor Author

@tomdaffurn tomdaffurn Nov 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't change the approach much. Previously it wrote schema files inside the generate stubs function on line 903. Stubs are only written for modules not in the last topology group. ie modules that have dependant modules.

Now it does the same but outside the if statement, for every group in the topology.

Yes, it does generate duplicated data classes, so when using the verb client you need to use the ftl.module.DataClass package instead of your own. That's a bit sucky.

Maybe we can change the codegen to special case local verb clients...

if err != nil {
return err
}

moduleNames := []string{}
for _, module := range knownSchemas {
Expand Down
98 changes: 44 additions & 54 deletions internal/buildengine/stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,56 +20,6 @@ var buildDirName = ".ftl"
//
// Currently, only Go stubs are supported. Kotlin and other language stubs can be added in the future.
func GenerateStubs(ctx context.Context, projectRoot string, modules []*schema.Module, metas map[string]moduleMeta) error {
err := generateStubsForEachLanguage(ctx, projectRoot, modules, metas)
if err != nil {
return err
}
return writeGenericSchemaFiles(modules, metas)
}

// CleanStubs removes all generated stubs.
func CleanStubs(ctx context.Context, projectRoot string) error {
logger := log.FromContext(ctx)
logger.Debugf("Deleting all generated stubs")
sharedFtlDir := filepath.Join(projectRoot, buildDirName)

// Wipe the modules directory to ensure we don't have any stale modules.
err := os.RemoveAll(sharedFtlDir)
if err != nil {
return fmt.Errorf("failed to remove %s: %w", sharedFtlDir, err)
}

return nil
}

// SyncStubReferences syncs the references in the generated stubs.
//
// For Go, this means updating all the go.work files to include all known modules in the shared stubbed modules directory.
func SyncStubReferences(ctx context.Context, projectRoot string, moduleNames []string, metas map[string]moduleMeta) error {
wg, wgctx := errgroup.WithContext(ctx)
for _, meta := range metas {
stubsRoot := stubsLanguageDir(projectRoot, meta.module.Config.Language)
if err := meta.plugin.SyncStubReferences(wgctx, meta.module.Config, stubsRoot, moduleNames); err != nil {
return err //nolint:wrapcheck
}
return nil
}
err := wg.Wait()
if err != nil {
return fmt.Errorf("failed to sync go stub references: %w", err)
}
return nil
}

func stubsLanguageDir(projectRoot, language string) string {
return filepath.Join(projectRoot, buildDirName, language, "modules")
}

func stubsModuleDir(projectRoot, language, module string) string {
return filepath.Join(stubsLanguageDir(projectRoot, language), module)
}

func generateStubsForEachLanguage(ctx context.Context, projectRoot string, modules []*schema.Module, metas map[string]moduleMeta) error {
modulesByName := map[string]*schema.Module{}
for _, module := range modules {
modulesByName[module.Name] = module
Expand Down Expand Up @@ -108,7 +58,42 @@ func generateStubsForEachLanguage(ctx context.Context, projectRoot string, modul
return nil
}

func writeGenericSchemaFiles(modules []*schema.Module, metas map[string]moduleMeta) error {
// CleanStubs removes all generated stubs.
func CleanStubs(ctx context.Context, projectRoot string) error {
logger := log.FromContext(ctx)
logger.Debugf("Deleting all generated stubs")
sharedFtlDir := filepath.Join(projectRoot, buildDirName)

// Wipe the modules directory to ensure we don't have any stale modules.
err := os.RemoveAll(sharedFtlDir)
if err != nil {
return fmt.Errorf("failed to remove %s: %w", sharedFtlDir, err)
}

return nil
}

// SyncStubReferences syncs the references in the generated stubs.
//
// For Go, this means updating all the go.work files to include all known modules in the shared stubbed modules directory.
func SyncStubReferences(ctx context.Context, projectRoot string, moduleNames []string, metas map[string]moduleMeta) error {
wg, wgctx := errgroup.WithContext(ctx)
for _, meta := range metas {
stubsRoot := stubsLanguageDir(projectRoot, meta.module.Config.Language)
if err := meta.plugin.SyncStubReferences(wgctx, meta.module.Config, stubsRoot, moduleNames); err != nil {
return err //nolint:wrapcheck
}
return nil
}
err := wg.Wait()
if err != nil {
return fmt.Errorf("failed to sync go stub references: %w", err)
}
return nil
}

// WriteGenericSchemaFiles writes <module>.pb schema files for the given modules to the GeneratedSchemaDir
func WriteGenericSchemaFiles(modules []*schema.Module, metas map[string]moduleMeta) error {
sch := &schema.Schema{Modules: modules}
for _, meta := range metas {
module := meta.module.Config
Expand All @@ -123,9 +108,6 @@ func writeGenericSchemaFiles(modules []*schema.Module, metas map[string]moduleMe
}

for _, mod := range sch.Modules {
if mod.Name == module.Module {
continue
}
data, err := schema.ModuleToBytes(mod)
if err != nil {
return fmt.Errorf("failed to export module schema for module %s %w", mod.Name, err)
Expand All @@ -138,3 +120,11 @@ func writeGenericSchemaFiles(modules []*schema.Module, metas map[string]moduleMe
}
return nil
}

func stubsLanguageDir(projectRoot, language string) string {
return filepath.Join(projectRoot, buildDirName, language, "modules")
}

func stubsModuleDir(projectRoot, language, module string) string {
return filepath.Join(stubsLanguageDir(projectRoot, language), module)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import com.fasterxml.jackson.databind.JsonNode;

import ftl.javaserver.Shape;
import ftl.javaserver.StringEnumVerbClient;
import xyz.block.ftl.Export;
import xyz.block.ftl.Verb;

Expand Down Expand Up @@ -38,4 +40,10 @@ public ShapeWrapper stringEnumVerb(ShapeWrapper shape) {
public AnimalWrapper typeEnumVerb(AnimalWrapper animal) {
return animal;
}

@Export
@Verb
public ftl.javaserver.ShapeWrapper localVerbCall(StringEnumVerbClient client) {
return client.stringEnumVerb(new ftl.javaserver.ShapeWrapper(Shape.SQUARE));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package xyz.block.ftl.test

import ftl.kotlinserver.StringEnumVerbClient
import xyz.block.ftl.Export
import xyz.block.ftl.Verb

Expand All @@ -23,4 +24,11 @@ class Verbs {
fun typeEnumVerb(animal: AnimalWrapper): AnimalWrapper {
return animal
}

@Export
@Verb
fun localVerbCall(client: StringEnumVerbClient): ftl.kotlinserver.ShapeWrapper {
return client.stringEnumVerb(ftl.kotlinserver.ShapeWrapper(ftl.kotlinserver.Shape.Square))
}

}
Loading