Skip to content

Commit

Permalink
fix: correctly generate schema files (#3844)
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartwdouglas authored Dec 23, 2024
1 parent fd758da commit 851018f
Show file tree
Hide file tree
Showing 17 changed files with 284 additions and 242 deletions.
242 changes: 121 additions & 121 deletions backend/protos/xyz/block/ftl/language/v1/language.pb.go

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions backend/protos/xyz/block/ftl/language/v1/language.proto
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ message ModuleConfigDefaultsResponse {
// Build lock path to prevent concurrent builds
optional string build_lock = 4;

// Default relative path to the directory containing generated schema files
optional string generated_schema_dir = 5;

// Default patterns to watch for file changes, relative to the module directory
repeated string watch = 6;

Expand Down Expand Up @@ -289,6 +286,9 @@ message SyncStubReferencesRequest {

// The names of all modules that have had stubs generated
repeated string modules = 3;

// The complete FTL schema
ftl.schema.v1.Schema schema = 4;
}

message SyncStubReferencesResponse {}
Expand Down
25 changes: 25 additions & 0 deletions common/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/alecthomas/assert/v2"
"golang.org/x/exp/maps"

"github.com/block/ftl/common/errors"
"github.com/block/ftl/common/slices"
Expand Down Expand Up @@ -1050,3 +1051,27 @@ func TestParseTypeMap(t *testing.T) {
actual = Normalise(actual)
assert.Equal(t, testSchema.Modules[4], actual, assert.Exclude[Position]())
}

func TestModuleDependencies(t *testing.T) {
input := `
// A comment
module a {
export verb m(Unit) Unit
+calls b.m
}
module b {
export verb m(Unit) Unit
+calls c.m
}
module c {
export verb m(Unit) Unit
}
`
actual, err := ParseString("", input)
assert.NoError(t, err)
actual = Normalise(actual)
assert.Equal(t, []string{"b", "c"}, maps.Keys(actual.ModuleDependencies("a")))
assert.Equal(t, []string{"c"}, maps.Keys(actual.ModuleDependencies("b")))
assert.Equal(t, []string{}, maps.Keys(actual.ModuleDependencies("c")))

}
6 changes: 6 additions & 0 deletions examples/java/time/src/main/java/ftl/time/Time.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ public class Time {
public TimeResponse time() {
return new TimeResponse(OffsetDateTime.now().plusDays(1));
}

@Verb
@Export
public TimeResponse time2() {
return new TimeResponse(OffsetDateTime.now().plusDays(1));
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion go-runtime/ftl/ftltest/testdata/go/pubsub/types.ftl.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion go-runtime/ftl/testdata/go/typeregistry/types.ftl.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 6 additions & 7 deletions go-runtime/goplugin/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,11 @@ func TestGoConfigDefaults(t *testing.T) {

func defaultsFromProto(proto *langpb.ModuleConfigDefaultsResponse) moduleconfig.CustomDefaults {
return moduleconfig.CustomDefaults{
DeployDir: proto.DeployDir,
Watch: proto.Watch,
Build: optional.Ptr(proto.Build),
DevModeBuild: optional.Ptr(proto.DevModeBuild),
GeneratedSchemaDir: optional.Ptr(proto.GeneratedSchemaDir),
LanguageConfig: proto.LanguageConfig.AsMap(),
SQLMigrationDir: proto.SqlMigrationDir,
DeployDir: proto.DeployDir,
Watch: proto.Watch,
Build: optional.Ptr(proto.Build),
DevModeBuild: optional.Ptr(proto.DevModeBuild),
LanguageConfig: proto.LanguageConfig.AsMap(),
SQLMigrationDir: proto.SqlMigrationDir,
}
}
2 changes: 1 addition & 1 deletion internal/buildengine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ func (e *Engine) buildWithCallback(ctx context.Context, callback buildCallback,
}

// Sync references to stubs if needed by the runtime
err = SyncStubReferences(ctx, e.projectConfig.Root(), moduleNames, metasMap)
err = SyncStubReferences(ctx, e.projectConfig.Root(), moduleNames, metasMap, &schema.Schema{Modules: maps.Values(builtModules)})
if err != nil {
return err
}
Expand Down
18 changes: 9 additions & 9 deletions internal/buildengine/languageplugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,13 @@ func (p *LanguagePlugin) ModuleConfigDefaults(ctx context.Context, dir string) (

func customDefaultsFromProto(proto *langpb.ModuleConfigDefaultsResponse) moduleconfig.CustomDefaults {
return moduleconfig.CustomDefaults{
DeployDir: proto.DeployDir,
Watch: proto.Watch,
Build: optional.Ptr(proto.Build),
DevModeBuild: optional.Ptr(proto.DevModeBuild),
BuildLock: optional.Ptr(proto.BuildLock),
GeneratedSchemaDir: optional.Ptr(proto.GeneratedSchemaDir),
LanguageConfig: proto.LanguageConfig.AsMap(),
SQLMigrationDir: proto.SqlMigrationDir,
DeployDir: proto.DeployDir,
Watch: proto.Watch,
Build: optional.Ptr(proto.Build),
DevModeBuild: optional.Ptr(proto.DevModeBuild),
BuildLock: optional.Ptr(proto.BuildLock),
LanguageConfig: proto.LanguageConfig.AsMap(),
SQLMigrationDir: proto.SqlMigrationDir,
}
}

Expand Down Expand Up @@ -300,7 +299,7 @@ func (p *LanguagePlugin) GenerateStubs(ctx context.Context, dir string, module *
// import the modules when users start reference them.
//
// It is optional to do anything with this call.
func (p *LanguagePlugin) SyncStubReferences(ctx context.Context, config moduleconfig.ModuleConfig, dir string, moduleNames []string) error {
func (p *LanguagePlugin) SyncStubReferences(ctx context.Context, config moduleconfig.ModuleConfig, dir string, moduleNames []string, view *schema.Schema) error {
configProto, err := langpb.ModuleConfigToProto(config.Abs())
if err != nil {
return fmt.Errorf("could not create proto for native module config: %w", err)
Expand All @@ -309,6 +308,7 @@ func (p *LanguagePlugin) SyncStubReferences(ctx context.Context, config moduleco
StubsRoot: dir,
Modules: moduleNames,
ModuleConfig: configProto,
Schema: view.ToProto(),
}))
if err != nil {
return fmt.Errorf("plugin failed to sync stub references: %w", err)
Expand Down
5 changes: 2 additions & 3 deletions internal/buildengine/stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,13 @@ func CleanStubs(ctx context.Context, projectRoot string) error {
// 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 {
func SyncStubReferences(ctx context.Context, projectRoot string, moduleNames []string, metas map[string]moduleMeta, view *schema.Schema) 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 {
if err := meta.plugin.SyncStubReferences(wgctx, meta.module.Config, stubsRoot, moduleNames, view); err != nil {
return err //nolint:wrapcheck
}
return nil
}
err := wg.Wait()
if err != nil {
Expand Down
11 changes: 5 additions & 6 deletions internal/moduleconfig/moduleconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,11 @@ type AbsModuleConfig ModuleConfig
type UnvalidatedModuleConfig ModuleConfig

type CustomDefaults struct {
DeployDir string
Watch []string
BuildLock optional.Option[string]
Build optional.Option[string]
DevModeBuild optional.Option[string]
GeneratedSchemaDir optional.Option[string]
DeployDir string
Watch []string
BuildLock optional.Option[string]
Build optional.Option[string]
DevModeBuild optional.Option[string]

// only the root keys in LanguageConfig are used to find missing values that can be defaulted
LanguageConfig map[string]any `toml:"-"`
Expand Down
20 changes: 9 additions & 11 deletions internal/moduleconfig/moduleconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ func TestDefaulting(t *testing.T) {
Language: "test",
},
defaults: CustomDefaults{
Build: optional.Some("build"),
DevModeBuild: optional.Some("devmodebuild"),
BuildLock: optional.Some("customdefaultlock"),
DeployDir: "deploydir",
GeneratedSchemaDir: optional.Some("generatedschemadir"),
Watch: []string{"a", "b", "c"},
Build: optional.Some("build"),
DevModeBuild: optional.Some("devmodebuild"),
BuildLock: optional.Some("customdefaultlock"),
DeployDir: "deploydir",
Watch: []string{"a", "b", "c"},
},
expected: ModuleConfig{
Realm: "home",
Expand Down Expand Up @@ -57,11 +56,10 @@ func TestDefaulting(t *testing.T) {
},
},
defaults: CustomDefaults{
Build: optional.Some("build"),
DevModeBuild: optional.Some("devmodebuild"),
DeployDir: "deploydir",
GeneratedSchemaDir: optional.Some("generatedschemadir"),
Watch: []string{"a", "b", "c"},
Build: optional.Some("build"),
DevModeBuild: optional.Some("devmodebuild"),
DeployDir: "deploydir",
Watch: []string{"a", "b", "c"},
},
expected: ModuleConfig{
Realm: "home",
Expand Down
14 changes: 6 additions & 8 deletions jvm-runtime/plugin/common/java_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ func TestJavaConfigDefaults(t *testing.T) {
language: "kotlin",
dir: "testdata/kotlin/echo",
expected: moduleconfig.CustomDefaults{
Build: optional.Some("mvn -B clean package"),
DevModeBuild: optional.Some("mvn clean quarkus:dev"),
DeployDir: "target",
GeneratedSchemaDir: optional.Some("src/main/ftl-module-schema"),
Build: optional.Some("mvn -B clean package"),
DevModeBuild: optional.Some("mvn clean quarkus:dev"),
DeployDir: "target",
LanguageConfig: map[string]any{
"build-tool": "maven",
},
Expand All @@ -50,10 +49,9 @@ func TestJavaConfigDefaults(t *testing.T) {
language: "kotlin",
dir: "testdata/kotlin/external",
expected: moduleconfig.CustomDefaults{
Build: optional.Some("mvn -B clean package"),
DevModeBuild: optional.Some("mvn clean quarkus:dev"),
DeployDir: "target",
GeneratedSchemaDir: optional.Some("src/main/ftl-module-schema"),
Build: optional.Some("mvn -B clean package"),
DevModeBuild: optional.Some("mvn clean quarkus:dev"),
DeployDir: "target",
LanguageConfig: map[string]any{
"build-tool": "maven",
},
Expand Down
48 changes: 33 additions & 15 deletions jvm-runtime/plugin/common/jvmcommon.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ func (s *Service) GenerateStubs(ctx context.Context, req *connect.Request[langpb
}

func (s *Service) SyncStubReferences(ctx context.Context, req *connect.Request[langpb.SyncStubReferencesRequest]) (*connect.Response[langpb.SyncStubReferencesResponse], error) {

sch, err := schema.FromProto(req.Msg.Schema)
if err != nil {
return nil, fmt.Errorf("failed to parse schema from proto: %w", err)
}
config := langpb.ModuleConfigFromProto(req.Msg.ModuleConfig)
if err != nil {
return nil, fmt.Errorf("failed to parse module config from proto: %w", err)
}
err = s.writeGenericSchemaFiles(ctx, sch, config)
if err != nil {
return nil, err
}
return connect.NewResponse(&langpb.SyncStubReferencesResponse{}), nil
}

Expand All @@ -165,14 +178,20 @@ func (s *Service) Build(ctx context.Context, req *connect.Request[langpb.BuildRe
if err != nil {
return err
}
err = s.writeGenericSchemaFiles(ctx, buildCtx)
err = s.writeGenericSchemaFiles(ctx, buildCtx.Schema, buildCtx.Config)
if err != nil {
return err
return fmt.Errorf("failed to write generic schema files: %w", err)
}
if req.Msg.RebuildAutomatically {
return s.runDevMode(ctx, req, buildCtx, stream)
}

if s.acceptsContextUpdates.Load() {
// Already running in dev mode, we don't need to rebuild
s.updatesTopic.Publish(buildContextUpdatedEvent{buildCtx: buildCtx})
return nil
}

// Initial build
if err := buildAndSend(ctx, stream, buildCtx, false); err != nil {
return err
Expand Down Expand Up @@ -522,10 +541,6 @@ func (s *Service) BuildContextUpdated(ctx context.Context, req *connect.Request[
if err != nil {
return nil, err
}
err = s.writeGenericSchemaFiles(ctx, buildCtx)
if err != nil {
return nil, err
}

s.updatesTopic.Publish(buildContextUpdatedEvent{
buildCtx: buildCtx,
Expand Down Expand Up @@ -585,10 +600,9 @@ func loadJavaConfig(languageConfig any, language string) (JavaConfig, error) {

func (s *Service) ModuleConfigDefaults(ctx context.Context, req *connect.Request[langpb.ModuleConfigDefaultsRequest]) (*connect.Response[langpb.ModuleConfigDefaultsResponse], error) {
defaults := langpb.ModuleConfigDefaultsResponse{
GeneratedSchemaDir: ptr("src/main/ftl-module-schema"),
LanguageConfig: &structpb.Struct{Fields: map[string]*structpb.Value{}},
Watch: []string{"pom.xml", "src/**", "build/generated", "target/generated-sources"},
SqlMigrationDir: "src/main/db",
LanguageConfig: &structpb.Struct{Fields: map[string]*structpb.Value{}},
Watch: []string{"pom.xml", "src/**", "build/generated", "target/generated-sources"},
SqlMigrationDir: "src/main/db",
}
dir := req.Msg.Dir
pom := filepath.Join(dir, "pom.xml")
Expand Down Expand Up @@ -803,17 +817,21 @@ func ptr(s string) *string {
return &s
}

func (s *Service) writeGenericSchemaFiles(ctx context.Context, buildContext buildContext) error {
func (s *Service) writeGenericSchemaFiles(ctx context.Context, v *schema.Schema, config moduleconfig.AbsModuleConfig) error {

modPath := filepath.Join(buildContext.Config.Dir, "src", "main", "ftl-module-schema")
logger := log.FromContext(ctx)
modPath := filepath.Join(config.Dir, "src", "main", "ftl-module-schema")
err := os.MkdirAll(modPath, 0750)
if err != nil {
return fmt.Errorf("failed to create directory %s: %w", modPath, err)
}
logger := log.FromContext(ctx)

for _, mod := range buildContext.Schema.Modules {
if mod.Name == buildContext.Config.Module {
for _, mod := range v.Modules {
if mod.Name == config.Module {
continue
}
deps := v.ModuleDependencies(mod.Name)
if deps[config.Module] != nil {
continue
}
data, err := schema.ModuleToBytes(mod)
Expand Down
Loading

0 comments on commit 851018f

Please sign in to comment.