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.
This commit is contained in:
Alex Reinking
2022-08-14 04:42:11 -04:00
parent 5b949bbb91
commit c9d70a7cc3
19 changed files with 152 additions and 6 deletions

View File

@@ -862,11 +862,29 @@ Available commands are:
Displays arguments as text but no new line.
.. option:: env [--unset=NAME ...] [NAME=VALUE ...] [--] <command> [<arg>...]
.. option:: env [<options>] [--] <command> [<arg>...]
.. 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

View File

@@ -0,0 +1,5 @@
cmake-E-env-modify
------------------
* A new ``--modify`` flag was added to :option:`cmake -E env <cmake_E env>` to support :prop_test:`ENVIRONMENT_MODIFICATION`
operations.

View File

@@ -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();

View File

@@ -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.

View File

@@ -791,6 +791,10 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> cmd(ai, ae);
int retval;

View File

@@ -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 ()

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,3 @@
^CMake Error: Error: Unrecognized environment manipulation argument: unknown
cmake -E env: invalid parameter to --modify: TEST_ENV=unknown:$

View File

@@ -0,0 +1 @@
^-- TEST_ENV is correctly set in environment: exp;ect;ed$

View File

@@ -0,0 +1 @@
^-- TEST_ENV is correctly set in environment: exp[;:]ect[;:]ed$

View File

@@ -0,0 +1 @@
^-- TEST_ENV is correctly set in environment: expected$

View File

@@ -0,0 +1 @@
^-- TEST_ENV is correctly not set in environment$

View File

@@ -0,0 +1 @@
^-- TEST_ENV is correctly set in environment: 1$

View File

@@ -0,0 +1 @@
^-- TEST_ENV is correctly set in environment: expected$

View File

@@ -0,0 +1 @@
^-- TEST_ENV is correctly not set in environment$

View File

@@ -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 .)