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: send build events over grpc and new lsp cmd #3953

Merged
merged 5 commits into from
Jan 10, 2025
Merged
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
1,138 changes: 1,138 additions & 0 deletions backend/protos/xyz/block/ftl/buildengine/v1/buildengine.pb.go

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions backend/protos/xyz/block/ftl/buildengine/v1/buildengine.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
syntax = "proto3";

package xyz.block.ftl.buildengine.v1;

import "xyz/block/ftl/language/v1/language.proto";
import "xyz/block/ftl/v1/ftl.proto";

option go_package = "github.com/block/ftl/backend/protos/xyz/block/ftl/buildengine/v1;buildenginepb";

// EngineStarted is published when the engine becomes busy building and deploying modules.
message EngineStarted {}

// EngineEnded is published when the engine is no longer building or deploying any modules.
// If there are any remaining errors, they will be included in the ModuleErrors map.
message EngineEnded {
map<string, xyz.block.ftl.language.v1.ErrorList> module_errors = 1; // module name -> error
}

// ModuleAdded is published when the engine discovers a module.
message ModuleAdded {
string module = 1;
}

// ModuleRemoved is published when the engine discovers a module has been removed.
message ModuleRemoved {
string module = 1;
}

// ModuleBuildWaiting is published when a build is waiting for dependencies to build
message ModuleBuildWaiting {
xyz.block.ftl.language.v1.ModuleConfig config = 1;
}

// ModuleBuildStarted is published when a build has started for a module.
message ModuleBuildStarted {
xyz.block.ftl.language.v1.ModuleConfig config = 1;
bool is_auto_rebuild = 2;
}

// ModuleBuildFailed is published for any build failures.
message ModuleBuildFailed {
xyz.block.ftl.language.v1.ModuleConfig config = 1;
xyz.block.ftl.language.v1.ErrorList errors = 2;
bool is_auto_rebuild = 3;
}

// ModuleBuildSuccess is published when all modules have been built successfully built.
message ModuleBuildSuccess {
xyz.block.ftl.language.v1.ModuleConfig config = 1;
bool is_auto_rebuild = 2;
}

// ModuleDeployStarted is published when a deploy has begun for a module.
message ModuleDeployStarted {
string module = 1;
}

// ModuleDeployFailed is published for any deploy failures.
message ModuleDeployFailed {
string module = 1;
xyz.block.ftl.language.v1.ErrorList errors = 2;
}

// ModuleDeploySuccess is published when all modules have been built successfully deployed.
message ModuleDeploySuccess {
string module = 1;
}

// EngineEvent is an event published by the engine as modules get built and deployed.
message EngineEvent {
oneof event {
EngineStarted engine_started = 1;
EngineEnded engine_ended = 2;
ModuleAdded module_added = 3;
ModuleRemoved module_removed = 4;
ModuleBuildWaiting module_build_waiting = 5;
ModuleBuildStarted module_build_started = 6;
ModuleBuildFailed module_build_failed = 7;
ModuleBuildSuccess module_build_success = 8;
ModuleDeployStarted module_deploy_started = 9;
ModuleDeployFailed module_deploy_failed = 10;
ModuleDeploySuccess module_deploy_success = 11;
}
}

message StreamEngineEventsRequest {}

message StreamEngineEventsResponse {
EngineEvent event = 1;
}

service BuildEngineService {
// Ping service for readiness.
rpc Ping(ftl.v1.PingRequest) returns (ftl.v1.PingResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}

rpc StreamEngineEvents(StreamEngineEventsRequest) returns (stream StreamEngineEventsResponse) {}
}

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

36 changes: 36 additions & 0 deletions backend/protos/xyz/block/ftl/language/v1/mixins.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package languagepb

