From c9d70a7cc32bd023c92fd9eac19cb907b86ada8f Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Sun, 14 Aug 2022 04:42:11 -0400 Subject: cmake -E env: Add --modify flag When `cmake -E env` is given the `--modify` flag, try to parse the following argument as an `ENVIRONMENT_MODIFICATION` operation and apply it to the environment. This generalizes `--unset=`: 1. When implementing `ENVIRONMENT_MODIFICATION` features for other CMake commands, the `MYVAR=OP:VALUE` strings do not need to be translated to OP-specific flags. 2. This provides a natural and consistent extension point to introduce new operations without introducing very many flags. 3. Users need to learn only one syntax to access the same functionality. There is one difference between the behavior here as compared to CTest's interpretation of the `ENVIRONMENT_MODIFICATION` test property. The `MYVAR=reset:` command when run in `cmake -E env` will reset `MYVAR` to whatever its value was when `cmake -E env` launched, rather than try to checkpoint after plain `MYVAR=VALUE` options. This makes `MYVAR=VALUE` and `--modify MYVAR=set:VALUE` semantically equivalent. --- Help/manual/cmake.1.rst | 22 ++++++++- Help/release/dev/cmake-E-env-modify.rst | 5 ++ Source/cmSystemTools.cxx | 7 ++- Source/cmSystemTools.h | 3 ++ Source/cmcmd.cxx | 38 +++++++++++++-- Tests/RunCMake/CommandLine/E_env-equal.cmake | 15 ++++++ .../E_env_modify-bad-operation-result.txt | 1 + .../E_env_modify-bad-operation-stderr.txt | 3 ++ .../CommandLine/E_env_modify-cmake_list-stdout.txt | 1 + .../CommandLine/E_env_modify-path_list-stdout.txt | 1 + .../CommandLine/E_env_modify-reset-stdout.txt | 1 + .../E_env_modify-reset-to-unset-stdout.txt | 1 + .../CommandLine/E_env_modify-set-stdout.txt | 1 + .../CommandLine/E_env_modify-string-stdout.txt | 1 + .../CommandLine/E_env_modify-unset-stdout.txt | 1 + .../E_env_modify-with-double-dash-result.txt | 1 + .../E_env_modify-without-double-dash-result.txt | 1 + .../E_env_modify-without-double-dash-stderr.txt | 1 + Tests/RunCMake/CommandLine/RunCMakeTest.cmake | 54 ++++++++++++++++++++++ 19 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 Help/release/dev/cmake-E-env-modify.rst create mode 100644 Tests/RunCMake/CommandLine/E_env-equal.cmake create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-bad-operation-result.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-bad-operation-stderr.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-cmake_list-stdout.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-path_list-stdout.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-reset-stdout.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-reset-to-unset-stdout.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-set-stdout.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-string-stdout.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-unset-stdout.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-with-double-dash-result.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-without-double-dash-result.txt create mode 100644 Tests/RunCMake/CommandLine/E_env_modify-without-double-dash-stderr.txt diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst index 1f91a25..7eaf3fa 100644 --- a/Help/manual/cmake.1.rst +++ b/Help/manual/cmake.1.rst @@ -862,11 +862,29 @@ Available commands are: Displays arguments as text but no new line. -.. option:: env [--unset=NAME ...] [NAME=VALUE ...] [--] [...] +.. option:: env [] [--] [...] .. versionadded:: 3.1 - Run command in a modified environment. + Run command in a modified environment. Options are: + + ``NAME=VALUE`` + Replaces the current value of ``NAME`` with ``VALUE``. + + ``--unset=NAME`` + Unsets the current value of ``NAME``. + + ``--modify ENVIRONMENT_MODIFICATION`` + .. versionadded:: 3.25 + + Apply a single :prop_test:`ENVIRONMENT_MODIFICATION` operation to the + modified environment. + + The ``NAME=VALUE`` and ``--unset=NAME`` options are equivalent to + ``--modify NAME=set:VALUE`` and ``--modify NAME=unset:``, respectively. + Note that ``--modify NAME=reset:`` resets ``NAME`` to the value it had + when ``cmake`` launched (or unsets it), not to the most recent + ``NAME=VALUE`` option. .. versionadded:: 3.24 Added support for the double dash argument ``--``. Use ``--`` to stop diff --git a/Help/release/dev/cmake-E-env-modify.rst b/Help/release/dev/cmake-E-env-modify.rst new file mode 100644 index 0000000..ea4a622 --- /dev/null +++ b/Help/release/dev/cmake-E-env-modify.rst @@ -0,0 +1,5 @@ +cmake-E-env-modify +------------------ + +* A new ``--modify`` flag was added to :option:`cmake -E env ` to support :prop_test:`ENVIRONMENT_MODIFICATION` + operations. diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index 2c32f09..3c4e709 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -1578,10 +1578,15 @@ void cmSystemTools::EnvDiff::PutEnv(const std::string& env) std::string name = env.substr(0, eq_loc); diff[name] = env.substr(eq_loc + 1); } else { - diff[env] = {}; + this->UnPutEnv(env); } } +void cmSystemTools::EnvDiff::UnPutEnv(const std::string& env) +{ + diff[env] = {}; +} + bool cmSystemTools::EnvDiff::ParseOperation(const std::string& envmod) { char path_sep = GetSystemPathlistSeparator(); diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h index e8c04b1..48bbe23 100644 --- a/Source/cmSystemTools.h +++ b/Source/cmSystemTools.h @@ -397,6 +397,9 @@ public: */ void PutEnv(const std::string& env); + /** Remove a single variable from the current environment diff. */ + void UnPutEnv(const std::string& env); + /** * Apply an ENVIRONMENT_MODIFICATION operation to this diff. Returns * false and issues an error on parse failure. diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index b1f1bcf..00c9bda 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -791,6 +791,10 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args, } if (args[1] == "env") { +#ifndef CMAKE_BOOTSTRAP + cmSystemTools::EnvDiff env; +#endif + auto ai = args.cbegin() + 2; auto ae = args.cend(); for (; ai != ae; ++ai) { @@ -803,16 +807,40 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args, } if (cmHasLiteralPrefix(a, "--unset=")) { // Unset environment variable. +#ifdef CMAKE_BOOTSTRAP cmSystemTools::UnPutEnv(a.substr(8)); +#else + env.UnPutEnv(a.substr(8)); +#endif + } else if (a == "--modify") { +#ifdef CMAKE_BOOTSTRAP + std::cerr + << "cmake -E env: --modify not available during bootstrapping\n"; + return 1; +#else + if (++ai == ae) { + std::cerr << "cmake -E env: --modify missing a parameter\n"; + return 1; + } + std::string const& op = *ai; + if (!env.ParseOperation(op)) { + std::cerr << "cmake -E env: invalid parameter to --modify: " << op + << '\n'; + return 1; + } +#endif } else if (!a.empty() && a[0] == '-') { // Environment variable and command names cannot start in '-', // so this must be an unknown option. - std::cerr << "cmake -E env: unknown option '" << a << '\'' - << std::endl; + std::cerr << "cmake -E env: unknown option '" << a << "'\n"; return 1; } else if (a.find('=') != std::string::npos) { // Set environment variable. +#ifdef CMAKE_BOOTSTRAP cmSystemTools::PutEnv(a); +#else + env.PutEnv(a); +#endif } else { // This is the beginning of the command. break; @@ -820,10 +848,14 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args, } if (ai == ae) { - std::cerr << "cmake -E env: no command given" << std::endl; + std::cerr << "cmake -E env: no command given\n"; return 1; } +#ifndef CMAKE_BOOTSTRAP + env.ApplyToCurrentEnv(); +#endif + // Execute command from remaining arguments. std::vector cmd(ai, ae); int retval; diff --git a/Tests/RunCMake/CommandLine/E_env-equal.cmake b/Tests/RunCMake/CommandLine/E_env-equal.cmake new file mode 100644 index 0000000..3f18bb6 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env-equal.cmake @@ -0,0 +1,15 @@ +if (NOT DEFINED ENV{TEST_ENV_EXPECTED}) + if (NOT DEFINED ENV{TEST_ENV}) + message(STATUS "TEST_ENV is correctly not set in environment") + else () + message(FATAL_ERROR "TEST_ENV is incorrectly set in environment") + endif () +else () + if (NOT DEFINED ENV{TEST_ENV}) + message(FATAL_ERROR "TEST_ENV is incorrectly not set in environment") + elseif ("$ENV{TEST_ENV}" STREQUAL "$ENV{TEST_ENV_EXPECTED}") + message(STATUS "TEST_ENV is correctly set in environment: $ENV{TEST_ENV}") + else () + message(FATAL_ERROR "TEST_ENV is incorrectly set in environment!\n\tactual: $ENV{TEST_ENV}\n\texpected: $ENV{TEST_ENV_EXPECTED}") + endif () +endif () diff --git a/Tests/RunCMake/CommandLine/E_env_modify-bad-operation-result.txt b/Tests/RunCMake/CommandLine/E_env_modify-bad-operation-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-bad-operation-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CommandLine/E_env_modify-bad-operation-stderr.txt b/Tests/RunCMake/CommandLine/E_env_modify-bad-operation-stderr.txt new file mode 100644 index 0000000..ccfdeab --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-bad-operation-stderr.txt @@ -0,0 +1,3 @@ +^CMake Error: Error: Unrecognized environment manipulation argument: unknown + +cmake -E env: invalid parameter to --modify: TEST_ENV=unknown:$ diff --git a/Tests/RunCMake/CommandLine/E_env_modify-cmake_list-stdout.txt b/Tests/RunCMake/CommandLine/E_env_modify-cmake_list-stdout.txt new file mode 100644 index 0000000..ad42f56 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-cmake_list-stdout.txt @@ -0,0 +1 @@ +^-- TEST_ENV is correctly set in environment: exp;ect;ed$ diff --git a/Tests/RunCMake/CommandLine/E_env_modify-path_list-stdout.txt b/Tests/RunCMake/CommandLine/E_env_modify-path_list-stdout.txt new file mode 100644 index 0000000..49572a3 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-path_list-stdout.txt @@ -0,0 +1 @@ +^-- TEST_ENV is correctly set in environment: exp[;:]ect[;:]ed$ diff --git a/Tests/RunCMake/CommandLine/E_env_modify-reset-stdout.txt b/Tests/RunCMake/CommandLine/E_env_modify-reset-stdout.txt new file mode 100644 index 0000000..a60f1bf --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-reset-stdout.txt @@ -0,0 +1 @@ +^-- TEST_ENV is correctly set in environment: expected$ diff --git a/Tests/RunCMake/CommandLine/E_env_modify-reset-to-unset-stdout.txt b/Tests/RunCMake/CommandLine/E_env_modify-reset-to-unset-stdout.txt new file mode 100644 index 0000000..a1d5c01 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-reset-to-unset-stdout.txt @@ -0,0 +1 @@ +^-- TEST_ENV is correctly not set in environment$ diff --git a/Tests/RunCMake/CommandLine/E_env_modify-set-stdout.txt b/Tests/RunCMake/CommandLine/E_env_modify-set-stdout.txt new file mode 100644 index 0000000..feff117 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-set-stdout.txt @@ -0,0 +1 @@ +^-- TEST_ENV is correctly set in environment: 1$ diff --git a/Tests/RunCMake/CommandLine/E_env_modify-string-stdout.txt b/Tests/RunCMake/CommandLine/E_env_modify-string-stdout.txt new file mode 100644 index 0000000..a60f1bf --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-string-stdout.txt @@ -0,0 +1 @@ +^-- TEST_ENV is correctly set in environment: expected$ diff --git a/Tests/RunCMake/CommandLine/E_env_modify-unset-stdout.txt b/Tests/RunCMake/CommandLine/E_env_modify-unset-stdout.txt new file mode 100644 index 0000000..a1d5c01 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-unset-stdout.txt @@ -0,0 +1 @@ +^-- TEST_ENV is correctly not set in environment$ diff --git a/Tests/RunCMake/CommandLine/E_env_modify-with-double-dash-result.txt b/Tests/RunCMake/CommandLine/E_env_modify-with-double-dash-result.txt new file mode 100644 index 0000000..573541a --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-with-double-dash-result.txt @@ -0,0 +1 @@ +0 diff --git a/Tests/RunCMake/CommandLine/E_env_modify-without-double-dash-result.txt b/Tests/RunCMake/CommandLine/E_env_modify-without-double-dash-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-without-double-dash-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CommandLine/E_env_modify-without-double-dash-stderr.txt b/Tests/RunCMake/CommandLine/E_env_modify-without-double-dash-stderr.txt new file mode 100644 index 0000000..8d98f9d --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_env_modify-without-double-dash-stderr.txt @@ -0,0 +1 @@ +.* diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake index 39d7858..7da0f8d 100644 --- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake +++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake @@ -744,6 +744,10 @@ run_cmake_command(E_cat-without-double-dash ${CMAKE_COMMAND} -E cat "-file-start unset(RunCMake_TEST_COMMAND_WORKING_DIRECTORY) unset(out) +# Unset environment variables that are used for testing cmake -E +unset(ENV{TEST_ENV}) +unset(ENV{TEST_ENV_EXPECTED}) + run_cmake_command(E_env-no-command0 ${CMAKE_COMMAND} -E env) run_cmake_command(E_env-no-command1 ${CMAKE_COMMAND} -E env TEST_ENV=1) run_cmake_command(E_env-bad-arg1 ${CMAKE_COMMAND} -E env -bad-arg1) @@ -758,6 +762,56 @@ file(COPY_FILE "${EXIT_CODE_EXE}" "${RunCMake_BINARY_DIR}/env=${exit_code}") run_cmake_command(E_env-with-double-dash ${CMAKE_COMMAND} -E env TEST_ENV=1 -- "${RunCMake_BINARY_DIR}/env=${exit_code}" zero_exit) run_cmake_command(E_env-without-double-dash ${CMAKE_COMMAND} -E env TEST_ENV=1 "${RunCMake_BINARY_DIR}/env=${exit_code}" zero_exit) +## Tests of env --modify +# Repeat the same tests as above +run_cmake_command(E_env_modify-set ${CMAKE_COMMAND} -E env --modify TEST_ENV=set:1 ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-set.cmake) +run_cmake_command(E_env_modify-unset ${CMAKE_COMMAND} -E env --modify TEST_ENV=set:1 ${CMAKE_COMMAND} -E env --modify TEST_ENV=unset: ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-unset.cmake) +run_cmake_command(E_env_modify-with-double-dash ${CMAKE_COMMAND} -E env --modify TEST_ENV=set:1 -- "${RunCMake_BINARY_DIR}/env=${exit_code}" zero_exit) +run_cmake_command(E_env_modify-without-double-dash ${CMAKE_COMMAND} -E env --modify TEST_ENV=set:1 "${RunCMake_BINARY_DIR}/env=${exit_code}" zero_exit) + +# Test environment modification commands +run_cmake_command(E_env_modify-reset + ${CMAKE_COMMAND} -E env TEST_ENV=expected + ${CMAKE_COMMAND} -E env TEST_ENV_EXPECTED=expected TEST_ENV=bad_value --modify TEST_ENV=reset: + ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-equal.cmake) + +run_cmake_command(E_env_modify-reset-to-unset + ${CMAKE_COMMAND} -E env --unset=TEST_ENV --unset=TEST_ENV_EXPECTED + ${CMAKE_COMMAND} -E env TEST_ENV=bad_value --modify TEST_ENV=reset: + ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-equal.cmake) + +run_cmake_command(E_env_modify-string + ${CMAKE_COMMAND} -E env TEST_ENV_EXPECTED=expected + --modify TEST_ENV=unset: + --modify TEST_ENV=string_append:ect + --modify TEST_ENV=string_prepend:exp + --modify TEST_ENV=string_append:ed + ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-equal.cmake) + +if (WIN32) + set(SEP "\\;") +else () + set(SEP ":") +endif () + +run_cmake_command(E_env_modify-path_list + ${CMAKE_COMMAND} -E env "TEST_ENV_EXPECTED=exp${SEP}ect${SEP}ed" + --modify TEST_ENV=unset: + --modify TEST_ENV=path_list_append:ect + --modify TEST_ENV=path_list_prepend:exp + --modify TEST_ENV=path_list_append:ed + ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-equal.cmake) + +run_cmake_command(E_env_modify-cmake_list + ${CMAKE_COMMAND} -E env "TEST_ENV_EXPECTED=exp\\;ect\\;ed" + --modify TEST_ENV=unset: + --modify TEST_ENV=cmake_list_append:ect + --modify TEST_ENV=cmake_list_prepend:exp + --modify TEST_ENV=cmake_list_append:ed + ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-equal.cmake) + +run_cmake_command(E_env_modify-bad-operation ${CMAKE_COMMAND} -E env --modify TEST_ENV=unknown:) + run_cmake_command(E_md5sum-dir ${CMAKE_COMMAND} -E md5sum .) run_cmake_command(E_sha1sum-dir ${CMAKE_COMMAND} -E sha1sum .) run_cmake_command(E_sha224sum-dir ${CMAKE_COMMAND} -E sha224sum .) -- cgit v0.12