Skip to content

Commit

Permalink
Introduce experiments API
Browse files Browse the repository at this point in the history
  • Loading branch information
eandre committed Nov 4, 2022
1 parent 8486173 commit d172ad9
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 44 deletions.
15 changes: 15 additions & 0 deletions cli/daemon/apps/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"encr.dev/cli/daemon/internal/manifest"
"encr.dev/cli/internal/appfile"
"encr.dev/internal/experiments"
)

var ErrNotFound = errors.New("app not found")
Expand Down Expand Up @@ -279,6 +280,20 @@ func (i *Instance) PlatformOrLocalID() string {
return i.localID
}

// Experiments returns the enabled experiments for this app.
//
// Note: we read the app file here instead of a cached value so we
// can detect changes between runs of the compiler if we're in
// watch mode.
func (i *Instance) Experiments(environ []string) (*experiments.Set, error) {
exp, err := appfile.Experiments(i.root)
if err != nil {
return nil, err
}

return experiments.NewSet(exp, environ)
}

func (i *Instance) Watch(fn WatchFunc) error {
if err := i.beginWatch(); err != nil {
return err
Expand Down
14 changes: 14 additions & 0 deletions cli/daemon/export/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import (
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/rs/zerolog"

"encr.dev/cli/internal/appfile"
"encr.dev/compiler"
"encr.dev/internal/env"
"encr.dev/internal/experiments"
"encr.dev/internal/version"
"encr.dev/pkg/cueutil"
"encr.dev/pkg/vcs"
Expand All @@ -41,7 +43,18 @@ func Docker(ctx context.Context, req *daemonpb.ExportRequest, log zerolog.Logger
return false, errors.Newf("unsupported format: %T", req.Format)
}

exp, err := appfile.Experiments(req.AppRoot)
if err != nil {
return false, errors.Wrap(err, "check experimental features")
}

expSet, err := experiments.NewSet(exp, nil)
if err != nil {
return false, errors.Wrap(err, "get experiments")
}

vcsRevision := vcs.GetRevision(req.AppRoot)

cfg := &compiler.Config{
Revision: vcsRevision.Revision,
UncommittedChanges: vcsRevision.Uncommitted,
Expand All @@ -55,6 +68,7 @@ func Docker(ctx context.Context, req *daemonpb.ExportRequest, log zerolog.Logger
GOOS: req.Goos,
GOARCH: req.Goarch,
KeepOutput: false,
Experiments: expSet,
Meta: &cueutil.Meta{
// Dummy data to satisfy config validation.
APIBaseURL: "http://localhost:0",
Expand Down
19 changes: 19 additions & 0 deletions cli/daemon/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"encr.dev/cli/daemon/sqldb"
"encr.dev/cli/internal/appfile"
"encr.dev/cli/internal/onboarding"
"encr.dev/internal/experiments"
"encr.dev/internal/optracker"
"encr.dev/internal/version"
"encr.dev/parser"
Expand Down Expand Up @@ -148,6 +149,14 @@ func (s *Server) Run(req *daemonpb.RunRequest, stream daemonpb.Daemon_RunServer)
if req.Debug {
fmt.Fprintf(stderr, " Process ID: %d\n", aurora.Cyan(pid))
}
// Log which experiments are enabled, if any
if exp := run.Proc().Experiments.List(); len(exp) > 0 {
strs := make([]string, len(exp))
for i, e := range exp {
strs[i] = string(e)
}
fmt.Fprintf(stderr, " Enabled experiment(s): %s\n", aurora.Yellow(strings.Join(strs, ", ")))
}

// If there's a newer version available, print a message.
if newVer != nil {
Expand Down Expand Up @@ -381,10 +390,20 @@ func (s *Server) parseApp(appRoot, workingDir string, parseTests bool) (*parser.
return nil, err
}

exp, err := appfile.Experiments(appRoot)
if err != nil {
return nil, err
}
expSet, err := experiments.NewSet(exp, nil)
if err != nil {
return nil, err
}

vcsRevision := vcs.GetRevision(appRoot)

cfg := &parser.Config{
AppRoot: appRoot,
Experiments: expSet,
AppRevision: vcsRevision.Revision,
AppHasUncommittedChanges: vcsRevision.Uncommitted,
ModulePath: mod.Module.Mod.Path,
Expand Down
45 changes: 31 additions & 14 deletions cli/daemon/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"encr.dev/cli/internal/xos"
"encr.dev/compiler"
"encr.dev/internal/env"
"encr.dev/internal/experiments"
"encr.dev/internal/optracker"
"encr.dev/internal/version"
"encr.dev/parser"
Expand Down Expand Up @@ -244,8 +245,14 @@ func (r *Run) parseApp() (*parser.Result, error) {

vcsRevision := vcs.GetRevision(r.App.Root())

experiments, err := r.App.Experiments(r.params.Environ)
if err != nil {
return nil, err
}

cfg := &parser.Config{
AppRoot: r.App.Root(),
Experiments: experiments,
AppRevision: vcsRevision.Revision,
AppHasUncommittedChanges: vcsRevision.Uncommitted,
ModulePath: mod.Module.Mod.Path,
Expand Down Expand Up @@ -280,6 +287,11 @@ func (r *Run) buildAndStart(ctx context.Context, tracker *optracker.OpTracker) e
tracker.Done(parseOp, 500*time.Millisecond)
tracker.Done(topoOp, 300*time.Millisecond)

expSet, err := r.App.Experiments(r.params.Environ)
if err != nil {
return err
}

if err := r.ResourceServers.StartRequiredServices(jobs, parse); err != nil {
return err
}
Expand All @@ -295,6 +307,7 @@ func (r *Run) buildAndStart(ctx context.Context, tracker *optracker.OpTracker) e
EncoreCompilerVersion: fmt.Sprintf("EncoreCLI/%s", version.Version),
EncoreRuntimePath: env.EncoreRuntimePath(),
EncoreGoRoot: env.EncoreGoRoot(),
Experiments: expSet,
Meta: &cueutil.Meta{
APIBaseURL: fmt.Sprintf("http://%s", r.ListenAddr),
EnvName: "local",
Expand Down Expand Up @@ -352,6 +365,7 @@ func (r *Run) buildAndStart(ctx context.Context, tracker *optracker.OpTracker) e
Secrets: secrets,
ServiceConfigs: build.Configs,
Environ: r.params.Environ,
Experiments: expSet,
})
if err != nil {
tracker.Fail(startOp, err)
Expand All @@ -374,11 +388,12 @@ func (r *Run) buildAndStart(ctx context.Context, tracker *optracker.OpTracker) e

// Proc represents a running Encore process.
type Proc struct {
ID string // unique process id
Run *Run // the run the process belongs to
Pid int // the OS process id
Meta *meta.Data // app metadata snapshot
Started time.Time // when the process started
ID string // unique process id
Run *Run // the run the process belongs to
Pid int // the OS process id
Meta *meta.Data // app metadata snapshot
Started time.Time // when the process started
Experiments *experiments.Set // enabled experiments

ctx context.Context
log zerolog.Logger
Expand Down Expand Up @@ -409,22 +424,24 @@ type StartProcParams struct {
Redis *redis.Server // nil means no redis
Logger RunLogger
Environ []string
Experiments *experiments.Set
}

// StartProc starts a single actual OS process for app.
func (r *Run) StartProc(params *StartProcParams) (p *Proc, err error) {
pid := GenID()
authKey := genAuthKey()
p = &Proc{
ID: pid,
Run: r,
Meta: params.Meta,
ctx: params.Ctx,
exit: make(chan struct{}),
buildDir: params.BuildDir,
log: r.log.With().Str("proc_id", pid).Str("build_dir", params.BuildDir).Logger(),
symParsed: make(chan struct{}),
authKey: authKey,
ID: pid,
Run: r,
Experiments: params.Experiments,
Meta: params.Meta,
ctx: params.Ctx,
exit: make(chan struct{}),
buildDir: params.BuildDir,
log: r.log.With().Str("proc_id", pid).Str("build_dir", params.BuildDir).Logger(),
symParsed: make(chan struct{}),
authKey: authKey,
}
go p.parseSymTable(params.BinPath)

Expand Down
18 changes: 18 additions & 0 deletions cli/daemon/run/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import (
"encore.dev/appruntime/config"
"encr.dev/cli/daemon/apps"
"encr.dev/cli/daemon/sqldb"
"encr.dev/cli/internal/appfile"
"encr.dev/compiler"
"encr.dev/internal/env"
"encr.dev/internal/experiments"
"encr.dev/internal/version"
"encr.dev/parser"
"encr.dev/pkg/cueutil"
Expand All @@ -29,6 +31,15 @@ import (
func (mgr *Manager) Check(ctx context.Context, appRoot, relwd string, codegenDebug bool) (buildDir string, err error) {
vcsRevision := vcs.GetRevision(appRoot)

exp, err := appfile.Experiments(appRoot)
if err != nil {
return "", err
}
expSet, err := experiments.NewSet(exp, nil)
if err != nil {
return "", err
}

// TODO: We should check that all secret keys are defined as well.
cfg := &compiler.Config{
Revision: vcsRevision.Revision,
Expand All @@ -40,6 +51,7 @@ func (mgr *Manager) Check(ctx context.Context, appRoot, relwd string, codegenDeb
EncoreGoRoot: env.EncoreGoRoot(),
KeepOutput: codegenDebug,
BuildTags: []string{"encore_local", "encore_no_gcp", "encore_no_aws", "encore_no_azure"},
Experiments: expSet,
Meta: &cueutil.Meta{
// Dummy data to satisfy config validation.
APIBaseURL: "http://localhost:0",
Expand Down Expand Up @@ -147,6 +159,11 @@ func (mgr *Manager) Test(ctx context.Context, params TestParams) (err error) {
return err
}

experiments, err := params.App.Experiments(params.Environ)
if err != nil {
return err
}

cfg := &compiler.Config{
Parse: params.Parse,
Revision: params.Parse.Meta.AppRevision,
Expand All @@ -157,6 +174,7 @@ func (mgr *Manager) Test(ctx context.Context, params TestParams) (err error) {
EncoreRuntimePath: env.EncoreRuntimePath(),
EncoreGoRoot: env.EncoreGoRoot(),
BuildTags: []string{"encore_local", "encore_no_gcp", "encore_no_aws", "encore_no_azure"},
Experiments: experiments,
Meta: &cueutil.Meta{
APIBaseURL: apiBaseURL,
EnvName: "local",
Expand Down
19 changes: 19 additions & 0 deletions cli/internal/appfile/appfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"path/filepath"

"github.com/tailscale/hujson"

"encr.dev/internal/experiments"
)

// Name is the name of the Encore app file.
Expand All @@ -22,6 +24,13 @@ type File struct {
// ID is the encore.dev app id for the app.
// It is empty if the app is not linked to encore.dev.
ID string `json:"id"` // can be empty

// Experiments is a list of values to enable experimental features in Encore.
// These are not guaranteed to be stable in either runtime behaviour
// or in API design.
//
// Do not use these features in production without consulting the Encore team.
Experiments []experiments.Name `json:"experiments,omitempty"`
}

// Parse parses the app file data into a File.
Expand Down Expand Up @@ -57,3 +66,13 @@ func Slug(appRoot string) (string, error) {
}
return f.ID, nil
}

// Experiments returns the experimental feature the app located
// at appRoot has opted into.
func Experiments(appRoot string) ([]experiments.Name, error) {
f, err := ParseFile(filepath.Join(appRoot, Name))
if err != nil {
return nil, err
}
return f.Experiments, nil
}
21 changes: 0 additions & 21 deletions cli/internal/experiment/experiment.go

This file was deleted.

7 changes: 6 additions & 1 deletion compiler/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"encr.dev/compiler/internal/codegen"
"encr.dev/compiler/internal/cuegen"
"encr.dev/internal/experiments"
"encr.dev/internal/optracker"
"encr.dev/parser"
"encr.dev/parser/est"
Expand Down Expand Up @@ -89,6 +90,9 @@ type Config struct {

// OpTracker is an option tracker to output the progress to the UI
OpTracker *optracker.OpTracker

// Are experimental features of Encore switched on?
Experiments *experiments.Set
}

// Validate validates the config.
Expand Down Expand Up @@ -257,6 +261,7 @@ func (b *builder) parseApp() error {

cfg := &parser.Config{
AppRoot: b.appRoot,
Experiments: b.cfg.Experiments,
AppRevision: b.cfg.Revision,
AppHasUncommittedChanges: b.cfg.UncommittedChanges,
ModulePath: b.modfile.Module.Mod.Path,
Expand Down Expand Up @@ -409,7 +414,7 @@ func (b *builder) buildMain() error {
}
if b.cfg.StaticLink {
var ldflags string

// Enable external linking if we use cgo.
if b.cfg.CgoEnabled {
ldflags = "-linkmode external "
Expand Down
Loading

0 comments on commit d172ad9

Please sign in to comment.