Skip to content

Commit

Permalink
feat: add "ftl init" with an initial scaffolder for Go (#491)
Browse files Browse the repository at this point in the history
Will add Kotlin in a followup.

This currently works, but `ftl-go deploy` needs to be adjusted to
support go.mod in the same directory as ftl.toml.

```
ftl init go ./examples/foo --go-module=github.com/TBD54566975/ftl/examples/foo
```
  • Loading branch information
alecthomas authored Oct 13, 2023
1 parent c43ad68 commit e689666
Show file tree
Hide file tree
Showing 25 changed files with 323 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ reflex.conf
/node_modules
*.tsbuildinfo
generated_ftl_module.go

scaffolding.zip
dist/
25 changes: 20 additions & 5 deletions Bitfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,27 @@ NODE_MODULES_IN = console/client/package{,-lock}.json
build: install -m 0700 -d %{OUT}

# Build all binaries
implicit %{RELEASE}/%{1}: cmd/*
inputs: %{RELEASE} %{GO_SOURCES} %{CLIENT_OUT}
build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION} -X main.timestamp=$(date +%s)" ./cmd/%{1}
# implicit %{RELEASE}/%{1}: cmd/*
# inputs: %{RELEASE} %{GO_SOURCES} %{CLIENT_OUT}
# build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION} -X main.timestamp=$(date +%s)" ./cmd/%{1}

#%{RELEASE}/ftl-controller: %{RELEASE} %{GO_SOURCES} %{CLIENT_OUT}
# build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION} -X main.timestamp=$(date +%s)" ./cmd/ftl-controller
%{RELEASE}/ftl-controller: %{RELEASE} %{GO_SOURCES} %{CLIENT_OUT} cmd/ftl-controller/**/*.go
build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION} -X main.timestamp=$(date +%s)" ./cmd/ftl-controller

%{RELEASE}/ftl-runner: %{RELEASE} %{GO_SOURCES} cmd/ftl-runner/**/*.go
build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION} -X main.timestamp=$(date +%s)" ./cmd/ftl-runner

%{RELEASE}/ftl: %{RELEASE} %{GO_SOURCES} cmd/ftl/**/*.go go-runtime/scaffolding.zip
build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION} -X main.timestamp=$(date +%s)" ./cmd/ftl

%{RELEASE}/ftl-initdb: %{RELEASE} %{GO_SOURCES} cmd/ftl-initdb/**/*.go
build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION} -X main.timestamp=$(date +%s)" ./cmd/ftl-initdb

