Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new functions for manipulating the environment #114

Merged
merged 10 commits into from
Nov 7, 2023
44 changes: 43 additions & 1 deletion include/gz/utils/Environment.hh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <gz/utils/Export.hh>

#include <string>
#include <unordered_map>

namespace gz
{
Expand Down Expand Up @@ -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<std::string, std::string>;

/// \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
/// NOLINTNEXTLINE - This is incorrectly parsed as a global variable
std::string GZ_UTILS_VISIBLE printenv();
} // namespace GZ_UTILS_VERSION_NAMESPACE
} // namespace utils
} // namespace gz

Expand Down
94 changes: 94 additions & 0 deletions src/Environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,19 @@

#include <gz/utils/Environment.hh>

#include <algorithm>
#include <cstdlib>
#include <string>
#include <vector>

#ifdef _WIN32
#include <windows.h>
#include <processenv.h>
#endif

#ifndef _WIN32
extern char ** environ;
#endif

namespace gz
{
Expand Down Expand Up @@ -98,6 +109,89 @@ bool unsetenv(const std::string &_name)
#endif
return true;
}

/////////////////////////////////////////////////
bool clearenv()
{
bool success = true;
#if __linux__
if (0 != ::clearenv())
{
success = false;
}
#else
// Windows and macOS don't have clearenv
// so iterate and clear one-by-one
for (const auto &[key, value] : env())
{
success &= unsetenv(key);
}
#endif
return success;

}

/////////////////////////////////////////////////
EnvironmentMap env()
{
EnvironmentMap ret;

// Helper function to split KEY=VAL
auto split = [](const std::string &_inp)
{
return std::make_pair(
_inp.substr(0, _inp.find('=')),
_inp.substr(_inp.find('=') + 1));
};

char **currentEnv = nullptr;
#ifdef _WIN32
currentEnv = *__p__environ();
#else
currentEnv = environ;
#endif
// In the case that clearenv() was just called
// currentEnv will be nullptr
if (currentEnv == nullptr)
return {};

for (; *currentEnv; ++currentEnv)
{
ret.emplace(split(*currentEnv));
}
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;
// Variables are in an unordered_map as we generally don't
// care, but for printing sort for consistent display
auto currentEnv = env();
auto sorted = std::vector<std::pair<std::string, std::string>>(
currentEnv.begin(), currentEnv.end());
std::sort(sorted.begin(), sorted.end());
for (const auto &[key, value] : sorted)
{
ret.append(key);
ret.append("=");
ret.append(value);
ret.append("\n");
}
return ret;
}
} // namespace GZ_UTILS_VERSION_NAMESPACE
} // namespace utils
} // namespace gz
44 changes: 43 additions & 1 deletion src/Environment_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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"));

Expand Down Expand Up @@ -67,6 +71,8 @@ TEST(Environment, envSet)
/////////////////////////////////////////////////
TEST(Environment, envUnset)
{
gz::utils::clearenv();

const auto key = "GZ_ENV_UNSET";
ASSERT_TRUE(utils::unsetenv(key));

Expand Down Expand Up @@ -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, ""));
Expand Down Expand Up @@ -133,3 +141,37 @@ 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"));

// Always returned in sorted order
EXPECT_EQ(gz::utils::printenv(),
"GZ_BAR_KEY=GZ_BAR_VAL\nGZ_BAZ_KEY=GZ_BAZ_VAL\nGZ_FOO_KEY=GZ_FOO_VAL\n");
}