diff --git a/internal/env/env.go b/internal/env/env.go index 5a62a51f..8aded374 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/go-task/task/v3/internal/experiments" "github.com/go-task/task/v3/taskfile/ast" ) @@ -11,15 +12,15 @@ func Get(t *ast.Task) []string { if t.Env == nil { return nil } - environ := os.Environ() for k, v := range t.Env.ToCacheMap() { if !isTypeAllowed(v) { continue } - - if _, alreadySet := os.LookupEnv(k); alreadySet { - continue + if !experiments.EnvPrecedence.Enabled { + if _, alreadySet := os.LookupEnv(k); alreadySet { + continue + } } environ = append(environ, fmt.Sprintf("%s=%v", k, v)) } diff --git a/internal/experiments/experiments.go b/internal/experiments/experiments.go index b7e57ee2..8b711bf9 100644 --- a/internal/experiments/experiments.go +++ b/internal/experiments/experiments.go @@ -29,6 +29,7 @@ var ( RemoteTaskfiles Experiment AnyVariables Experiment MapVariables Experiment + EnvPrecedence Experiment ) func init() { @@ -37,6 +38,7 @@ func init() { RemoteTaskfiles = New("REMOTE_TASKFILES") AnyVariables = New("ANY_VARIABLES", "1", "2") MapVariables = New("MAP_VARIABLES", "1", "2") + EnvPrecedence = New("ENV_PRECEDENCE") } func New(xName string, enabledValues ...string) Experiment { @@ -104,5 +106,6 @@ func List(l *logger.Logger) error { printExperiment(w, l, GentleForce) printExperiment(w, l, RemoteTaskfiles) printExperiment(w, l, MapVariables) + printExperiment(w, l, EnvPrecedence) return w.Flush() } diff --git a/task_test.go b/task_test.go index 0dd489ca..90a2ac14 100644 --- a/task_test.go +++ b/task_test.go @@ -19,6 +19,7 @@ import ( "github.com/go-task/task/v3" "github.com/go-task/task/v3/errors" + "github.com/go-task/task/v3/internal/experiments" "github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/taskfile/ast" ) @@ -60,7 +61,6 @@ func (fct fileContentTest) Run(t *testing.T) { for f := range fct.Files { _ = os.Remove(filepathext.SmartJoin(fct.Dir, f)) } - e := &task.Executor{ Dir: fct.Dir, TempDir: task.TempDir{ @@ -71,9 +71,9 @@ func (fct fileContentTest) Run(t *testing.T) { Stdout: io.Discard, Stderr: io.Discard, } + require.NoError(t, e.Setup(), "e.Setup()") require.NoError(t, e.Run(context.Background(), &ast.Call{Task: fct.Target}), "e.Run(target)") - for name, expectContent := range fct.Files { t.Run(fct.name(name), func(t *testing.T) { path := filepathext.SmartJoin(e.Dir, name) @@ -108,6 +108,7 @@ func TestEmptyTaskfile(t *testing.T) { } func TestEnv(t *testing.T) { + t.Setenv("QUX", "from_os") tt := fileContentTest{ Dir: "testdata/env", Target: "default", @@ -116,9 +117,21 @@ func TestEnv(t *testing.T) { "local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n", "global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n", "multiple_type.txt": "FOO='1' BAR='true' BAZ='1.1'\n", + "not-overriden.txt": "QUX='from_os'\n", }, } tt.Run(t) + t.Setenv("TASK_X_ENV_PRECEDENCE", "1") + experiments.EnvPrecedence = experiments.New("ENV_PRECEDENCE") + ttt := fileContentTest{ + Dir: "testdata/env", + Target: "overriden", + TrimSpace: false, + Files: map[string]string{ + "overriden.txt": "QUX='from_taskfile'\n", + }, + } + ttt.Run(t) } func TestVars(t *testing.T) { diff --git a/testdata/env/Taskfile.yml b/testdata/env/Taskfile.yml index 8bbd0a9d..d2b802fc 100644 --- a/testdata/env/Taskfile.yml +++ b/testdata/env/Taskfile.yml @@ -8,12 +8,14 @@ env: FOO: foo BAR: bar BAZ: "{{.BAZ}}" + QUX: from_taskfile tasks: default: cmds: - task: local - task: global + - task: not-overriden - task: multiple_type local: @@ -40,3 +42,11 @@ tasks: BAZ: 1.1 cmds: - echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > multiple_type.txt + + not-overriden: + cmds: + - echo "QUX='$QUX'" > not-overriden.txt + + overriden: + cmds: + - echo "QUX='$QUX'" > overriden.txt diff --git a/website/docs/experiments/env_precedence.mdx b/website/docs/experiments/env_precedence.mdx new file mode 100644 index 00000000..1dd3d412 --- /dev/null +++ b/website/docs/experiments/env_precedence.mdx @@ -0,0 +1,70 @@ +--- +draft: false # Hide in production +slug: '/experiments/env-precedence' +--- + +# Env Precedence (#1038) + +:::caution + +All experimental features are subject to breaking changes and/or removal _at any +time_. We strongly recommend that you do not use these features in a production +environment. They are intended for testing and feedback only. + +::: + +:::warning + +This experiment breaks the following functionality: + +- environment variable will take precedence over OS environment variables + +::: + +:::info + +To enable this experiment, set the environment variable: `TASK_X_ENV_PRECEDENCE=1`. +Check out [our guide to enabling experiments ][enabling-experiments] for more +information. + +::: + +Before this experiment, the OS variable took precedence over the task environment variable. This experiment changes the precedence to make the task environment variable take precedence over the OS variable. + +Consider the following example: + +```yml +version: '3' + +tasks: + default: + env: + KEY: 'other' + cmds: + - echo "$KEY" +``` +Running `KEY=some task` before this experiment, the output would be `some`, but after this experiment, the output would be `other`. + +If you still want to get the OS variable, you can use the template function env like follow : `{{env "OS_VAR"}}`. + +```yml +version: '3' + +tasks: + default: + env: + KEY: 'other' + cmds: + - echo "$KEY" + - echo {{env "KEY"}} +``` +Running `KEY=some task`, the output would be `other` and `some`. + +Like other variables/envs, you can also fall back to a given value using the default template function: +```yml +MY_ENV: '{{.MY_ENV | default "fallback"}}' +``` + +{/* prettier-ignore-start */} +[enabling-experiments]: ./experiments.mdx#enabling-experiments +{/* prettier-ignore-end */}