# Release builds include zipped scaffolding becaused raw go:embed doesn't
# preserve permissions or symlinks. Irritating.
go-runtime/scaffolding.zip: go-runtime/scaffolding/*
cd go-runtime/scaffolding
build: zip -q --symlinks -r ../scaffolding.zip .

%{SCHEMA_OUT}: %{SCHEMA_IN}
build:
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ release: build/release/ftl-controller build/release/ftl-runner build/release/ftl
build/release/%: console/client/dist/index.html
go build -o $@ -tags release -ldflags "-X main.version=$(VERSION) -X main.timestamp=$(shell date +%s)" ./cmd/$(shell basename $@)

build/release/ftl:
cd go-runtime/scaffolding && zip -q --symlinks -r ../scaffolding.zip .
go build -o $@ -tags release -ldflags "-X main.version=$(VERSION) -X main.timestamp=$(shell date +%s)" ./cmd/ftl

$(KT_MVN_OUT): $(KT_RUNTIME_IN)
mvn -pl :ftl-runtime clean package

Expand Down
52 changes: 52 additions & 0 deletions cmd/ftl/cmd_init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"context"
"os"
"path/filepath"

"github.com/alecthomas/errors"

"github.com/TBD54566975/ftl/backend/common/exec"
"github.com/TBD54566975/ftl/backend/common/log"
goruntime "github.com/TBD54566975/ftl/go-runtime"
"github.com/TBD54566975/ftl/internal"
)

type initCmd struct {
Hermit bool `default:"true" help:"Include Hermit language-specific toolchain binaries in the module." negatable:""`
Go initGoCmd `cmd:"" help:"Initialize a new FTL Go module."`
Kotlin initKotlinCmd `cmd:"" help:"Initialize a new FTL Kotlin module."`
}

type initGoCmd struct {
Dir string `arg:"" default:"." type:"dir" help:"Directory to initialize the module in."`
Name string `help:"Name of the FTL module (defaults to name of directory)."`
GoModule string `required:"" help:"Go module path."`
}

func (i initGoCmd) Run(ctx context.Context, parent *initCmd) error {
if i.Name == "" {
i.Name = filepath.Base(i.Dir)
}
if err := internal.Scaffold(goruntime.Files, i.Dir, i); err != nil {
return errors.WithStack(err)
}
if !parent.Hermit {
if err := os.RemoveAll(filepath.Join(i.Dir, "bin")); err != nil {
return errors.WithStack(err)
}
}
if err := exec.Command(ctx, log.Info, i.Dir, "go", "mod", "tidy").Run(); err != nil {
return errors.WithStack(err)
}
return nil
}

type initKotlinCmd struct {
Dir string `arg:"" default:"." help:"Directory to initialize the module in."`
}

func (i *initKotlinCmd) Run() error {
panic("??")
}
1 change: 1 addition & 0 deletions cmd/ftl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type CLI struct {
Authenticators map[string]string `help:"Authenticators to use for FTL endpoints." mapsep:"," env:"FTL_AUTHENTICATORS" placeholder:"HOST=EXE,…"`

Status statusCmd `cmd:"" help:"Show FTL status."`
Init initCmd `cmd:"" help:"Initialize a new FTL module."`
PS psCmd `cmd:"" help:"List deployments."`
Serve serveCmd `cmd:"" help:"Start the FTL server."`
Call callCmd `cmd:"" help:"Call an FTL function."`
Expand Down
18 changes: 0 additions & 18 deletions examples/echo/_ftl/main.go

This file was deleted.

3 changes: 2 additions & 1 deletion examples/go.sum

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

3 changes: 2 additions & 1 deletion examples/online-boutique/go.sum

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

18 changes: 0 additions & 18 deletions examples/time/_ftl/main.go

This file was deleted.

22 changes: 22 additions & 0 deletions go-runtime/devel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//go:build !release

package goruntime

import (
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
)

// Files is the FTL Go runtime scaffolding files.
var Files = func() fs.FS {
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
out, err := cmd.CombinedOutput()
if err != nil {
panic(err)
}
dir := filepath.Join(strings.TrimSpace(string(out)), "go-runtime", "scaffolding")
return os.DirFS(dir)
}()
25 changes: 25 additions & 0 deletions go-runtime/release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build release

package goruntime

import (
"archive/zip"
"bytes"
_ "embed"
"io/fs"
)

//go:embed scaffolding.zip
var archive []byte

// Files is the FTL Go runtime scaffolding files.
//
// scaffolding.zip can be generated by running `bit go-runtime/scaffolding.zip`
// or indirectly via `bit build/release/ftl`.
var Files fs.FS = func() fs.FS {
zr, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive)))
if err != nil {
panic(err)
}
return zr
}()
1 change: 1 addition & 0 deletions go-runtime/scaffolding/bin/.go-1.21.3.pkg
7 changes: 7 additions & 0 deletions go-runtime/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 go-runtime/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 go-runtime/scaffolding/bin/go
1 change: 1 addition & 0 deletions go-runtime/scaffolding/bin/gofmt
43 changes: 43 additions & 0 deletions go-runtime/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.
2 changes: 2 additions & 0 deletions go-runtime/scaffolding/ftl.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module = "{{ .Name | lower }}"
language = "go"
5 changes: 5 additions & 0 deletions go-runtime/scaffolding/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module {{ .GoModule }}

go 1.21.0

require github.com/TBD54566975/ftl latest
18 changes: 18 additions & 0 deletions go-runtime/scaffolding/{{ .Name | lower }}.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//ftl:module {{ .Name | lower }}
package {{ .Name | lower }}

import (
"context"
_ "github.com/TBD54566975/ftl/go-runtime/sdk" // Import the FTL SDK.
)

type {{ .Name | camel }}Request struct {
}

type {{ .Name | camel }}Response struct {
}

//ftl:verb
func {{ .Name | camel }}(ctx context.Context, req {{ .Name | camel }}Request) ({{ .Name | camel }}Response, error) {
return {{ .Name | camel }}Response{}, nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/swaggest/refl v1.2.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum

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

Loading

0 comments on commit e689666

Please sign in to comment.