Implement environment variable substitution for unix platforms
I want to be able to use this to redirect the bazel config file based
on the environment, for integration with our existing
environment-based mechanisms for selecting build configurations.
I found some opinions in comments which said this was too difficult,
which I have amended.
Closes #7318.
PiperOrigin-RevId: 237467945
diff --git a/src/main/cpp/BUILD b/src/main/cpp/BUILD
index 1af898d..7135055 100644
--- a/src/main/cpp/BUILD
+++ b/src/main/cpp/BUILD
@@ -125,13 +125,18 @@
# The system bazelrc can be voided by setting BAZEL_SYSTEM_BAZELRC_PATH to
# /dev/null.
copts = select({
- # We need to escape for multiple levels, here, this becomes
+ # For windows platforms, this can include environment
+ # variables in the form %ProgramData%. We need to escape for
+ # multiple levels, here, this becomes
# /DBAZEL_SYSTEM_BAZELRC_PATH="%%ProgramData%%/bazel.bazelrc"',
- # and the double % get reduced down to 1 by the compiler. A forward
- # slash is used because \b is a special character, backspace.
+ # and the double % get reduced down to 1 by the compiler. A
+ # forward slash is used because \b is a special character,
+ # backspace.
"//src/conditions:windows": [
"/DBAZEL_SYSTEM_BAZELRC_PATH#\\\"%%ProgramData%%/bazel.bazelrc\\\"",
],
+ # For posix platforms, this can include environment variables in the
+ # form ${var_name}. Braces are required.
"//conditions:default": [
"-DBAZEL_SYSTEM_BAZELRC_PATH=\\\"/etc/bazel.bazelrc\\\"",
],
diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc
index 181afd2..6c4e3f4 100644
--- a/src/main/cpp/option_processor.cc
+++ b/src/main/cpp/option_processor.cc
@@ -220,7 +220,7 @@
std::string FindSystemWideRc(const std::string& system_bazelrc_path) {
const std::string path =
- blaze_util::MakeAbsoluteAndResolveWindowsEnvvars(system_bazelrc_path);
+ blaze_util::MakeAbsoluteAndResolveEnvvars(system_bazelrc_path);
if (blaze_util::CanReadFile(path)) {
return path;
}
@@ -325,11 +325,11 @@
// Get the system rc (unless --nosystem_rc).
if (SearchNullaryOption(cmd_line->startup_args, "system_rc", true)) {
- // MakeAbsoluteAndResolveWindowsEnvvars will standardize the form of the
+ // MakeAbsoluteAndResolveEnvvars will standardize the form of the
// provided path. This also means we accept relative paths, which is
// is convenient for testing.
const std::string system_rc =
- blaze_util::MakeAbsoluteAndResolveWindowsEnvvars(system_bazelrc_path_);
+ blaze_util::MakeAbsoluteAndResolveEnvvars(system_bazelrc_path_);
rc_files.push_back(system_rc);
}
diff --git a/src/main/cpp/util/path_platform.h b/src/main/cpp/util/path_platform.h
index c3f098c..329380f 100644
--- a/src/main/cpp/util/path_platform.h
+++ b/src/main/cpp/util/path_platform.h
@@ -54,15 +54,16 @@
// MakeAbsolute("C:/foo") ---> "C:/foo"
std::string MakeAbsolute(const std::string &path);
-// Returns the given path in absolute form, taking into account a possible
-// starting environment variable on the windows platform, so that we can
-// accept standard path variables like %USERPROFILE%. We do not support
-// unix-style envvars here: recreating that logic is error-prone and not
-// worthwhile, since they are less critical to standard paths as in Windows.
+// Returns the given path in absolute form, taking into account a
+// possible starting environment variable, so that we can accept
+// standard path variables like %USERPROFILE% or ${BAZEL}. For
+// simplicity, we implement only those two forms, not $BAZEL.
//
// MakeAbsolute("foo") in wd "/bar" --> "/bar/foo"
-// MakeAbsolute("%USERPROFILE%/foo") --> "C:\Users\bazel-user\foo"
-std::string MakeAbsoluteAndResolveWindowsEnvvars(const std::string &path);
+// MakeAbsoluteAndResolveEnvvars("%USERPROFILE%/foo") -->
+// "C:\Users\bazel-user\foo"
+// MakeAbsoluteAndResolveEnvvars("${BAZEL}/foo") --> "/opt/bazel/foo"
+std::string MakeAbsoluteAndResolveEnvvars(const std::string &path);
// TODO(bazel-team) consider changing the path(_platform) header split to be a
// path.h and path_windows.h split, which would make it clearer what functions
diff --git a/src/main/cpp/util/path_posix.cc b/src/main/cpp/util/path_posix.cc
index 9c0b440..11577c3 100644
--- a/src/main/cpp/util/path_posix.cc
+++ b/src/main/cpp/util/path_posix.cc
@@ -16,6 +16,7 @@
#include <limits.h> // PATH_MAX
+#include <stdlib.h> // getenv
#include <string.h> // strncmp
#include <unistd.h> // access, open, close, fsync
#include "src/main/cpp/util/errors.h"
@@ -67,8 +68,30 @@
return JoinPath(blaze_util::GetCwd(), path);
}
-std::string MakeAbsoluteAndResolveWindowsEnvvars(const std::string &path) {
- return MakeAbsolute(path);
+std::string ResolveEnvvars(const std::string &path) {
+ std::string result = path;
+ size_t start = 0;
+ while ((start = result.find("${", start)) != std::string::npos) {
+ // Just match to the next }
+ size_t end = result.find("}", start + 1);
+ if (end == std::string::npos) {
+ BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+ << "ResolveEnvvars(" << path << "): incomplete variable at position "
+ << start;
+ }
+ // Extract the variable name
+ const std::string name = result.substr(start + 2, end - start - 2);
+ // Get the value from the environment
+ const char *c_value = getenv(name.c_str());
+ const std::string value = std::string(c_value ? c_value : "");
+ result.replace(start, end - start + 1, value);
+ start += value.length();
+ }
+ return result;
+}
+
+std::string MakeAbsoluteAndResolveEnvvars(const std::string &path) {
+ return MakeAbsolute(ResolveEnvvars(path));
}
} // namespace blaze_util
diff --git a/src/main/cpp/util/path_windows.cc b/src/main/cpp/util/path_windows.cc
index e1bde22..0f42276 100644
--- a/src/main/cpp/util/path_windows.cc
+++ b/src/main/cpp/util/path_windows.cc
@@ -88,7 +88,7 @@
WstringToCstring(RemoveUncPrefixMaybe(wpath.c_str())).get());
}
-std::string MakeAbsoluteAndResolveWindowsEnvvars(const std::string& path) {
+std::string MakeAbsoluteAndResolveEnvvars(const std::string& path) {
// Get the size of the expanded string, so we know how big of a buffer to
// provide. The returned size includes the null terminator.
std::unique_ptr<CHAR[]> resolved(new CHAR[MAX_PATH]);
diff --git a/src/test/cpp/util/path_posix_test.cc b/src/test/cpp/util/path_posix_test.cc
index df79b59..7c08490 100644
--- a/src/test/cpp/util/path_posix_test.cc
+++ b/src/test/cpp/util/path_posix_test.cc
@@ -159,14 +159,28 @@
EXPECT_EQ(MakeAbsolute(""), "");
}
-TEST(PathPosixTest, MakeAbsoluteAndResolveWindowsEnvvars) {
- // Check that Unix-style envvars are not resolved.
- EXPECT_EQ(MakeAbsoluteAndResolveWindowsEnvvars("$PATH"),
- JoinPath(GetCwd(), "$PATH"));
- EXPECT_EQ(MakeAbsoluteAndResolveWindowsEnvvars("${PATH}"),
- JoinPath(GetCwd(), "${PATH}"));
+TEST(PathPosixTest, MakeAbsoluteAndResolveEnvvars) {
+ // Check that Unix-style envvars are resolved.
+ const std::string tmpdir = getenv("TEST_TMPDIR");
+ const std::string expected_tmpdir_bar = ConvertPath(tmpdir + "/bar");
+ setenv("PATH_POSIX_TEST_ENV", "${TEST_TMPDIR}", 1);
+
+ // Using an existing environment variable
+ EXPECT_EQ(expected_tmpdir_bar,
+ MakeAbsoluteAndResolveEnvvars("${TEST_TMPDIR}/bar"));
+ // Using an undefined environment variable (case-sensitive)
+ EXPECT_EQ("/bar", MakeAbsoluteAndResolveEnvvars("${test_tmpdir}/bar"));
+
+ // This style of variable is not supported
+ EXPECT_EQ(JoinPath(GetCwd(), "$TEST_TMPDIR/bar"),
+ MakeAbsoluteAndResolveEnvvars("$TEST_TMPDIR/bar"));
+
+ // Only one layer of variables is expanded, we do not recurse
+ EXPECT_EQ(JoinPath(GetCwd(), "${TEST_TMPDIR}/bar"),
+ MakeAbsoluteAndResolveEnvvars("${PATH_POSIX_TEST_ENV}/bar"));
+
// Check that Windows-style envvars are not resolved when not on Windows.
- EXPECT_EQ(MakeAbsoluteAndResolveWindowsEnvvars("%PATH%"),
+ EXPECT_EQ(MakeAbsoluteAndResolveEnvvars("%PATH%"),
JoinPath(GetCwd(), "%PATH%"));
}
diff --git a/src/test/cpp/util/path_windows_test.cc b/src/test/cpp/util/path_windows_test.cc
index 6cd027e..ef29083 100644
--- a/src/test/cpp/util/path_windows_test.cc
+++ b/src/test/cpp/util/path_windows_test.cc
@@ -392,7 +392,7 @@
EXPECT_EQ("", MakeAbsolute(""));
}
-TEST(PathWindowsTest, MakeAbsoluteAndResolveWindowsEnvvars_WithTmpdir) {
+TEST(PathWindowsTest, MakeAbsoluteAndResolveEnvvars_WithTmpdir) {
// We cannot test the system-default paths like %ProgramData% because these
// are wiped from the test environment. TestTmpdir is set by Bazel though,
// so serves as a fine substitute.
@@ -402,20 +402,20 @@
const std::string expected_tmpdir_bar = ConvertPath(tmpdir + "\\bar");
EXPECT_EQ(expected_tmpdir_bar,
- MakeAbsoluteAndResolveWindowsEnvvars("%TEST_TMPDIR%\\bar"));
+ MakeAbsoluteAndResolveEnvvars("%TEST_TMPDIR%\\bar"));
EXPECT_EQ(expected_tmpdir_bar,
- MakeAbsoluteAndResolveWindowsEnvvars("%Test_Tmpdir%\\bar"));
+ MakeAbsoluteAndResolveEnvvars("%Test_Tmpdir%\\bar"));
EXPECT_EQ(expected_tmpdir_bar,
- MakeAbsoluteAndResolveWindowsEnvvars("%test_tmpdir%\\bar"));
+ MakeAbsoluteAndResolveEnvvars("%test_tmpdir%\\bar"));
EXPECT_EQ(expected_tmpdir_bar,
- MakeAbsoluteAndResolveWindowsEnvvars("%test_tmpdir%/bar"));
+ MakeAbsoluteAndResolveEnvvars("%test_tmpdir%/bar"));
}
-TEST(PathWindowsTest, MakeAbsoluteAndResolveWindowsEnvvars_LongPaths) {
+TEST(PathWindowsTest, MakeAbsoluteAndResolveEnvvars_LongPaths) {
const std::string long_path = "c:\\" + std::string(MAX_PATH, 'a');
blaze::SetEnv("long", long_path);
- EXPECT_EQ(long_path, MakeAbsoluteAndResolveWindowsEnvvars("%long%"));
+ EXPECT_EQ(long_path, MakeAbsoluteAndResolveEnvvars("%long%"));
}
} // namespace blaze_util