From acd5fbdda407bf423ca85324d35236c947dca9e0 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Wed, 1 Nov 2023 14:06:52 +0000 Subject: [PATCH] Add new functions for manipulating the environment This adds functions for using collections with the environment. It allows for conveniently retreiving and setting the environment variables via a map, rather than individually. This also adds a clearenv and printenv for completeness. Signed-off-by: Michael Carroll --- include/gz/utils/Environment.hh | 44 ++++++++++++++++++++- src/Environment.cc | 70 +++++++++++++++++++++++++++++++++ src/Environment_TEST.cc | 43 +++++++++++++++++++- 3 files changed, 155 insertions(+), 2 deletions(-) diff --git a/include/gz/utils/Environment.hh b/include/gz/utils/Environment.hh index 8ac06a4..a6dfa5f 100644 --- a/include/gz/utils/Environment.hh +++ b/include/gz/utils/Environment.hh @@ -22,6 +22,7 @@ #include #include +#include namespace gz { @@ -66,7 +67,48 @@ bool GZ_UTILS_VISIBLE setenv( /// \return True if the variable was unset or false otherwise. bool GZ_UTILS_VISIBLE unsetenv(const std::string &_name); -} +/// \brief Unset all environment variables +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `env` or `setenv` +/// +/// \return True if the environment was unset or false otherwise. +bool GZ_UTILS_VISIBLE clearenv(); + +/// \brief Type alias for a collection of environment variables +using EnvironmentMap = std::unordered_map; + +/// \brief Retrieve all current environment variables +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `setenv` or `unsetenv` +/// +/// \return A collection of current environment variables +EnvironmentMap GZ_UTILS_VISIBLE env(); + +/// \brief Set the environment variable '_name'. +/// +/// Note: On Windows setting an empty string (_value=="") +/// is the equivalent of unsetting the variable. +// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `env` or `unsetenv` +/// +/// \param[in] _vars Collection of environment variables to set +/// \return True if all variables were set or false otherwise. +bool GZ_UTILS_VISIBLE setenv(const EnvironmentMap &_vars); + +/// \brief Print the entire current environment to a string +/// +/// This prints each variable in the form KEY=VALUE\n +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `setenv` or `unsetenv` +/// +/// \return A string containing all environment variables +std::string printenv(); + +} // namespace GZ_UTILS_VERSION_NAMESPACE } // namespace utils } // namespace gz diff --git a/src/Environment.cc b/src/Environment.cc index 63e0746..3300845 100644 --- a/src/Environment.cc +++ b/src/Environment.cc @@ -20,6 +20,8 @@ #include #include +extern char ** environ; + namespace gz { namespace utils @@ -98,6 +100,74 @@ bool unsetenv(const std::string &_name) #endif return true; } + +///////////////////////////////////////////////// +bool clearenv() +{ + ::clearenv(); + return true; + /* + bool success = true; + for (const auto &[key, value] : env()) + { + success &= unsetenv(key); + } + return success; + */ } + +///////////////////////////////////////////////// +EnvironmentMap env() +{ + // Portable method for reading environment variables + // Ref: https://stackoverflow.com/a/71483564/460065 + char **currentEnv {nullptr}; +#if defined(WIN) && (_MSC_VER >= 1900) + currentEnv = *__p_environ(); +#else + currentEnv = environ; +#endif + + // In the case that clearenv() was just called + // currentEnv will be nullptr + if (currentEnv == nullptr) + return {}; + + EnvironmentMap ret; + for (; *currentEnv; ++currentEnv) + { + std::string var(*currentEnv); + auto key = var.substr(0, var.find('=')); + var.erase(0, var.find('=') + 1); + ret[key] = var; + } + return ret; } + +///////////////////////////////////////////////// +bool setenv(const EnvironmentMap &_vars) +{ + bool success = true; + for (const auto &[key, value] : _vars) + { + success &= setenv(key, value); + } + return success; +} + +///////////////////////////////////////////////// +std::string printenv() +{ + std::string ret; + for (const auto &[key, value] : env()) + { + ret.append(key); + ret.append("="); + ret.append(value); + ret.append("\n"); + } + return ret; } +} // namespace GZ_UTILS_VERSION_NAMESPACE +} // namespace utils +} // namespace gz diff --git a/src/Environment_TEST.cc b/src/Environment_TEST.cc index 8168153..24da134 100644 --- a/src/Environment_TEST.cc +++ b/src/Environment_TEST.cc @@ -26,6 +26,8 @@ using namespace gz; ///////////////////////////////////////////////// TEST(Environment, emptyENV) { + gz::utils::clearenv(); + std::string var; EXPECT_FALSE(utils::env("!!SHOULD_NOT_EXIST!!", var)); EXPECT_TRUE(var.empty()); @@ -34,6 +36,8 @@ TEST(Environment, emptyENV) ///////////////////////////////////////////////// TEST(Environment, envSet) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_SET"; ASSERT_TRUE(utils::setenv(key, "VALUE")); @@ -67,6 +71,8 @@ TEST(Environment, envSet) ///////////////////////////////////////////////// TEST(Environment, envUnset) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_UNSET"; ASSERT_TRUE(utils::unsetenv(key)); @@ -94,8 +100,10 @@ TEST(Environment, envUnset) } ///////////////////////////////////////////////// -TEST(Util_TEST, envSetEmpty) +TEST(Environment, envSetEmpty) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_SET_EMPTY"; ASSERT_TRUE(utils::setenv(key, "")); @@ -133,3 +141,36 @@ TEST(Util_TEST, envSetEmpty) } ASSERT_TRUE(utils::unsetenv(key)); } + +///////////////////////////////////////////////// +TEST(Environment, envGetCollection) +{ + gz::utils::clearenv(); + auto currentEnv = gz::utils::env(); + EXPECT_EQ(currentEnv.size(), 0); + + ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL")); + + currentEnv = gz::utils::env(); + EXPECT_EQ(currentEnv.size(), 3); + + EXPECT_EQ(currentEnv["GZ_FOO_KEY"], "GZ_FOO_VAL"); + EXPECT_EQ(currentEnv["GZ_BAR_KEY"], "GZ_BAR_VAL"); + EXPECT_EQ(currentEnv["GZ_BAZ_KEY"], "GZ_BAZ_VAL"); +} + +///////////////////////////////////////////////// +TEST(Environment, printenv) +{ + gz::utils::clearenv(); + EXPECT_EQ(gz::utils::printenv(), ""); + + ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL")); + + EXPECT_EQ(gz::utils::printenv(), + "GZ_BAZ_KEY=GZ_BAZ_VAL\nGZ_BAR_KEY=GZ_BAR_VAL\nGZ_FOO_KEY=GZ_FOO_VAL\n"); +}