import (
"fmt"
"strings"

"github.com/alecthomas/types/optional"
"google.golang.org/protobuf/types/known/structpb"
Expand All @@ -24,6 +25,41 @@ func ErrorsToProto(errs []builderrors.Error) *ErrorList {
return &ErrorList{Errors: slices.Map(errs, errorToProto)}
}

// ErrorString formats a languagepb.Error with its position (if available) and message
func ErrorString(err *Error) string {
if err.Pos != nil {
return fmt.Sprintf("%s: %s", positionString(err.Pos), err.Msg)
}
return err.Msg
}

// ErrorListString formats all errors in a languagepb.ErrorList, one per line
func ErrorListString(errList *ErrorList) string {
if errList == nil || len(errList.Errors) == 0 {
return ""
}
result := make([]string, len(errList.Errors))
for i, err := range errList.Errors {
result[i] = ErrorString(err)
}
return strings.Join(result, "\n")
}

// positionString formats a languagepb.Position similar to builderrors.Position
func positionString(pos *Position) string {
if pos == nil {
return ""
}
columnStr := fmt.Sprintf("%d", pos.StartColumn)
if pos.StartColumn != pos.EndColumn {
columnStr += fmt.Sprintf("-%d", pos.EndColumn)
}
if pos.Filename == "" {
return fmt.Sprintf("%d:%s", pos.Line, columnStr)
}
return fmt.Sprintf("%s:%d:%s", pos.Filename, pos.Line, columnStr)
}

func levelFromProto(level Error_ErrorLevel) builderrors.ErrorLevel {
switch level {
case Error_ERROR_LEVEL_INFO:
Expand Down
3 changes: 2 additions & 1 deletion common/builderrors/builderrors.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ func (p Position) String() string {
type ErrorLevel int

const (
INFO ErrorLevel = iota
UNDEFINED ErrorLevel = iota
INFO
WARN
ERROR
)
Expand Down
9 changes: 6 additions & 3 deletions frontend/cli/cmd_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net/url"

"github.com/block/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect"
"github.com/block/ftl/internal/buildengine"
Expand All @@ -12,9 +13,10 @@ import (
)

type buildCmd struct {
Parallelism int `short:"j" help:"Number of modules to build in parallel." default:"${numcpu}"`
Dirs []string `arg:"" help:"Base directories containing modules (defaults to modules in project config)." type:"existingdir" optional:""`
BuildEnv []string `help:"Environment variables to set for the build."`
Parallelism int `short:"j" help:"Number of modules to build in parallel." default:"${numcpu}"`
Dirs []string `arg:"" help:"Base directories containing modules (defaults to modules in project config)." type:"existingdir" optional:""`
BuildEnv []string `help:"Environment variables to set for the build."`
UpdatesEndpoint *url.URL `help:"Socket to bind to." default:"http://127.0.0.1:8900" env:"FTL_BUILD_UPDATES_ENDPOINT"`
}

func (b *buildCmd) Run(
Expand All @@ -39,6 +41,7 @@ func (b *buildCmd) Run(
schemaSourceFactory(),
projConfig,
b.Dirs,
b.UpdatesEndpoint,
buildengine.BuildEnv(b.BuildEnv),
buildengine.Parallelism(b.Parallelism),
)
Expand Down
2 changes: 1 addition & 1 deletion frontend/cli/cmd_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (d *deployCmd) Run(
}
defer cancel()
engine, err := buildengine.New(
ctx, provisionerClient, schemaSourceFactory(), projConfig, d.Build.Dirs,
ctx, provisionerClient, schemaSourceFactory(), projConfig, d.Build.Dirs, d.Build.UpdatesEndpoint,
buildengine.BuildEnv(d.Build.BuildEnv),
buildengine.Parallelism(d.Build.Parallelism),
)
Expand Down
25 changes: 6 additions & 19 deletions frontend/cli/cmd_dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"github.com/block/ftl/internal/configuration/manager"
"github.com/block/ftl/internal/dev"
"github.com/block/ftl/internal/log"
"github.com/block/ftl/internal/lsp"
"github.com/block/ftl/internal/projectconfig"
"github.com/block/ftl/internal/rpc"
"github.com/block/ftl/internal/schema/schemaeventsource"
Expand All @@ -29,12 +28,10 @@ import (
)

type devCmd struct {
Watch time.Duration `help:"Watch template directory at this frequency and regenerate on change." default:"500ms"`
NoServe bool `help:"Do not start the FTL server." default:"false"`
Lsp bool `help:"Run the language server." default:"false"`
ServeCmd serveCommonConfig `embed:""`
languageServer *lsp.Server
Build buildCmd `embed:""`
Watch time.Duration `help:"Watch template directory at this frequency and regenerate on change." default:"500ms"`
NoServe bool `help:"Do not start the FTL server." default:"false"`
ServeCmd serveCommonConfig `embed:""`
Build buildCmd `embed:""`
}

func (d *devCmd) Run(
Expand Down Expand Up @@ -115,21 +112,11 @@ func (d *devCmd) Run(
starting.Close()

opts := []buildengine.Option{buildengine.Parallelism(d.Build.Parallelism), buildengine.BuildEnv(d.Build.BuildEnv), buildengine.WithDevMode(devModeEndpointUpdates), buildengine.WithStartTime(startTime)}
if d.Lsp {
d.languageServer = lsp.NewServer(ctx)
ctx = log.ContextWithLogger(ctx, log.FromContext(ctx).AddSink(lsp.NewLogSink(d.languageServer)))
g.Go(func() error {
return d.languageServer.Run()
})
}

engine, err := buildengine.New(ctx, client, schemaEventSourceFactory(), projConfig, d.Build.Dirs, opts...)
engine, err := buildengine.New(ctx, client, schemaEventSourceFactory(), projConfig, d.Build.Dirs, d.Build.UpdatesEndpoint, opts...)
if err != nil {
return err
}
if d.languageServer != nil {
d.languageServer.Subscribe(ctx, engine.EngineUpdates)
}

return engine.Dev(ctx, d.Watch)
})

Expand Down
Loading
Loading