Skip to content

feat: experiments logging improvements #2049

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

Merged
merged 5 commits into from
Feb 8, 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
9 changes: 5 additions & 4 deletions cmd/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func run() error {
}

if flags.Experiments {
return experiments.List(log)
return log.PrintExperiments()
}

if flags.Init {
Expand Down Expand Up @@ -109,6 +109,10 @@ func run() error {
dir = home
}

if err := experiments.Validate(); err != nil {
log.Warnf("%s\n", err.Error())
}

var taskSorter sort.TaskSorter
switch flags.TaskSort {
case "none":
Expand Down Expand Up @@ -154,9 +158,6 @@ func run() error {
if err != nil {
return err
}
if experiments.AnyVariables.Enabled {
log.Warnf("The 'Any Variables' experiment flag is no longer required to use non-map variable types. If you wish to use map variables, please use 'TASK_X_MAP_VARIABLES' instead. See https://github.com/go-task/task/issues/1585\n")
}

// If the download flag is specified, we should stop execution as soon as
// taskfile is downloaded
Expand Down
8 changes: 7 additions & 1 deletion internal/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/go-task/task/v3/taskfile/ast"
)

const taskVarPrefix = "TASK_"

func Get(t *ast.Task) []string {
if t.Env == nil {
return nil
Expand All @@ -23,7 +25,7 @@ func GetFromVars(env *ast.Vars) []string {
if !isTypeAllowed(v) {
continue
}
if !experiments.EnvPrecedence.Enabled {
if !experiments.EnvPrecedence.Enabled() {
if _, alreadySet := os.LookupEnv(k); alreadySet {
continue
}
Expand All @@ -42,3 +44,7 @@ func isTypeAllowed(v any) bool {
return false
}
}

func GetTaskEnv(key string) string {
return os.Getenv(taskVarPrefix + key)
}
32 changes: 32 additions & 0 deletions internal/experiments/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package experiments

import (
"fmt"
"strings"
)

type InvalidValueError struct {
Name string
AllowedValues []string
Value string
}

func (err InvalidValueError) Error() string {
return fmt.Sprintf(
"task: Experiment %q has an invalid value %q (allowed values: %s)",
err.Name,
err.Value,
strings.Join(err.AllowedValues, ", "),
)
}

type InactiveError struct {
Name string
}

func (err InactiveError) Error() string {
return fmt.Sprintf(
"task: Experiment %q is inactive and cannot be enabled",
err.Name,
)
}
56 changes: 56 additions & 0 deletions internal/experiments/experiment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package experiments

import (
"fmt"
"slices"
)

type Experiment struct {
Name string // The name of the experiment.
AllowedValues []string // The values that can enable this experiment.
Value string // The version of the experiment that is enabled.
}

// New creates a new experiment with the given name and sets the values that can
// enable it.
func New(xName string, allowedValues ...string) Experiment {
value := getEnv(xName)
x := Experiment{
Name: xName,
AllowedValues: allowedValues,
Value: value,
}
xList = append(xList, x)
return x
}

func (x *Experiment) Enabled() bool {
return slices.Contains(x.AllowedValues, x.Value)
}

func (x *Experiment) Active() bool {
return len(x.AllowedValues) > 0
}

func (x Experiment) Valid() error {
if !x.Active() && x.Value != "" {
return &InactiveError{
Name: x.Name,
}
}
if !x.Enabled() && x.Value != "" {
return &InvalidValueError{
Name: x.Name,
AllowedValues: x.AllowedValues,
Value: x.Value,
}
}
return nil
}

func (x Experiment) String() string {
if x.Enabled() {
return fmt.Sprintf("on (%s)", x.Value)
}
return "off"
}
74 changes: 74 additions & 0 deletions internal/experiments/experiment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package experiments_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/go-task/task/v3/internal/experiments"
)

func TestNew(t *testing.T) {
const (
exampleExperiment = "EXAMPLE"
exampleExperimentEnv = "TASK_X_EXAMPLE"
)
tests := []struct {
name string
allowedValues []string
value string
wantEnabled bool
wantActive bool
wantValid error
}{
{
name: `[] allowed, value=""`,
wantEnabled: false,
wantActive: false,
},
{
name: `[] allowed, value="1"`,
value: "1",
wantEnabled: false,
wantActive: false,
wantValid: &experiments.InactiveError{
Name: exampleExperiment,
},
},
{
name: `[1] allowed, value=""`,
allowedValues: []string{"1"},
wantEnabled: false,
wantActive: true,
},
{
name: `[1] allowed, value="1"`,
allowedValues: []string{"1"},
value: "1",
wantEnabled: true,
wantActive: true,
},
{
name: `[1] allowed, value="2"`,
allowedValues: []string{"1"},
value: "2",
wantEnabled: false,
wantActive: true,
wantValid: &experiments.InvalidValueError{
Name: exampleExperiment,
AllowedValues: []string{"1"},
Value: "2",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv(exampleExperimentEnv, tt.value)
x := experiments.New(exampleExperiment, tt.allowedValues...)
assert.Equal(t, exampleExperiment, x.Name)
assert.Equal(t, tt.wantEnabled, x.Enabled())
assert.Equal(t, tt.wantActive, x.Active())
assert.Equal(t, tt.wantValid, x.Valid())
})
}
}
63 changes: 18 additions & 45 deletions internal/experiments/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,17 @@ package experiments

import (
"fmt"
"io"
"os"
"path/filepath"
"slices"
"strings"

"github.com/Ladicle/tabwriter"
"github.com/joho/godotenv"
"github.com/spf13/pflag"

"github.com/go-task/task/v3/internal/logger"
)

const envPrefix = "TASK_X_"

type Experiment struct {
Name string
Enabled bool
Value string
}

// A list of experiments.
// A set of experiments that can be enabled or disabled.
var (
GentleForce Experiment
RemoteTaskfiles Experiment
Expand All @@ -32,32 +21,31 @@ var (
EnvPrecedence Experiment
)

// An internal list of all the initialized experiments used for iterating.
var xList []Experiment

func init() {
readDotEnv()
GentleForce = New("GENTLE_FORCE")
RemoteTaskfiles = New("REMOTE_TASKFILES")
AnyVariables = New("ANY_VARIABLES", "1", "2")
GentleForce = New("GENTLE_FORCE", "1")
RemoteTaskfiles = New("REMOTE_TASKFILES", "1")
AnyVariables = New("ANY_VARIABLES")
MapVariables = New("MAP_VARIABLES", "1", "2")
EnvPrecedence = New("ENV_PRECEDENCE")
EnvPrecedence = New("ENV_PRECEDENCE", "1")
}

func New(xName string, enabledValues ...string) Experiment {
if len(enabledValues) == 0 {
enabledValues = []string{"1"}
}
value := getEnv(xName)
return Experiment{
Name: xName,
Enabled: slices.Contains(enabledValues, value),
Value: value,
// Validate checks if any experiments have been enabled while being inactive.
// If one is found, the function returns an error.
func Validate() error {
for _, x := range List() {
if err := x.Valid(); err != nil {
return err
}
}
return nil
}

func (x Experiment) String() string {
if x.Enabled {
return fmt.Sprintf("on (%s)", x.Value)
}
return "off"
func List() []Experiment {
return xList
}

func getEnv(xName string) string {
Expand Down Expand Up @@ -95,18 +83,3 @@ func readDotEnv() {
}
}
}

func printExperiment(w io.Writer, l *logger.Logger, x Experiment) {
l.FOutf(w, logger.Yellow, "* ")
l.FOutf(w, logger.Green, x.Name)
l.FOutf(w, logger.Default, ": \t%s\n", x.String())
}

func List(l *logger.Logger) error {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, ' ', 0)
printExperiment(w, l, GentleForce)
printExperiment(w, l, RemoteTaskfiles)
printExperiment(w, l, MapVariables)
printExperiment(w, l, EnvPrecedence)
return w.Flush()
}
7 changes: 4 additions & 3 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/spf13/pflag"

"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/taskfile/ast"
)
Expand Down Expand Up @@ -79,7 +80,7 @@ func init() {
log.Print(usage)
pflag.PrintDefaults()
}
offline, err := strconv.ParseBool(cmp.Or(os.Getenv("TASK_OFFLINE"), "false"))
offline, err := strconv.ParseBool(cmp.Or(env.GetTaskEnv("OFFLINE"), "false"))
if err != nil {
offline = false
}
Expand Down Expand Up @@ -115,15 +116,15 @@ func init() {
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")

// Gentle force experiment will override the force flag and add a new force-all flag
if experiments.GentleForce.Enabled {
if experiments.GentleForce.Enabled() {
pflag.BoolVarP(&Force, "force", "f", false, "Forces execution of the directly called task.")
pflag.BoolVar(&ForceAll, "force-all", false, "Forces execution of the called task and all its dependant tasks.")
} else {
pflag.BoolVarP(&ForceAll, "force", "f", false, "Forces execution even when the task is up-to-date.")
}

// Remote Taskfiles experiment will adds the "download" and "offline" flags
if experiments.RemoteTaskfiles.Enabled {
if experiments.RemoteTaskfiles.Enabled() {
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
pflag.BoolVar(&Offline, "offline", offline, "Forces Task to only use local or cached Taskfiles.")
pflag.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
Expand Down
Loading
Loading