Bazel client, Windows: implement envvar handling

Implement GetEnv, SetEnv, UnsetEnv, and use it in
some locations, plus add some tests.

See https://github.com/bazelbuild/bazel/issues/2107

--
PiperOrigin-RevId: 144808435
MOS_MIGRATED_REVID=144808435
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index 0d8bdb2..23cf626 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -284,15 +284,15 @@
   // for TEMP under MSYS, though it can retrieve WINDIR.
 
   WCHAR buffer[kWindowsPathBufferSize] = {0};
-  if (!GetTempPathW(kWindowsPathBufferSize, buffer)) {
+  if (!::GetTempPathW(kWindowsPathBufferSize, buffer)) {
     PrintErrorW(L"GetTempPathW");
     pdie(255, "Could not retrieve the temp directory path");
   }
   return string(blaze_util::WstringToCstring(buffer).get());
 #else  // not COMPILER_MSVC
   for (const char* i : {"TMPDIR", "TEMPDIR", "TMP", "TEMP"}) {
-    char* tmpdir = getenv(i);
-    if (tmpdir != NULL && strlen(tmpdir) > 0) {
+    string tmpdir(GetEnv(i));
+    if (!tmpdir.empty()) {
       return tmpdir;
     }
   }
@@ -351,8 +351,8 @@
 }
 
 string GetDefaultHostJavabase() {
-  const char *javahome = getenv("JAVA_HOME");
-  if (javahome == NULL) {
+  string javahome(GetEnv("JAVA_HOME"));
+  if (javahome.empty()) {
     die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
         "Error: JAVA_HOME not set.");
   }
@@ -1123,33 +1123,36 @@
 }
 
 string GetEnv(const string& name) {
+  DWORD size = ::GetEnvironmentVariableA(name.c_str(), NULL, 0);
+  if (size == 0) {
 #ifdef COMPILER_MSVC
-  // TODO(bazel-team): implement this.
-  pdie(255, "blaze::GetEnv is not implemented on Windows");
-  return "";
+    return string();  // unset or empty envvar
 #else  // not COMPILER_MSVC
-  char* result = getenv(name.c_str());
-  return result != NULL ? string(result) : "";
+    char* result = getenv(name.c_str());
+    return result != NULL ? string(result) : string();
 #endif  // COMPILER_MSVC
+  }
+
+  unique_ptr<char[]> value(new char[size]);
+  ::GetEnvironmentVariableA(name.c_str(), value.get(), size);
+  return string(value.get());
 }
 
 void SetEnv(const string& name, const string& value) {
-#ifdef COMPILER_MSVC
-  // TODO(bazel-team): implement this.
-  pdie(255, "blaze::SetEnv is not implemented on Windows");
-#else  // not COMPILER_MSVC
-  setenv(name.c_str(), value.c_str(), 1);
-#endif  // COMPILER_MSVC
+  if (value.empty()) {
+    ::SetEnvironmentVariableA(name.c_str(), NULL);
+#ifndef COMPILER_MSVC
+    unsetenv(name.c_str());
+#endif  // not COMPILER_MSVC
+  } else {
+    ::SetEnvironmentVariableA(name.c_str(), value.c_str());
+#ifndef COMPILER_MSVC
+    setenv(name.c_str(), value.c_str(), 1);
+#endif  // not COMPILER_MSVC
+  }
 }
 
-void UnsetEnv(const string& name) {
-#ifdef COMPILER_MSVC
-  // TODO(bazel-team): implement this.
-  pdie(255, "blaze::UnsetEnv is not implemented on Windows");
-#else  // not COMPILER_MSVC
-  unsetenv(name.c_str());
-#endif  // COMPILER_MSVC
-}
+void UnsetEnv(const string& name) { SetEnv(name, ""); }
 
 void SetupStdStreams() {
 #ifdef COMPILER_MSVC
diff --git a/src/test/cpp/BUILD b/src/test/cpp/BUILD
index acc5461..057c721 100644
--- a/src/test/cpp/BUILD
+++ b/src/test/cpp/BUILD
@@ -10,7 +10,18 @@
 
 cc_test(
     name = "blaze_util_test",
-    srcs = ["blaze_util_test.cc"],
+    srcs = select({
+        "//src:windows": [
+            "blaze_util_test.cc",
+            "blaze_util_windows_test.cc",
+        ],
+        "//src:windows_msvc": [
+            "blaze_util_windows_test.cc",
+        ],
+        "//conditions:default": [
+            "blaze_util_test.cc",
+        ],
+    }),
     deps = [
         "//src/main/cpp:blaze_util",
         "//src/main/cpp/util",
diff --git a/src/test/cpp/blaze_util_windows_test.cc b/src/test/cpp/blaze_util_windows_test.cc
new file mode 100644
index 0000000..e558617
--- /dev/null
+++ b/src/test/cpp/blaze_util_windows_test.cc
@@ -0,0 +1,157 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <windows.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "src/main/cpp/blaze_util.h"
+#include "src/main/cpp/blaze_util_platform.h"
+#include "src/main/cpp/util/strings.h"
+#include "gtest/gtest.h"
+
+namespace blaze {
+
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+// Asserts that the envvar named `key` is unset.
+// Exercises GetEnvironmentVariable{A,W}, both with `key` and its lower case
+// version, to make sure that envvar retrieval is case-insensitive (envvar names
+// are case-insensitive on Windows).
+//
+// This is a macro so the assertions will have the correct line number.
+#define ASSERT_ENVVAR_UNSET(/* const char* */ key)                            \
+  {                                                                           \
+    ASSERT_EQ(::GetEnvironmentVariableA(key, NULL, 0), (DWORD)0);             \
+    ASSERT_EQ(                                                                \
+        ::GetEnvironmentVariableA(blaze_util::AsLower(key).c_str(), NULL, 0), \
+        (DWORD)0);                                                            \
+    ASSERT_EQ(::GetEnvironmentVariableW(                                      \
+                  blaze_util::CstringToWstring(key).get(), NULL, 0),          \
+              (DWORD)0);                                                      \
+    ASSERT_EQ(::GetEnvironmentVariableW(blaze_util::CstringToWstring(         \
+                                            blaze_util::AsLower(key).c_str()) \
+                                            .get(),                           \
+                                        NULL, 0),                             \
+              (DWORD)0);                                                      \
+  }
+
+// Asserts that the envvar named `key` is set to the `expected` value.
+// Exercises GetEnvironmentVariable{A,W}, both with `key` and its lower case
+// version, to make sure that envvar retrieval is case-insensitive (envvar names
+// are case-insensitive on Windows).
+//
+// This is a macro so the assertions will have the correct line number.
+#define ASSERT_ENVVAR(/* const (char* | string&) */ _key,                    \
+                      /* const (char* | string&) */ _expected)               \
+  {                                                                          \
+    string key(_key);                                                        \
+    string expected(_expected);                                              \
+    DWORD size = ::GetEnvironmentVariableA(key.c_str(), NULL, 0);            \
+    ASSERT_GT(size, (DWORD)0);                                               \
+    unique_ptr<char[]> buf(new char[size]);                                  \
+                                                                             \
+    /* Assert that GetEnvironmentVariableA can retrieve the value. */        \
+    ASSERT_EQ(::GetEnvironmentVariableA(key.c_str(), buf.get(), size),       \
+              size - 1);                                                     \
+    ASSERT_EQ(string(buf.get()), expected);                                  \
+                                                                             \
+    /* Assert that envvar keys are case-insensitive. */                      \
+    string lkey(blaze_util::AsLower(key));                                   \
+    ASSERT_EQ(::GetEnvironmentVariableA(lkey.c_str(), buf.get(), size),      \
+              size - 1);                                                     \
+    ASSERT_EQ(string(buf.get()), expected);                                  \
+                                                                             \
+    /* Assert that GetEnvironmentVariableW can retrieve the value. */        \
+    wstring wkey(blaze_util::CstringToWstring(key.c_str()).get());           \
+    wstring wexpected(blaze_util::CstringToWstring(expected.c_str()).get()); \
+    size = ::GetEnvironmentVariableW(wkey.c_str(), NULL, 0);                 \
+    ASSERT_GT(size, (DWORD)0);                                               \
+    unique_ptr<WCHAR[]> wbuf(new WCHAR[size]);                               \
+    ASSERT_EQ(::GetEnvironmentVariableW(wkey.c_str(), wbuf.get(), size),     \
+              size - 1);                                                     \
+    ASSERT_EQ(wstring(wbuf.get()), wexpected);                               \
+                                                                             \
+    /* Assert that widechar envvar keys are case-insensitive. */             \
+    wstring wlkey(blaze_util::CstringToWstring(lkey.c_str()).get());         \
+    ASSERT_EQ(::GetEnvironmentVariableW(wlkey.c_str(), wbuf.get(), size),    \
+              size - 1);                                                     \
+    ASSERT_EQ(wstring(wbuf.get()), wexpected);                               \
+  }
+
+TEST(BlazeUtilWindowsTest, TestGetEnv) {
+  ASSERT_ENVVAR_UNSET("DOES_not_EXIST");
+
+  string actual(GetEnv("TEST_SRCDIR"));
+  ASSERT_NE(actual, "");
+
+  std::replace(actual.begin(), actual.end(), '/', '\\');
+  ASSERT_NE(actual.find(":\\"), string::npos);
+
+  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");
+  ASSERT_TRUE(::SetEnvironmentVariableA("Bazel_TEST_Key1", "some_VALUE"));
+  ASSERT_ENVVAR("Bazel_TEST_Key1", "some_VALUE");
+  ASSERT_TRUE(::SetEnvironmentVariableA("Bazel_TEST_Key1", NULL));
+
+  string long_string(MAX_PATH, 'a');
+  string long_key = string("Bazel_TEST_Key2_") + long_string;
+  string long_value = string("Bazel_TEST_Value2_") + long_string;
+
+  ASSERT_ENVVAR_UNSET(long_key.c_str());
+  ASSERT_TRUE(::SetEnvironmentVariableA(long_key.c_str(), long_value.c_str()));
+  ASSERT_ENVVAR(long_key, long_value);
+  ASSERT_TRUE(::SetEnvironmentVariableA(long_key.c_str(), NULL));
+}
+
+TEST(BlazeUtilWindowsTest, TestSetEnv) {
+  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");
+  SetEnv("Bazel_TEST_Key1", "some_VALUE");
+  ASSERT_ENVVAR("Bazel_TEST_Key1", "some_VALUE");
+  SetEnv("Bazel_TEST_Key1", "");
+  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");
+
+  string long_string(MAX_PATH, 'a');
+  string long_key = string("Bazel_TEST_Key2_") + long_string;
+  string long_value = string("Bazel_TEST_Value2_") + long_string;
+
+  ASSERT_ENVVAR_UNSET(long_key.c_str());
+  SetEnv(long_key, long_value);
+  ASSERT_ENVVAR(long_key.c_str(), long_value.c_str());
+  SetEnv(long_key, "");
+  ASSERT_ENVVAR_UNSET(long_key.c_str());
+}
+
+TEST(BlazeUtilWindowsTest, TestUnsetEnv) {
+  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");
+  SetEnv("Bazel_TEST_Key1", "some_VALUE");
+  ASSERT_ENVVAR("Bazel_TEST_Key1", "some_VALUE");
+  UnsetEnv("Bazel_TEST_Key1");
+  ASSERT_ENVVAR_UNSET("Bazel_TEST_Key1");
+
+  string long_string(MAX_PATH, 'a');
+  string long_key = string("Bazel_TEST_Key2_") + long_string;
+  string long_value = string("Bazel_TEST_Value2_") + long_string;
+
+  ASSERT_ENVVAR_UNSET(long_key.c_str());
+  SetEnv(long_key, long_value);
+  ASSERT_ENVVAR(long_key.c_str(), long_value.c_str());
+  UnsetEnv(long_key);
+  ASSERT_ENVVAR_UNSET(long_key.c_str());
+}
+
+}  // namespace blaze