diff --git a/cli/integration_tests/strict_env_vars/fixture-configs/all.json b/cli/integration_tests/_fixtures/strict_env_vars_configs/all.json similarity index 100% rename from cli/integration_tests/strict_env_vars/fixture-configs/all.json rename to cli/integration_tests/_fixtures/strict_env_vars_configs/all.json diff --git a/cli/integration_tests/strict_env_vars/fixture-configs/global_pt-empty.json b/cli/integration_tests/_fixtures/strict_env_vars_configs/global_pt-empty.json similarity index 100% rename from cli/integration_tests/strict_env_vars/fixture-configs/global_pt-empty.json rename to cli/integration_tests/_fixtures/strict_env_vars_configs/global_pt-empty.json diff --git a/cli/integration_tests/strict_env_vars/fixture-configs/global_pt.json b/cli/integration_tests/_fixtures/strict_env_vars_configs/global_pt.json similarity index 100% rename from cli/integration_tests/strict_env_vars/fixture-configs/global_pt.json rename to cli/integration_tests/_fixtures/strict_env_vars_configs/global_pt.json diff --git a/cli/integration_tests/strict_env_vars/fixture-configs/task_pt-empty.json b/cli/integration_tests/_fixtures/strict_env_vars_configs/task_pt-empty.json similarity index 100% rename from cli/integration_tests/strict_env_vars/fixture-configs/task_pt-empty.json rename to cli/integration_tests/_fixtures/strict_env_vars_configs/task_pt-empty.json diff --git a/cli/integration_tests/strict_env_vars/fixture-configs/task_pt.json b/cli/integration_tests/_fixtures/strict_env_vars_configs/task_pt.json similarity index 100% rename from cli/integration_tests/strict_env_vars/fixture-configs/task_pt.json rename to cli/integration_tests/_fixtures/strict_env_vars_configs/task_pt.json diff --git a/cli/integration_tests/dry_json/monorepo.t b/cli/integration_tests/dry_json/monorepo.t index f30ac3d8dd60f..5c5d20065a587 100644 --- a/cli/integration_tests/dry_json/monorepo.t +++ b/cli/integration_tests/dry_json/monorepo.t @@ -72,6 +72,7 @@ Setup $ cat tmpjson.log | jq 'keys' [ + "envMode", "globalCacheInputs", "id", "packages", @@ -122,13 +123,16 @@ Setup }, "expandedOutputs": [], "framework": "", + "envMode": "loose", "environmentVariables": { "configured": [], "inferred": [], "global": [ "SOME_ENV_VAR=", "VERCEL_ANALYTICS_ID=" - ] + ], + "passthrough": null, + "globalPassthrough": null } } @@ -170,6 +174,7 @@ Setup }, "expandedOutputs": [], "framework": "", + "envMode": "loose", "environmentVariables": { "configured": [ "NODE_ENV=" @@ -178,7 +183,9 @@ Setup "global": [ "SOME_ENV_VAR=", "VERCEL_ANALYTICS_ID=" - ] + ], + "passthrough": null, + "globalPassthrough": null } } @@ -192,7 +199,9 @@ Run again with NODE_ENV set and see the value in the summary. --filter=util work "global": [ "SOME_ENV_VAR=", "VERCEL_ANALYTICS_ID=" - ] + ], + "passthrough": null, + "globalPassthrough": null } Tasks that don't exist throw an error diff --git a/cli/integration_tests/dry_json/single_package.t b/cli/integration_tests/dry_json/single_package.t index 16d3290597af3..36d94fab30b6a 100644 --- a/cli/integration_tests/dry_json/single_package.t +++ b/cli/integration_tests/dry_json/single_package.t @@ -29,6 +29,7 @@ Setup } } }, + "envMode": "infer", "tasks": [ { "taskId": "build", @@ -70,12 +71,15 @@ Setup }, "expandedOutputs": [], "framework": "\u003cNO FRAMEWORK DETECTED\u003e", + "envMode": "loose", "environmentVariables": { "configured": [], "inferred": [], "global": [ "VERCEL_ANALYTICS_ID=" - ] + ], + "passthrough": null, + "globalPassthrough": null } } ] diff --git a/cli/integration_tests/dry_json/single_package_no_config.t b/cli/integration_tests/dry_json/single_package_no_config.t index 89452084bb2c3..0ed2d1291d7e6 100644 --- a/cli/integration_tests/dry_json/single_package_no_config.t +++ b/cli/integration_tests/dry_json/single_package_no_config.t @@ -26,6 +26,7 @@ Setup } } }, + "envMode": "infer", "tasks": [ { "taskId": "build", @@ -61,12 +62,15 @@ Setup }, "expandedOutputs": [], "framework": "\u003cNO FRAMEWORK DETECTED\u003e", + "envMode": "loose", "environmentVariables": { "configured": [], "inferred": [], "global": [ "VERCEL_ANALYTICS_ID=" - ] + ], + "passthrough": null, + "globalPassthrough": null } } ] diff --git a/cli/integration_tests/dry_json/single_package_with_deps.t b/cli/integration_tests/dry_json/single_package_with_deps.t index e54901ddea665..882b12484a861 100644 --- a/cli/integration_tests/dry_json/single_package_with_deps.t +++ b/cli/integration_tests/dry_json/single_package_with_deps.t @@ -39,6 +39,7 @@ Setup } } }, + "envMode": "infer", "tasks": [ { "taskId": "build", @@ -81,12 +82,15 @@ Setup }, "expandedOutputs": [], "framework": "\u003cNO FRAMEWORK DETECTED\u003e", + "envMode": "loose", "environmentVariables": { "configured": [], "inferred": [], "global": [ "VERCEL_ANALYTICS_ID=" - ] + ], + "passthrough": null, + "globalPassthrough": null } }, { @@ -128,12 +132,15 @@ Setup }, "expandedOutputs": [], "framework": "\u003cNO FRAMEWORK DETECTED\u003e", + "envMode": "loose", "environmentVariables": { "configured": [], "inferred": [], "global": [ "VERCEL_ANALYTICS_ID=" - ] + ], + "passthrough": null, + "globalPassthrough": null } } ] diff --git a/cli/integration_tests/run_summary/error.t b/cli/integration_tests/run_summary/error.t index b0743d764d79e..ee9ec101d6cf3 100644 --- a/cli/integration_tests/run_summary/error.t +++ b/cli/integration_tests/run_summary/error.t @@ -63,13 +63,16 @@ Validate that we got a full task summary for the failed task with an error in .e }, "expandedOutputs": [], "framework": "", + "envMode": "loose", "environmentVariables": { "configured": [], "inferred": [], "global": [ "SOME_ENV_VAR=", "VERCEL_ANALYTICS_ID=" - ] + ], + "passthrough": null, + "globalPassthrough": null }, "execution": { "startTime": [0-9]+, (re) diff --git a/cli/integration_tests/run_summary/single-package.t b/cli/integration_tests/run_summary/single-package.t index e333abe21ed47..d343248ac3879 100644 --- a/cli/integration_tests/run_summary/single-package.t +++ b/cli/integration_tests/run_summary/single-package.t @@ -51,6 +51,7 @@ Check "command", "dependencies", "dependents", + "envMode", "environmentVariables", "excludedOutputs", "execution", diff --git a/cli/integration_tests/run_summary/strict_env.t b/cli/integration_tests/run_summary/strict_env.t new file mode 100644 index 0000000000000..f264a5309dbf7 --- /dev/null +++ b/cli/integration_tests/run_summary/strict_env.t @@ -0,0 +1,206 @@ +Setup + $ . ${TESTDIR}/../_helpers/setup.sh + $ . ${TESTDIR}/../_helpers/setup_monorepo.sh $(pwd) strict_env_vars + +Set the env vars + $ export GLOBAL_VAR_PT=higlobalpt + $ export GLOBAL_VAR_DEP=higlobaldep + $ export LOCAL_VAR_PT=hilocalpt + $ export LOCAL_VAR_DEP=hilocaldep + $ export OTHER_VAR=hiother + $ export SYSTEMROOT=hisysroot + +Run as `infer` + $ rm -rf .turbo/runs + $ ${TURBO} run build --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + infer + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": null, + "globalPassthrough": null + } + +Run as `strict` + $ rm -rf .turbo/runs + $ ${TURBO} run build --experimental-env-mode=strict --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": null, + "globalPassthrough": null + } + +Run as `loose` + $ rm -rf .turbo/runs + $ ${TURBO} run build --experimental-env-mode=loose --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": null, + "globalPassthrough": null + } + +All specified + infer + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/all.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": [ + "LOCAL_VAR_PT=7cd1bb19c058cf4d6ad6aa579d685bddddf3ab587b78bdcb1e6e488fb6f47a3b" + ], + "globalPassthrough": [ + "GLOBAL_VAR_PT=cecd31fff1e723588eac8fe1edff89a6d2ec072f5c3bd884a98297487670b1f0" + ] + } + +All specified + loose + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/all.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --experimental-env-mode=loose --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": [ + "LOCAL_VAR_PT=7cd1bb19c058cf4d6ad6aa579d685bddddf3ab587b78bdcb1e6e488fb6f47a3b" + ], + "globalPassthrough": [ + "GLOBAL_VAR_PT=cecd31fff1e723588eac8fe1edff89a6d2ec072f5c3bd884a98297487670b1f0" + ] + } + +Global passthrough specified empty array + infer + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": null, + "globalPassthrough": [] + } + +Global passthrough specified value + infer + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": null, + "globalPassthrough": [ + "GLOBAL_VAR_PT=cecd31fff1e723588eac8fe1edff89a6d2ec072f5c3bd884a98297487670b1f0" + ] + } + +Global passthrough specified empty array + loose + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --experimental-env-mode=loose --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": null, + "globalPassthrough": [] + } + +Global passthrough specified value + loose + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --experimental-env-mode=loose --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": null, + "globalPassthrough": [ + "GLOBAL_VAR_PT=cecd31fff1e723588eac8fe1edff89a6d2ec072f5c3bd884a98297487670b1f0" + ] + } + +Task passthrough specified empty array + infer + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/task_pt-empty.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + infer + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": [], + "globalPassthrough": null + } + +Task passthrough specified value + infer + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/task_pt.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + infer + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + strict + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": [ + "LOCAL_VAR_PT=7cd1bb19c058cf4d6ad6aa579d685bddddf3ab587b78bdcb1e6e488fb6f47a3b" + ], + "globalPassthrough": null + } + +Task passthrough specified empty array + loose + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/task_pt-empty.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --experimental-env-mode=loose --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": [], + "globalPassthrough": null + } + +Task passthrough specified value + loose + $ rm -rf .turbo/runs + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/task_pt.json" "$(pwd)/turbo.json" && git commit --allow-empty -am "no comment" --quiet + $ ${TURBO} run build --experimental-env-mode=loose --summarize > /dev/null + $ cat .turbo/runs/*.json | jq -r '.envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].envMode' + loose + $ cat .turbo/runs/*.json | jq -r '.tasks[0].environmentVariables | {passthrough, globalPassthrough}' + { + "passthrough": [ + "LOCAL_VAR_PT=7cd1bb19c058cf4d6ad6aa579d685bddddf3ab587b78bdcb1e6e488fb6f47a3b" + ], + "globalPassthrough": null + } diff --git a/cli/integration_tests/strict_env_vars/dry_json.t b/cli/integration_tests/strict_env_vars/dry_json.t new file mode 100644 index 0000000000000..afa65871bb5bb --- /dev/null +++ b/cli/integration_tests/strict_env_vars/dry_json.t @@ -0,0 +1,22 @@ +Setup + $ . ${TESTDIR}/../_helpers/setup.sh + $ . ${TESTDIR}/../_helpers/setup_monorepo.sh $(pwd) strict_env_vars + +Empty passthroughs are null + $ ${TURBO} build --dry=json | jq -r '.tasks[0].environmentVariables | { passthrough, globalPassthrough }' + { + "passthrough": null, + "globalPassthrough": null + } + +Make sure that we populate the JSON output + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/all.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ ${TURBO} build --dry=json | jq -r '.tasks[0].environmentVariables | { passthrough, globalPassthrough }' + { + "passthrough": [ + "LOCAL_VAR_PT=" + ], + "globalPassthrough": [ + "GLOBAL_VAR_PT=" + ] + } diff --git a/cli/integration_tests/strict_env_vars/global_hash_infer.t b/cli/integration_tests/strict_env_vars/global_hash_infer.t index cca6353f245a4..342cd99805b34 100644 --- a/cli/integration_tests/strict_env_vars/global_hash_infer.t +++ b/cli/integration_tests/strict_env_vars/global_hash_infer.t @@ -12,11 +12,11 @@ There's no config to start, so the global hash does not change when flag is pass $ test $BASELINE = $WITH_FLAG Add empty config for global pass through env var, global hash changes - $ cp "$TESTDIR/fixture-configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ WITH_EMPTY_GLOBAL=$(${TURBO} build -vv --experimental-env-mode=infer 2>&1 | "$TESTDIR/../_helpers/get-global-hash.sh") $ test $BASELINE != $WITH_EMPTY_GLOBAL Add global pass through env var, global hash changes again, because we changed the value - $ cp "$TESTDIR/fixture-configs/global_pt.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ WITH_GLOBAL=$(${TURBO} build -vv --experimental-env-mode=infer 2>&1 | "$TESTDIR/../_helpers/get-global-hash.sh") $ test $WITH_EMPTY_GLOBAL != $WITH_GLOBAL diff --git a/cli/integration_tests/strict_env_vars/global_hash_loose.t b/cli/integration_tests/strict_env_vars/global_hash_loose.t index 7a492b717fb89..b2d760e8c8ee2 100644 --- a/cli/integration_tests/strict_env_vars/global_hash_loose.t +++ b/cli/integration_tests/strict_env_vars/global_hash_loose.t @@ -13,13 +13,13 @@ Hash changes, because we're using a new mode Add empty config for global pass through env var Hash does not change, because in loose mode, we don't care what the actual config contains - $ cp "$TESTDIR/fixture-configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ WITH_EMPTY_GLOBAL=$(${TURBO} build -vv --experimental-env-mode=loose 2>&1 | "$TESTDIR/../_helpers/get-global-hash.sh") $ test $WITH_FLAG = $WITH_EMPTY_GLOBAL Add global pass through env var Hash does not change, because in loose mode, we don't care what the actual config contains - $ cp "$TESTDIR/fixture-configs/global_pt.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ WITH_GLOBAL=$(${TURBO} build -vv --experimental-env-mode=loose 2>&1 | "$TESTDIR/../_helpers/get-global-hash.sh") $ test $WITH_FLAG = $WITH_GLOBAL $ test $WITH_EMPTY_GLOBAL = $WITH_GLOBAL diff --git a/cli/integration_tests/strict_env_vars/global_hash_no-value.t b/cli/integration_tests/strict_env_vars/global_hash_no-value.t index 9751c4e05dfd8..d09e3bc51f2e9 100644 --- a/cli/integration_tests/strict_env_vars/global_hash_no-value.t +++ b/cli/integration_tests/strict_env_vars/global_hash_no-value.t @@ -12,11 +12,11 @@ There's no config to start, so the global hash does not change when flag is pass $ test $BASELINE = $WITH_FLAG Add empty config for global pass through env var, global hash changes - $ cp "$TESTDIR/fixture-configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ WITH_EMPTY_GLOBAL=$(${TURBO} build -vv --experimental-env-mode 2>&1 | "$TESTDIR/../_helpers/get-global-hash.sh") $ test $BASELINE != $WITH_EMPTY_GLOBAL Add global pass through env var, global hash changes again, because we changed the value - $ cp "$TESTDIR/fixture-configs/global_pt.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ WITH_GLOBAL=$(${TURBO} build -vv --experimental-env-mode 2>&1 | "$TESTDIR/../_helpers/get-global-hash.sh") $ test $WITH_EMPTY_GLOBAL != $WITH_GLOBAL diff --git a/cli/integration_tests/strict_env_vars/global_hash_strict.t b/cli/integration_tests/strict_env_vars/global_hash_strict.t index 3accaa7b33419..585a572e193c7 100644 --- a/cli/integration_tests/strict_env_vars/global_hash_strict.t +++ b/cli/integration_tests/strict_env_vars/global_hash_strict.t @@ -13,12 +13,12 @@ Hash changes, because we're using a new mode Add empty config for global pass through env var Hash does not change, because the mode is the same and we haven't added any new pass through vars - $ cp "$TESTDIR/fixture-configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ WITH_EMPTY_GLOBAL=$(${TURBO} build -vv --experimental-env-mode=strict 2>&1 | "$TESTDIR/../_helpers/get-global-hash.sh") $ test $WITH_FLAG = $WITH_EMPTY_GLOBAL Add global pass through env var Hash changes, because we have a new pass through value - $ cp "$TESTDIR/fixture-configs/global_pt.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ WITH_GLOBAL=$(${TURBO} build -vv --experimental-env-mode=strict 2>&1 | "$TESTDIR/../_helpers/get-global-hash.sh") $ test $WITH_EMPTY_GLOBAL != $WITH_GLOBAL diff --git a/cli/integration_tests/strict_env_vars/usage_infer.t b/cli/integration_tests/strict_env_vars/usage_infer.t index 7a1269b4740ae..ec4322f884596 100644 --- a/cli/integration_tests/strict_env_vars/usage_infer.t +++ b/cli/integration_tests/strict_env_vars/usage_infer.t @@ -24,19 +24,19 @@ Inferred mode as loose because no pass through configs, all vars are available globalpt: 'higlobalpt', localpt: 'hilocalpt', globaldep: 'higlobaldep', localdep: 'hilocaldep', other: 'hiother', sysroot set: 'yes', path set: 'yes' Inferred mode as strict, because global pass through config, no vars available - $ cp "$TESTDIR/fixture-configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/global_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ ${TURBO} build -vv --experimental-env-mode=infer > /dev/null 2>&1 $ cat apps/my-app/out.txt globalpt: '', localpt: '', globaldep: '', localdep: '', other: '', sysroot set: 'yes', path set: 'yes' Inferred mode as strict, because task pass through config, no vars available - $ cp "$TESTDIR/fixture-configs/task_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/task_pt-empty.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ ${TURBO} build -vv --experimental-env-mode=infer > /dev/null 2>&1 $ cat apps/my-app/out.txt globalpt: '', localpt: '', globaldep: '', localdep: '', other: '', sysroot set: 'yes', path set: 'yes' Inferred mode as strict, with declared deps and pass through. all declared available, other is not available - $ cp "$TESTDIR/fixture-configs/all.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/all.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ ${TURBO} build -vv --experimental-env-mode=infer > /dev/null 2>&1 $ cat apps/my-app/out.txt globalpt: 'higlobalpt', localpt: 'hilocalpt', globaldep: 'higlobaldep', localdep: 'hilocaldep', other: '', sysroot set: 'yes', path set: 'yes' diff --git a/cli/integration_tests/strict_env_vars/usage_loose.t b/cli/integration_tests/strict_env_vars/usage_loose.t index 271c71904d8ee..2a7e14939ed21 100644 --- a/cli/integration_tests/strict_env_vars/usage_loose.t +++ b/cli/integration_tests/strict_env_vars/usage_loose.t @@ -18,7 +18,7 @@ All vars available in loose mode globalpt: 'higlobalpt', localpt: 'hilocalpt', globaldep: 'higlobaldep', localdep: 'hilocaldep', other: 'hiother', sysroot set: 'yes', path set: 'yes' All vars available in loose mode, even when global and pass through configs defined - $ cp "$TESTDIR/fixture-configs/all.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/all.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ ${TURBO} build -vv --experimental-env-mode=loose > /dev/null 2>&1 $ cat apps/my-app/out.txt globalpt: 'higlobalpt', localpt: 'hilocalpt', globaldep: 'higlobaldep', localdep: 'hilocaldep', other: 'hiother', sysroot set: 'yes', path set: 'yes' diff --git a/cli/integration_tests/strict_env_vars/usage_strict.t b/cli/integration_tests/strict_env_vars/usage_strict.t index ffb2f2b55fdac..de914e76b809d 100644 --- a/cli/integration_tests/strict_env_vars/usage_strict.t +++ b/cli/integration_tests/strict_env_vars/usage_strict.t @@ -18,7 +18,7 @@ No vars available by default globalpt: '', localpt: '', globaldep: '', localdep: '', other: '', sysroot set: 'yes', path set: 'yes' All declared vars available, others are not available - $ cp "$TESTDIR/fixture-configs/all.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet + $ cp "$TESTDIR/../_fixtures/strict_env_vars_configs/all.json" "$(pwd)/turbo.json" && git commit -am "no comment" --quiet $ ${TURBO} build -vv --experimental-env-mode=strict > /dev/null 2>&1 $ cat apps/my-app/out.txt globalpt: 'higlobalpt', localpt: 'hilocalpt', globaldep: 'higlobaldep', localdep: 'hilocaldep', other: '', sysroot set: 'yes', path set: 'yes' diff --git a/cli/internal/env/env.go b/cli/internal/env/env.go index 31ca69775d65d..1546b3da7a936 100644 --- a/cli/internal/env/env.go +++ b/cli/internal/env/env.go @@ -56,6 +56,10 @@ type EnvironmentVariablePairs []string // mapToPair returns a deterministically sorted set of EnvironmentVariablePairs from an EnvironmentVariableMap // It takes a transformer value to operate on each key-value pair and return a string func (evm EnvironmentVariableMap) mapToPair(transformer func(k string, v string) string) EnvironmentVariablePairs { + if evm == nil { + return nil + } + // convert to set to eliminate duplicates pairs := make([]string, 0, len(evm)) for k, v := range evm { diff --git a/cli/internal/graph/graph.go b/cli/internal/graph/graph.go index db68417d24c8d..480dec9ad38db 100644 --- a/cli/internal/graph/graph.go +++ b/cli/internal/graph/graph.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/go-hclog" "github.com/pyr-sh/dag" + "github.com/vercel/turbo/cli/internal/env" "github.com/vercel/turbo/cli/internal/fs" "github.com/vercel/turbo/cli/internal/nodes" "github.com/vercel/turbo/cli/internal/runsummary" @@ -50,6 +51,7 @@ type CompleteGraph struct { func (g *CompleteGraph) GetPackageTaskVisitor( ctx gocontext.Context, taskGraph *dag.AcyclicGraph, + globalEnvMode util.EnvMode, getArgs func(taskID string) []string, logger hclog.Logger, execFunc func(ctx gocontext.Context, packageTask *nodes.PackageTask, taskSummary *runsummary.TaskSummary) error, @@ -76,12 +78,29 @@ func (g *CompleteGraph) GetPackageTaskVisitor( return fmt.Errorf("Could not find definition for task") } + // Task env mode is only independent when global env mode is `infer`. + taskEnvMode := globalEnvMode + useOldTaskHashable := false + if taskEnvMode == util.Infer { + if taskDefinition.PassthroughEnv != nil { + taskEnvMode = util.Strict + } else { + // If we're in infer mode we have just detected non-usage of strict env vars. + // Since we haven't stabilized this we don't want to break their cache. + useOldTaskHashable = true + + // But our old behavior's actual meaning of this state is `loose`. + taskEnvMode = util.Loose + } + } + // TODO: maybe we can remove this PackageTask struct at some point packageTask := &nodes.PackageTask{ TaskID: taskID, Task: taskName, PackageName: packageName, Pkg: pkg, + EnvMode: taskEnvMode, Dir: pkg.Dir.ToString(), TaskDefinition: taskDefinition, Outputs: taskDefinition.Outputs.Inclusions, @@ -94,6 +113,7 @@ func (g *CompleteGraph) GetPackageTaskVisitor( taskGraph.DownEdges(taskID), logger, passThruArgs, + useOldTaskHashable, ) // Not being able to construct the task hash is a hard error @@ -111,6 +131,13 @@ func (g *CompleteGraph) GetPackageTaskVisitor( packageTask.LogFile = logFile packageTask.Command = command + var envVarPassthroughMap env.EnvironmentVariableMap + if taskDefinition.PassthroughEnv != nil { + if envVarPassthroughDetailedMap, err := env.GetHashableEnvVars(taskDefinition.PassthroughEnv, nil, ""); err == nil { + envVarPassthroughMap = envVarPassthroughDetailedMap.BySource.Explicit + } + } + summary := &runsummary.TaskSummary{ TaskID: taskID, Task: taskName, @@ -126,9 +153,11 @@ func (g *CompleteGraph) GetPackageTaskVisitor( Command: command, CommandArguments: passThruArgs, Framework: framework, + EnvMode: taskEnvMode, EnvVars: runsummary.TaskEnvVarSummary{ - Configured: envVars.BySource.Explicit.ToSecretHashable(), - Inferred: envVars.BySource.Matching.ToSecretHashable(), + Configured: envVars.BySource.Explicit.ToSecretHashable(), + Inferred: envVars.BySource.Matching.ToSecretHashable(), + Passthrough: envVarPassthroughMap.ToSecretHashable(), }, ExternalDepsHash: pkg.ExternalDepsHash, } diff --git a/cli/internal/nodes/packagetask.go b/cli/internal/nodes/packagetask.go index ef2a29350a7d1..e2dcb27dabda1 100644 --- a/cli/internal/nodes/packagetask.go +++ b/cli/internal/nodes/packagetask.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/vercel/turbo/cli/internal/fs" + "github.com/vercel/turbo/cli/internal/util" ) // PackageTask represents running a particular task in a particular package @@ -13,6 +14,7 @@ type PackageTask struct { Task string PackageName string Pkg *fs.PackageJSON + EnvMode util.EnvMode TaskDefinition *fs.TaskDefinition Dir string Command string diff --git a/cli/internal/run/dry_run.go b/cli/internal/run/dry_run.go index 62dd43b494201..f4b04aa41ebf7 100644 --- a/cli/internal/run/dry_run.go +++ b/cli/internal/run/dry_run.go @@ -10,10 +10,12 @@ import ( "github.com/vercel/turbo/cli/internal/cache" "github.com/vercel/turbo/cli/internal/cmdutil" "github.com/vercel/turbo/cli/internal/core" + "github.com/vercel/turbo/cli/internal/fs" "github.com/vercel/turbo/cli/internal/graph" "github.com/vercel/turbo/cli/internal/nodes" "github.com/vercel/turbo/cli/internal/runsummary" "github.com/vercel/turbo/cli/internal/taskhash" + "github.com/vercel/turbo/cli/internal/util" ) // DryRun gets all the info needed from tasks and prints out a summary, but doesn't actually @@ -25,6 +27,8 @@ func DryRun( engine *core.Engine, _ *taskhash.Tracker, // unused, but keep here for parity with RealRun method signature turboCache cache.Cache, + _ *fs.TurboJSON, // unused, but keep here for parity with RealRun method signature + globalEnvMode util.EnvMode, base *cmdutil.CmdBase, summary runsummary.Meta, ) error { @@ -59,7 +63,7 @@ func DryRun( return rs.ArgsForTask(taskID) } - visitorFn := g.GetPackageTaskVisitor(ctx, engine.TaskGraph, getArgs, base.Logger, execFunc) + visitorFn := g.GetPackageTaskVisitor(ctx, engine.TaskGraph, globalEnvMode, getArgs, base.Logger, execFunc) execOpts := core.EngineExecutionOptions{ Concurrency: 1, Parallel: false, diff --git a/cli/internal/run/global_hash.go b/cli/internal/run/global_hash.go index 8eb4fef23457e..2ebf6427eba15 100644 --- a/cli/internal/run/global_hash.go +++ b/cli/internal/run/global_hash.go @@ -47,28 +47,40 @@ type oldGlobalHashable struct { } // calculateGlobalHashFromHashable returns a hash string from the globalHashable -func calculateGlobalHashFromHashable(named GlobalHashable) (string, error) { - // When we aren't in infer mode, we can hash the whole object - if named.envMode != util.Infer { - return fs.HashObject(named) - } +func calculateGlobalHashFromHashable(full GlobalHashable) (string, error) { + switch full.envMode { + case util.Infer: + if full.envVarPassthroughs != nil { + // In infer mode, if there is any passThru config (even if it is an empty array) + // we'll hash the whole object, so we can detect changes to that config + // Further, resolve the envMode to the concrete value. + full.envMode = util.Strict + return fs.HashObject(full) + } - // In infer mode, if there is any passThru config (even if it is an empty array) - // we'll hash the whole object, so we can detect changes to that config - if named.envVarPassthroughs != nil { - return fs.HashObject(named) + // If we're in infer mode, and there is no global pass through config, + // we use the old struct layout. this will be true for everyone not using the strict env + // feature, and we don't want to break their cache. + return fs.HashObject(oldGlobalHashable{ + globalFileHashMap: full.globalFileHashMap, + rootExternalDepsHash: full.rootExternalDepsHash, + envVars: full.envVars.All.ToHashable(), + globalCacheKey: full.globalCacheKey, + pipeline: full.pipeline, + }) + case util.Loose: + // Remove the passthroughs from hash consideration if we're explicitly loose. + full.envVarPassthroughs = nil + return fs.HashObject(full) + case util.Strict: + // Collapse `nil` and `[]` in strict mode. + if full.envVarPassthroughs == nil { + full.envVarPassthroughs = make([]string, 0) + } + return fs.HashObject(full) + default: + panic("unimplemented environment mode") } - - // If we're in infer mode, and there is no global pass through config, - // we need to use the old struct layout. This will be true for everyone - // not using the strict env feature and we don't want to break their cache. - return fs.HashObject(oldGlobalHashable{ - globalFileHashMap: named.globalFileHashMap, - rootExternalDepsHash: named.rootExternalDepsHash, - envVars: named.envVars.All.ToHashable(), - globalCacheKey: named.globalCacheKey, - pipeline: named.pipeline, - }) } func calculateGlobalHash( @@ -140,11 +152,6 @@ func calculateGlobalHash( return GlobalHashable{}, fmt.Errorf("error hashing files: %w", err) } - // Remove the passthroughs from hash consideration if we're explicitly loose. - if envMode == util.Loose { - envVarPassthroughs = nil - } - return GlobalHashable{ globalFileHashMap: globalFileHashMap, rootExternalDepsHash: rootPackageJSON.ExternalDepsHash, diff --git a/cli/internal/run/real_run.go b/cli/internal/run/real_run.go index a00b5f92c9f78..b72c6f6ebb3c2 100644 --- a/cli/internal/run/real_run.go +++ b/cli/internal/run/real_run.go @@ -42,6 +42,7 @@ func RealRun( taskHashTracker *taskhash.Tracker, turboCache cache.Cache, turboJSON *fs.TurboJSON, + globalEnvMode util.EnvMode, packagesInScope []string, base *cmdutil.CmdBase, runSummary runsummary.Meta, @@ -126,7 +127,7 @@ func RealRun( return rs.ArgsForTask(taskID) } - visitorFn := g.GetPackageTaskVisitor(ctx, engine.TaskGraph, getArgs, base.Logger, execFunc) + visitorFn := g.GetPackageTaskVisitor(ctx, engine.TaskGraph, globalEnvMode, getArgs, base.Logger, execFunc) errs := engine.Execute(visitorFn, execOpts) // Track if we saw any child with a non-zero exit code @@ -219,20 +220,6 @@ func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTas progressLogger := ec.logger.Named("") progressLogger.Debug("start") - strictEnv := false - switch ec.rs.Opts.runOpts.EnvMode { - case util.Infer: - globalStrict := ec.passthroughEnv != nil - taskStrict := packageTask.TaskDefinition.PassthroughEnv != nil - inferredStrict := taskStrict || globalStrict - - strictEnv = inferredStrict - case util.Loose: - strictEnv = false - case util.Strict: - strictEnv = true - } - passThroughArgs := ec.rs.ArgsForTask(packageTask.Task) hash := packageTask.Hash ec.logger.Debug("task hash", "value", hash) @@ -305,7 +292,7 @@ func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTas currentState := env.GetEnvMap() passthroughEnv := env.EnvironmentVariableMap{} - if strictEnv { + if packageTask.EnvMode == util.Strict { defaultPassthrough := []string{ "PATH", "SHELL", diff --git a/cli/internal/run/run.go b/cli/internal/run/run.go index cc6f812f8515e..2ac114121187d 100644 --- a/cli/internal/run/run.go +++ b/cli/internal/run/run.go @@ -15,6 +15,7 @@ import ( "github.com/vercel/turbo/cli/internal/core" "github.com/vercel/turbo/cli/internal/daemon" "github.com/vercel/turbo/cli/internal/daemonclient" + "github.com/vercel/turbo/cli/internal/env" "github.com/vercel/turbo/cli/internal/fs" "github.com/vercel/turbo/cli/internal/graph" "github.com/vercel/turbo/cli/internal/process" @@ -346,6 +347,18 @@ func (r *run) run(ctx gocontext.Context, targets []string) error { } } + var envVarPassthroughMap env.EnvironmentVariableMap + if globalHashable.envVarPassthroughs != nil { + if envVarPassthroughDetailedMap, err := env.GetHashableEnvVars(globalHashable.envVarPassthroughs, nil, ""); err == nil { + envVarPassthroughMap = envVarPassthroughDetailedMap.BySource.Explicit + } + } + + globalEnvMode := rs.Opts.runOpts.EnvMode + if globalEnvMode == util.Infer && turboJSON.GlobalPassthroughEnv != nil { + globalEnvMode = util.Strict + } + // RunSummary contains information that is statically analyzable about // the tasks that we expect to run based on the user command. summary := runsummary.NewRunSummary( @@ -357,10 +370,12 @@ func (r *run) run(ctx gocontext.Context, targets []string) error { r.base.APIClient, rs.Opts.runOpts, packagesInScope, + globalEnvMode, runsummary.NewGlobalHashSummary( globalHashable.globalFileHashMap, globalHashable.rootExternalDepsHash, globalHashable.envVars, + envVarPassthroughMap, globalHashable.globalCacheKey, globalHashable.pipeline, ), @@ -376,6 +391,8 @@ func (r *run) run(ctx gocontext.Context, targets []string) error { engine, taskHashTracker, turboCache, + turboJSON, + globalEnvMode, r.base, summary, ) @@ -390,6 +407,7 @@ func (r *run) run(ctx gocontext.Context, targets []string) error { taskHashTracker, turboCache, turboJSON, + globalEnvMode, packagesInScope, r.base, summary, diff --git a/cli/internal/runsummary/format_json.go b/cli/internal/runsummary/format_json.go index 0508f38010d11..76a0a40f7fdcb 100644 --- a/cli/internal/runsummary/format_json.go +++ b/cli/internal/runsummary/format_json.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" "github.com/segmentio/ksuid" + "github.com/vercel/turbo/cli/internal/util" ) // FormatJSON returns a json string representing a RunSummary @@ -29,6 +30,7 @@ func (rsm *Meta) FormatJSON() ([]byte, error) { func (rsm *Meta) normalize() { for _, t := range rsm.RunSummary.Tasks { t.EnvVars.Global = rsm.RunSummary.GlobalHashSummary.envVars + t.EnvVars.GlobalPassthrough = rsm.RunSummary.GlobalHashSummary.passthroughEnvVars } // Remove execution summary for dry runs @@ -58,6 +60,7 @@ type nonMonorepoRunSummary struct { TurboVersion string `json:"turboVersion"` GlobalHashSummary *GlobalHashSummary `json:"globalCacheInputs"` Packages []string `json:"-"` + EnvMode util.EnvMode `json:"envMode"` ExecutionSummary *executionSummary `json:"execution,omitempty"` Tasks []*TaskSummary `json:"tasks"` } diff --git a/cli/internal/runsummary/globalhash_summary.go b/cli/internal/runsummary/globalhash_summary.go index 09467df59b7b1..e24976d5f99cb 100644 --- a/cli/internal/runsummary/globalhash_summary.go +++ b/cli/internal/runsummary/globalhash_summary.go @@ -14,7 +14,8 @@ type GlobalHashSummary struct { Pipeline fs.PristinePipeline `json:"rootPipeline"` // This is a private field because and not in JSON, because we'll add it to each task - envVars env.EnvironmentVariablePairs + envVars env.EnvironmentVariablePairs + passthroughEnvVars env.EnvironmentVariablePairs } // NewGlobalHashSummary creates a GlobalHashSummary struct from a set of fields. @@ -22,11 +23,13 @@ func NewGlobalHashSummary( fileHashMap map[turbopath.AnchoredUnixPath]string, rootExternalDepsHash string, envVars env.DetailedMap, + passthroughEnvVars env.EnvironmentVariableMap, globalCacheKey string, pipeline fs.PristinePipeline, ) *GlobalHashSummary { return &GlobalHashSummary{ envVars: envVars.All.ToSecretHashable(), + passthroughEnvVars: passthroughEnvVars.ToSecretHashable(), GlobalFileHashMap: fileHashMap, RootExternalDepsHash: rootExternalDepsHash, GlobalCacheKey: globalCacheKey, diff --git a/cli/internal/runsummary/run_summary.go b/cli/internal/runsummary/run_summary.go index 1c6062a3c7c56..a32111d0a0d12 100644 --- a/cli/internal/runsummary/run_summary.go +++ b/cli/internal/runsummary/run_summary.go @@ -59,6 +59,7 @@ type RunSummary struct { TurboVersion string `json:"turboVersion"` GlobalHashSummary *GlobalHashSummary `json:"globalCacheInputs"` Packages []string `json:"packages"` + EnvMode util.EnvMode `json:"envMode"` ExecutionSummary *executionSummary `json:"execution,omitempty"` Tasks []*TaskSummary `json:"tasks"` } @@ -73,6 +74,7 @@ func NewRunSummary( apiClient *client.APIClient, runOpts util.RunOpts, packages []string, + globalEnvMode util.EnvMode, globalHashSummary *GlobalHashSummary, synthesizedCommand string, ) Meta { @@ -98,6 +100,7 @@ func NewRunSummary( ExecutionSummary: executionSummary, TurboVersion: turboVersion, Packages: packages, + EnvMode: globalEnvMode, Tasks: []*TaskSummary{}, GlobalHashSummary: globalHashSummary, }, diff --git a/cli/internal/runsummary/task_summary.go b/cli/internal/runsummary/task_summary.go index d5532c69645c6..7aa470cb5816b 100644 --- a/cli/internal/runsummary/task_summary.go +++ b/cli/internal/runsummary/task_summary.go @@ -71,15 +71,18 @@ type TaskSummary struct { ResolvedTaskDefinition *fs.TaskDefinition `json:"resolvedTaskDefinition"` ExpandedOutputs []turbopath.AnchoredSystemPath `json:"expandedOutputs"` Framework string `json:"framework"` + EnvMode util.EnvMode `json:"envMode"` EnvVars TaskEnvVarSummary `json:"environmentVariables"` Execution *TaskExecutionSummary `json:"execution,omitempty"` // omit when it's not set } // TaskEnvVarSummary contains the environment variables that impacted a task's hash type TaskEnvVarSummary struct { - Configured []string `json:"configured"` - Inferred []string `json:"inferred"` - Global []string `json:"global"` + Configured []string `json:"configured"` + Inferred []string `json:"inferred"` + Global []string `json:"global"` + Passthrough []string `json:"passthrough"` + GlobalPassthrough []string `json:"globalPassthrough"` } // cleanForSinglePackage converts a TaskSummary to remove references to workspaces diff --git a/cli/internal/taskhash/taskhash.go b/cli/internal/taskhash/taskhash.go index c1614f703d81b..a912ad9370b44 100644 --- a/cli/internal/taskhash/taskhash.go +++ b/cli/internal/taskhash/taskhash.go @@ -277,18 +277,67 @@ func (th *Tracker) CalculateFileHashes( return nil } -type taskHashInputs struct { +type taskHashable struct { packageDir turbopath.AnchoredUnixPath hashOfFiles string externalDepsHash string task string outputs fs.TaskOutputs passThruArgs []string + envMode util.EnvMode + passthroughEnv []string hashableEnvPairs []string globalHash string taskDependencyHashes []string } +type oldTaskHashable struct { + packageDir turbopath.AnchoredUnixPath + hashOfFiles string + externalDepsHash string + task string + outputs fs.TaskOutputs + passThruArgs []string + hashableEnvPairs []string + globalHash string + taskDependencyHashes []string +} + +// calculateTaskHashFromHashable returns a hash string from the taskHashable +func calculateTaskHashFromHashable(full *taskHashable, useOldTaskHashable bool) (string, error) { + // The user is not using the strict environment variables feature. + if useOldTaskHashable { + return fs.HashObject(&oldTaskHashable{ + packageDir: full.packageDir, + hashOfFiles: full.hashOfFiles, + externalDepsHash: full.externalDepsHash, + task: full.task, + outputs: full.outputs, + passThruArgs: full.passThruArgs, + hashableEnvPairs: full.hashableEnvPairs, + globalHash: full.globalHash, + taskDependencyHashes: full.taskDependencyHashes, + }) + } + + switch full.envMode { + case util.Loose: + // Remove the passthroughs from hash consideration if we're explicitly loose. + full.passthroughEnv = nil + return fs.HashObject(full) + case util.Strict: + // Collapse `nil` and `[]` in strict mode. + if full.passthroughEnv == nil { + full.passthroughEnv = make([]string, 0) + } + return fs.HashObject(full) + case util.Infer: + panic("task inferred status should have already been resolved") + default: + panic("unimplemented environment mode") + } +} + func (th *Tracker) calculateDependencyHashes(dependencySet dag.Set) ([]string, error) { dependencyHashSet := make(util.Set) @@ -320,7 +369,7 @@ func (th *Tracker) calculateDependencyHashes(dependencySet dag.Set) ([]string, e // CalculateTaskHash calculates the hash for package-task combination. It is threadsafe, provided // that it has previously been called on its task-graph dependencies. File hashes must be calculated // first. -func (th *Tracker) CalculateTaskHash(packageTask *nodes.PackageTask, dependencySet dag.Set, logger hclog.Logger, args []string) (string, error) { +func (th *Tracker) CalculateTaskHash(packageTask *nodes.PackageTask, dependencySet dag.Set, logger hclog.Logger, args []string, useOldTaskHashable bool) (string, error) { pfs := specFromPackageTask(packageTask) pkgFileHashKey := pfs.ToKey() @@ -354,17 +403,19 @@ func (th *Tracker) CalculateTaskHash(packageTask *nodes.PackageTask, dependencyS // log any auto detected env vars logger.Debug(fmt.Sprintf("task hash env vars for %s:%s", packageTask.PackageName, packageTask.Task), "vars", hashableEnvPairs) - hash, err := fs.HashObject(&taskHashInputs{ + hash, err := calculateTaskHashFromHashable(&taskHashable{ packageDir: packageTask.Pkg.Dir.ToUnixPath(), hashOfFiles: hashOfFiles, externalDepsHash: packageTask.Pkg.ExternalDepsHash, task: packageTask.Task, outputs: outputs.Sort(), passThruArgs: args, + envMode: packageTask.EnvMode, + passthroughEnv: packageTask.TaskDefinition.PassthroughEnv, hashableEnvPairs: hashableEnvPairs, globalHash: th.globalHash, taskDependencyHashes: taskDependencyHashes, - }) + }, useOldTaskHashable) if err != nil { return "", fmt.Errorf("failed to hash task %v: %v", packageTask.TaskID, hash) } diff --git a/cli/internal/util/run_opts.go b/cli/internal/util/run_opts.go index 7f3c09c4d0079..08676a0110d03 100644 --- a/cli/internal/util/run_opts.go +++ b/cli/internal/util/run_opts.go @@ -1,5 +1,7 @@ package util +import "strings" + // EnvMode specifies if we will be using strict env vars type EnvMode string @@ -12,6 +14,11 @@ const ( Strict EnvMode = "Strict" ) +// MarshalText implements TextMarshaler for the struct. +func (s EnvMode) MarshalText() (text []byte, err error) { + return []byte(strings.ToLower(string(s))), nil +} + // RunOpts holds the options that control the execution of a turbo run type RunOpts struct { // Force execution to be serially one-at-a-time