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 @@
#endif
return true;
}

/////////////////////////////////////////////////
bool clearenv()
{
bool success = true;
#if __linux__
if (0 != ::clearenv())
{
success = false;

Check warning on line 120 in src/Environment.cc

View check run for this annotation

Codecov / codecov/patch

src/Environment.cc#L120

Added line #L120 was not covered by tests
}
#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)

Check warning on line 166 in src/Environment.cc

View check run for this annotation

Codecov / codecov/patch

src/Environment.cc#L166

Added line #L166 was not covered by tests
{
bool success = true;
for (const auto &[key, value] : _vars)

Check warning on line 169 in src/Environment.cc

View check run for this annotation

Codecov / codecov/patch

src/Environment.cc#L168-L169

Added lines #L168 - L169 were not covered by tests
{
success &= setenv(key, value);

Check warning on line 171 in src/Environment.cc

View check run for this annotation

Codecov / codecov/patch

src/Environment.cc#L171

Added line #L171 was not covered by tests
}
return success;

Check warning on line 173 in src/Environment.cc

View check run for this annotation

Codecov / codecov/patch

src/Environment.cc#L173

Added line #L173 was not covered by tests
}

/////////////////////////////////////////////////
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");
}
Loading