Skip to content

Commit

Permalink
feat: make ftl build et al work with Go (#775)
Browse files Browse the repository at this point in the history
- `ftl-go` has been deleted.
- There must be a 1:1 mapping between FTL modules and Go modules.
- Updated `ftl init go` to generate a valid module.
- Updated all of the Go examples, including online-boutique, which can
now be trivially started with `ftl dev ./examples/online-boutique`
- The tooling is quite restrictive about code layout at the moment, but
we can come back to that later.

eg. Create, deploy and call a new module.

```
🐚 ~/dev/ftl $ ftl init go examples echo2
info: Initializing FTL Go module echo2 in examples
info: Running go mod tidy
🐚 ~/dev/ftl $ ftl deploy examples/echo2
info: Creating deployment for module echo2
info: Building Go module 'echo2'
info: Generating external modules
info: Extracting schema
info: Generating main module
info: Compiling
info: Uploading 1/1 files
info: Uploaded examples/echo2/_ftl/main as ba6f7f20ee5dac4e97197633348b5bb41e68e63c7e2d9b1da614d0a011b83872:main
info: Created deployment echo2-0a1cebbfcc
🐚 ~/dev/ftl $ ftl call echo2.echo
{"message":"Hello, anonymous!"}
```

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
alecthomas and github-actions[bot] committed Jan 14, 2024
1 parent ff04614 commit 0aa5428
Show file tree
Hide file tree
Showing 86 changed files with 1,128 additions and 1,268 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
target/
build/
deploy/
.ftl*/
.DS_Store
reflex.conf
/logs/
Expand All @@ -14,3 +13,5 @@ reflex.conf
generated_ftl_module.go
*.zip
dist/
examples/**/_ftl/
go.work*
8 changes: 8 additions & 0 deletions Bitfile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ go-runtime/scaffolding.zip: go-runtime/scaffolding/**/*
cd go-runtime/scaffolding
build: zip -q --symlinks -r ../scaffolding.zip .

go-runtime/compile/build-template.zip: go-runtime/compile/build-template/**/*
cd go-runtime/compile/build-template
build: zip -q --symlinks -r ../build-template.zip .

go-runtime/compile/external-module-template.zip: go-runtime/compile/external-module-template/**/*
cd go-runtime/compile/external-module-template
build: zip -q --symlinks -r ../external-module-template.zip .

kotlin-runtime/scaffolding.zip: kotlin-runtime/scaffolding/**/*
cd kotlin-runtime/scaffolding
build: zip -q --symlinks -r ../scaffolding.zip .
Expand Down
4 changes: 2 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ install-runtime-jar:

# Deploy the Go time module
deploy-time:
cd examples && ftl-go deploy time
ftl deploy examples/time

# Deploy the Kotlin echo module
deploy-echo-kotlin:
cd examples/echo-kotlin && mvn compile && ftl deploy target
ftl deploy examples/echo-kotlin

regen-schema:
bit protos/xyz/block/ftl/v1/schema/schema.proto
Expand Down
54 changes: 47 additions & 7 deletions backend/common/moduleconfig/config.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package moduleconfig

import (
"fmt"
"path/filepath"
"strings"

"github.com/BurntSushi/toml"
)

// GoConfig is language-specific configuration for Go modules.
type GoConfig struct {
}

// KotlinConfig is language-specific configuration for Kotlin modules.
type KotlinConfig struct {
}

// ModuleConfig is the configuration for an FTL module.
//
// Module config files are currently TOML.
Expand All @@ -17,6 +27,9 @@ type ModuleConfig struct {
DeployDir string `toml:"deploy-dir"`
Schema string `toml:"schema"`
Watch []string `toml:"watch"`

Go GoConfig `toml:"go,optional"`
Kotlin KotlinConfig `toml:"kotlin,optional"`
}

// LoadConfig from a directory.
Expand All @@ -27,11 +40,16 @@ func LoadConfig(dir string) (ModuleConfig, error) {
if err != nil {
return ModuleConfig{}, err
}
setConfigDefaults(&config)
if err := setConfigDefaults(dir, &config); err != nil {
return config, fmt.Errorf("%s: %w", path, err)
}
return config, nil
}

func setConfigDefaults(config *ModuleConfig) {
func setConfigDefaults(moduleDir string, config *ModuleConfig) error {
if config.Schema == "" {
config.Schema = "schema.pb"
}
switch config.Language {
case "kotlin":
if config.Build == "" {
Expand All @@ -43,18 +61,40 @@ func setConfigDefaults(config *ModuleConfig) {
if len(config.Deploy) == 0 {
config.Deploy = []string{"main", "classes", "dependency", "classpath.txt"}
}
if config.Schema == "" {
config.Schema = "schema.pb"
}
if len(config.Watch) == 0 {
config.Watch = []string{"pom.xml", "src/**", "target/generated-sources"}
}

case "go":
if config.DeployDir == "" {
config.DeployDir = "build"
config.DeployDir = "_ftl"
}
if len(config.Deploy) == 0 {
config.Deploy = []string{"main", "schema.pb"}
config.Deploy = []string{"main"}
}
if len(config.Watch) == 0 {
config.Watch = []string{"**/*.go", "go.mod", "go.sum"}
}
}

// Do some validation.
if !isBeneath(moduleDir, config.DeployDir) {
return fmt.Errorf("deploy-dir must be relative to the module directory")
}
for _, deploy := range config.Deploy {
if !isBeneath(moduleDir, deploy) {
return fmt.Errorf("deploy files must be relative to the module directory")
}
}
for _, watch := range config.Watch {
if !isBeneath(moduleDir, watch) {
return fmt.Errorf("watch files must be relative to the module directory")
}
}
return nil
}

func isBeneath(moduleDir, path string) bool {
resolved := filepath.Clean(filepath.Join(moduleDir, path))
return strings.HasPrefix(resolved, strings.TrimSuffix(moduleDir, "/")+"/")
}
18 changes: 13 additions & 5 deletions backend/schema/builtin.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package schema

import (
"golang.design/x/reflect"
)

// BuiltinsSource is the schema source code for built-in types.
var BuiltinsSource = `
const BuiltinsSource = `
// Built-in types for FTL.
builtin module builtin {
// HTTP request structure used for HTTP ingress verbs.
Expand All @@ -23,11 +27,15 @@ builtin module builtin {
}
`

// Builtins returns a [Module] containing built-in types.
func Builtins() *Module {
module, err := ParseModuleString("builtins.ftl", BuiltinsSource)
var builtinsModuleParsed = func() *Module {
module, err := moduleParser.ParseString("", BuiltinsSource)
if err != nil {
panic("failed to parse builtins: " + err.Error())
panic(err)
}
return module
}()

// Builtins returns a [Module] containing built-in types.
func Builtins() *Module {
return reflect.DeepCopy(builtinsModuleParsed)
}
6 changes: 3 additions & 3 deletions backend/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ func TestParsing(t *testing.T) {
{name: "InvalidRequestRef",
input: `module test { verb test(InvalidRequest) InvalidResponse}`,
errors: []string{
"1:25: reference to unknown data structure \"InvalidRequest\"",
"1:41: reference to unknown data structure \"InvalidResponse\""}},
"1:25: unqualified reference to unknown data structure \"InvalidRequest\"",
"1:41: unqualified reference to unknown data structure \"InvalidResponse\""}},
{name: "InvalidDataRef",
input: `module test { data Data { user user.User }}`,
errors: []string{
Expand All @@ -234,7 +234,7 @@ func TestParsing(t *testing.T) {
input: `module test { data Data {} calls verb }`,
errors: []string{
"1:28: metadata \"calls verb\" is not valid on data structures",
"1:34: reference to unknown Verb \"verb\"",
"1:34: unqualified reference to unknown Verb \"verb\"",
}},
{name: "KeywordAsName",
input: `module int { data String { name String } verb verb(String) String }`,
Expand Down
Loading

0 comments on commit 0aa5428

Please sign in to comment.