runfiles,cc: C++ runfiles library in @bazel_tools

Add the C++ runfiles library to
@bazel_tools//tools/cpp/runfiles:runfiles.

RELNOTES[NEW]: C++,runfiles: to access data-dependencies (runfiles) in C++ programs, use the runfiles library built into Bazel. For usage info, see https://github.com/bazelbuild/bazel/blob/master/tools/cpp/runfiles/runfiles.h

Change-Id: I5057a9f477289eea7244c60105e77fc71652a817

Closes #5293.

Change-Id: I90cba6fa4c6595c838ae42f9d2c17548c8387e5d
PiperOrigin-RevId: 198531849
diff --git a/src/test/py/bazel/runfiles_test.py b/src/test/py/bazel/runfiles_test.py
index 86c40b9..9131cd2 100644
--- a/src/test/py/bazel/runfiles_test.py
+++ b/src/test/py/bazel/runfiles_test.py
@@ -32,20 +32,24 @@
                   "\n".join(stderr))
 
   def _AssertRunfilesLibraryInBazelToolsRepo(self, family, lang_name):
-    for s, t, exe in [
-        ("WORKSPACE.mock", "WORKSPACE", False),
-        ("foo/BUILD.mock", "foo/BUILD", False),
-        ("foo/foo.py", "foo/foo.py", True),
-        ("foo/Foo.java", "foo/Foo.java", False),
-        ("foo/foo.sh", "foo/foo.sh", True),
-        ("foo/datadep/hello.txt", "foo/datadep/hello.txt", False),
-        ("bar/BUILD.mock", "bar/BUILD", False),
-        ("bar/bar.py", "bar/bar.py", True),
-        ("bar/bar-py-data.txt", "bar/bar-py-data.txt", False),
-        ("bar/Bar.java", "bar/Bar.java", False),
-        ("bar/bar-java-data.txt", "bar/bar-java-data.txt", False),
-        ("bar/bar.sh", "bar/bar.sh", True),
-        ("bar/bar-sh-data.txt", "bar/bar-sh-data.txt", False)]:
+    for s, t, exe in [("WORKSPACE.mock", "WORKSPACE",
+                       False), ("foo/BUILD.mock", "foo/BUILD",
+                                False), ("foo/foo.py", "foo/foo.py", True),
+                      ("foo/Foo.java", "foo/Foo.java",
+                       False), ("foo/foo.sh", "foo/foo.sh",
+                                True), ("foo/foo.cc", "foo/foo.cc", False),
+                      ("foo/datadep/hello.txt", "foo/datadep/hello.txt",
+                       False), ("bar/BUILD.mock", "bar/BUILD",
+                                False), ("bar/bar.py", "bar/bar.py", True),
+                      ("bar/bar-py-data.txt", "bar/bar-py-data.txt",
+                       False), ("bar/Bar.java", "bar/Bar.java",
+                                False), ("bar/bar-java-data.txt",
+                                         "bar/bar-java-data.txt", False),
+                      ("bar/bar.sh", "bar/bar.sh",
+                       True), ("bar/bar-sh-data.txt", "bar/bar-sh-data.txt",
+                               False), ("bar/bar.cc", "bar/bar.cc",
+                                        False), ("bar/bar-cc-data.txt",
+                                                 "bar/bar-cc-data.txt", False)]:
       self.CopyFile(
           self.Rlocation("io_bazel/src/test/py/bazel/testdata/runfiles_test/" +
                          s), t, exe)
@@ -54,7 +58,10 @@
     self.AssertExitCode(exit_code, 0, stderr)
     bazel_bin = stdout[0]
 
-    exit_code, _, stderr = self.RunBazel(["build", "//foo:runfiles-" + family])
+    exit_code, _, stderr = self.RunBazel([
+        "build", "--verbose_failures", "--experimental_shortened_obj_file_path",
+        "//foo:runfiles-" + family
+    ])
     self.AssertExitCode(exit_code, 0, stderr)
 
     if test_base.TestBase.IsWindows():
@@ -67,7 +74,8 @@
     exit_code, stdout, stderr = self.RunProgram(
         [bin_path], env_add={"TEST_SRCDIR": "__ignore_me__"})
     self.AssertExitCode(exit_code, 0, stderr)
-    if len(stdout) != 8:
+    # 10 output lines: 2 from foo-<family>, and 2 from each of bar-<lang>.
+    if len(stdout) != 10:
       self.fail("stdout: %s" % stdout)
 
     self.assertEqual(stdout[0], "Hello %s Foo!" % lang_name)
@@ -82,7 +90,7 @@
 
     i = 2
     for lang in [("py", "Python", "bar.py"), ("java", "Java", "Bar.java"),
-                 ("sh", "Bash", "bar.sh")]:
+                 ("sh", "Bash", "bar.sh"), ("cc", "C++", "bar.cc")]:
       self.assertEqual(stdout[i], "Hello %s Bar!" % lang[1])
       six.assertRegex(self, stdout[i + 1],
                       "^rloc=.*/bar/bar-%s-data.txt" % lang[0])
@@ -105,6 +113,9 @@
   def testBashRunfilesLibraryInBazelToolsRepo(self):
     self._AssertRunfilesLibraryInBazelToolsRepo("sh", "Bash")
 
+  def testCppRunfilesLibraryInBazelToolsRepo(self):
+    self._AssertRunfilesLibraryInBazelToolsRepo("cc", "C++")
+
   def testRunfilesLibrariesFindRunfilesWithoutEnvvars(self):
     for s, t, exe in [
         ("WORKSPACE.mock", "WORKSPACE", False),
@@ -115,6 +126,8 @@
         ("bar/bar-java-data.txt", "bar/bar-java-data.txt", False),
         ("bar/bar.sh", "bar/bar.sh", True),
         ("bar/bar-sh-data.txt", "bar/bar-sh-data.txt", False),
+        ("bar/bar.cc", "bar/bar.cc", False),
+        ("bar/bar-cc-data.txt", "bar/bar-cc-data.txt", False),
     ]:
       self.CopyFile(
           self.Rlocation("io_bazel/src/test/py/bazel/testdata/runfiles_test/" +
@@ -124,12 +137,14 @@
     self.AssertExitCode(exit_code, 0, stderr)
     bazel_bin = stdout[0]
 
-    exit_code, _, stderr = self.RunBazel(
-        ["build", "//bar:bar-py", "//bar:bar-java", "//bar:bar-sh"])
+    exit_code, _, stderr = self.RunBazel([
+        "build", "--verbose_failures", "--experimental_shortened_obj_file_path",
+        "//bar:bar-py", "//bar:bar-java", "//bar:bar-sh", "//bar:bar-cc"
+    ])
     self.AssertExitCode(exit_code, 0, stderr)
 
     for lang in [("py", "Python", "bar.py"), ("java", "Java", "Bar.java"),
-                 ("sh", "Bash", "bar.sh")]:
+                 ("sh", "Bash", "bar.sh"), ("cc", "C++", "bar.cc")]:
       if test_base.TestBase.IsWindows():
         bin_path = os.path.join(bazel_bin, "bar/bar-%s.exe" % lang[0])
       else:
@@ -171,6 +186,8 @@
         ("bar/bar-java-data.txt", "bar/bar-java-data.txt", False),
         ("bar/bar.sh", "bar/bar.sh", True),
         ("bar/bar-sh-data.txt", "bar/bar-sh-data.txt", False),
+        ("bar/bar.cc", "bar/bar.cc", False),
+        ("bar/bar-cc-data.txt", "bar/bar-cc-data.txt", False),
     ]:
       self.CopyFile(
           self.Rlocation("io_bazel/src/test/py/bazel/testdata/runfiles_test/" +
@@ -180,10 +197,11 @@
     self.AssertExitCode(exit_code, 0, stderr)
     bazel_bin = stdout[0]
 
-    for lang in [("java", "Java"),
-                 ("sh", "Bash")]:  # TODO(laszlocsomor): add "cc" when ready.
+    for lang in [("java", "Java"), ("sh", "Bash"), ("cc", "C++")]:
       exit_code, _, stderr = self.RunBazel([
-          "build", "--experimental_enable_runfiles=no", "//bar:bar-" + lang[0]
+          "build", "--verbose_failures",
+          "--experimental_shortened_obj_file_path",
+          "--experimental_enable_runfiles=no", "//bar:bar-" + lang[0]
       ])
       self.AssertExitCode(exit_code, 0, stderr)
 
diff --git a/src/test/py/bazel/testdata/runfiles_test/bar/BUILD.mock b/src/test/py/bazel/testdata/runfiles_test/bar/BUILD.mock
index ab97f8d..930884b 100644
--- a/src/test/py/bazel/testdata/runfiles_test/bar/BUILD.mock
+++ b/src/test/py/bazel/testdata/runfiles_test/bar/BUILD.mock
@@ -22,3 +22,10 @@
     data = ["bar-sh-data.txt"],
     deps = ["@bazel_tools//tools/bash/runfiles"],
 )
+
+cc_binary(
+    name = "bar-cc",
+    srcs = ["bar.cc"],
+    data = ["bar-cc-data.txt"],
+    deps = ["@bazel_tools//tools/cpp/runfiles"],
+)
diff --git a/src/test/py/bazel/testdata/runfiles_test/bar/bar-cc-data.txt b/src/test/py/bazel/testdata/runfiles_test/bar/bar-cc-data.txt
new file mode 100644
index 0000000..09db5f4
--- /dev/null
+++ b/src/test/py/bazel/testdata/runfiles_test/bar/bar-cc-data.txt
@@ -0,0 +1 @@
+data for bar.cc
diff --git a/src/test/py/bazel/testdata/runfiles_test/bar/bar.cc b/src/test/py/bazel/testdata/runfiles_test/bar/bar.cc
new file mode 100644
index 0000000..1101b1d
--- /dev/null
+++ b/src/test/py/bazel/testdata/runfiles_test/bar/bar.cc
@@ -0,0 +1,59 @@
+// Copyright 2018 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.
+
+// Mock C++ binary, only used in tests.
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "tools/cpp/runfiles/runfiles.h"
+
+namespace {
+
+using bazel::tools::cpp::runfiles::Runfiles;
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::ifstream;
+using std::string;
+using std::unique_ptr;
+
+bool is_file(const string& path) {
+  if (path.empty()) {
+    return false;
+  }
+  return ifstream(path).is_open();
+}
+
+int _main(int argc, char** argv) {
+  cout << "Hello C++ Bar!" << endl;
+  string error;
+  unique_ptr<Runfiles> runfiles(Runfiles::Create(argv[0], &error));
+  if (runfiles == nullptr) {
+    cerr << "ERROR[" << __FILE__ << "]: " << error << endl;
+    return 1;
+  }
+  string path = runfiles->Rlocation("foo_ws/bar/bar-cc-data.txt");
+  if (!is_file(path)) {
+    return 1;
+  }
+  cout << "rloc=" << path << endl;
+  return 0;
+}
+
+}  // namespace
+
+int main(int argc, char** argv) { return _main(argc, argv); }
diff --git a/src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock b/src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock
index f35b840..42c7d84 100644
--- a/src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock
+++ b/src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock
@@ -6,6 +6,7 @@
         "//bar:bar-java",
         "//bar:bar-py",
         "//bar:bar-sh",
+        "//bar:bar-cc",
     ],
     main = "foo.py",
     deps = ["@bazel_tools//tools/python/runfiles"],
@@ -19,6 +20,7 @@
         "//bar:bar-py",
         "//bar:bar-java",
         "//bar:bar-sh",
+        "//bar:bar-cc",
     ],
     main_class = "Foo",
     deps = ["@bazel_tools//tools/runfiles:java-runfiles"],
@@ -32,6 +34,24 @@
         "//bar:bar-java",
         "//bar:bar-py",
         "//bar:bar-sh",
+        "//bar:bar-cc",
     ],
     deps = ["@bazel_tools//tools/bash/runfiles"],
 )
+
+cc_binary(
+    name = "runfiles-cc",
+    srcs = ["foo.cc"],
+    data = [
+        "datadep/hello.txt",
+        "//bar:bar-java",
+        "//bar:bar-py",
+        "//bar:bar-sh",
+        "//bar:bar-cc",
+    ],
+    copts = select({
+        "@bazel_tools//src/conditions:windows": ["/DIS_WINDOWS=1"],
+        "//conditions:default": [],
+    }),
+    deps = ["@bazel_tools//tools/cpp/runfiles"],
+)
diff --git a/src/test/py/bazel/testdata/runfiles_test/foo/Foo.java b/src/test/py/bazel/testdata/runfiles_test/foo/Foo.java
index 3571de1..898cb03 100644
--- a/src/test/py/bazel/testdata/runfiles_test/foo/Foo.java
+++ b/src/test/py/bazel/testdata/runfiles_test/foo/Foo.java
@@ -28,7 +28,7 @@
     Runfiles r = Runfiles.create();
     System.out.println("rloc=" + r.rlocation("foo_ws/foo/datadep/hello.txt"));
 
-    for (String lang : new String[] {"py", "java", "sh"}) {
+    for (String lang : new String[] {"py", "java", "sh", "cc"}) {
       String path = r.rlocation(childBinaryName(lang));
       if (path == null || path.isEmpty()) {
         throw new IOException("cannot find child binary for " + lang);
diff --git a/src/test/py/bazel/testdata/runfiles_test/foo/foo.cc b/src/test/py/bazel/testdata/runfiles_test/foo/foo.cc
new file mode 100644
index 0000000..07ec268
--- /dev/null
+++ b/src/test/py/bazel/testdata/runfiles_test/foo/foo.cc
@@ -0,0 +1,170 @@
+// Copyright 2018 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.
+
+// Mock C++ binary, only used in tests.
+
+#include "tools/cpp/runfiles/runfiles.h"
+
+#ifdef IS_WINDOWS
+#include <windows.h>
+#else  // not IS_WINDOWS
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif  // IS_WINDOWS
+
+#include <string.h>
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+
+namespace {
+
+using bazel::tools::cpp::runfiles::Runfiles;
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::ifstream;
+using std::string;
+using std::unique_ptr;
+
+string child_binary_name(const char* lang) {
+#ifdef IS_WINDOWS
+  return string("foo_ws/bar/bar-") + lang + ".exe";
+#else
+  return string("foo_ws/bar/bar-") + lang;
+#endif  // IS_WINDOWS
+}
+
+bool is_file(const string& path) {
+  if (path.empty()) {
+    return false;
+  }
+  return ifstream(path).is_open();
+}
+
+#ifdef IS_WINDOWS
+unique_ptr<char[]> create_env_block(const Runfiles& runfiles) {
+  char systemroot[MAX_PATH];
+  DWORD systemroot_size =
+      GetEnvironmentVariable("SYSTEMROOT", systemroot, MAX_PATH);
+  if (!systemroot_size || systemroot_size == MAX_PATH) {
+    cerr << "ERROR[" << __FILE__ << "]: %SYSTEMROOT% is too long" << endl;
+    return std::move(unique_ptr<char[]>());
+  }
+
+  size_t total_envblock_size =
+      10 /* the string "SYSTEMROOT" */ + 1 /* equals-sign */ + systemroot_size +
+      1 /* null-terminator */ +
+      2 /* two null-terminator at the end of the environment block */;
+
+  // count total size of the environment block
+  const auto envvars = runfiles.EnvVars();
+  for (const auto i : envvars) {
+    total_envblock_size += i.first.size() + 1 /* equals-sign */ +
+                           i.second.size() + 1 /* null-terminator */;
+  }
+
+  // copy environment variables from `envvars`
+  unique_ptr<char[]> result(new char[total_envblock_size]);
+  char* p = result.get();
+  for (const auto i : envvars) {
+    strncpy(p, i.first.c_str(), i.first.size());
+    p += i.first.size();
+    *p++ = '=';
+    strncpy(p, i.second.c_str(), i.second.size());
+    p += i.second.size();
+    *p++ = '\0';
+  }
+
+  // SYSTEMROOT environment variable
+  strncpy(p, "SYSTEMROOT=", 11);
+  p += 11;
+  strncpy(p, systemroot, systemroot_size);
+  p += systemroot_size;
+  *p++ = '\0';
+
+  // final two null-terminators
+  p[0] = '\0';
+  p[1] = '\0';
+
+  return std::move(result);
+}
+#endif
+
+int _main(int argc, char** argv) {
+  cout << "Hello C++ Foo!" << endl;
+  string error;
+  unique_ptr<Runfiles> runfiles(Runfiles::Create(argv[0], &error));
+  if (runfiles == nullptr) {
+    cerr << "ERROR[" << __FILE__ << "]: " << error << endl;
+    return 1;
+  }
+  string path = runfiles->Rlocation("foo_ws/foo/datadep/hello.txt");
+  if (!is_file(path)) {
+    return 1;
+  }
+  cout << "rloc=" << path << endl;
+
+#ifdef IS_WINDOWS
+  auto envvars = create_env_block(*runfiles);
+#else
+  const auto envvars = runfiles->EnvVars();
+#endif
+
+  // Run a subprocess, propagate the runfiles envvar to it. The subprocess will
+  // use this process's runfiles manifest or runfiles directory.
+  for (const char* lang : {"py", "java", "sh", "cc"}) {
+    const string bar = runfiles->Rlocation(child_binary_name(lang));
+
+    unique_ptr<char[]> argv0(new char[bar.size() + 1]);
+    strncpy(argv0.get(), bar.c_str(), bar.size());
+    argv0.get()[bar.size()] = 0;
+
+#ifdef IS_WINDOWS
+    PROCESS_INFORMATION processInfo;
+    STARTUPINFOA startupInfo = {0};
+    BOOL ok = CreateProcessA(NULL, argv0.get(), NULL, NULL, FALSE, 0,
+                             envvars.get(), NULL, &startupInfo, &processInfo);
+    if (!ok) {
+      DWORD err = GetLastError();
+      fprintf(stderr, "ERROR: CreateProcessA error: %d\n", err);
+      return 1;
+    }
+    WaitForSingleObject(processInfo.hProcess, INFINITE);
+    CloseHandle(processInfo.hProcess);
+    CloseHandle(processInfo.hThread);
+#else
+    char* args[2] = {argv0.get(), NULL};
+    pid_t child = fork();
+    if (child) {
+      int status;
+      waitpid(child, &status, 0);
+    } else {
+      for (const auto i : envvars) {
+        setenv(i.first.c_str(), i.second.c_str(), 1);
+      }
+      execv(args[0], args);
+    }
+#endif
+  }
+  return 0;
+}
+
+}  // namespace
+
+int main(int argc, char** argv) { return _main(argc, argv); }
diff --git a/src/test/py/bazel/testdata/runfiles_test/foo/foo.py b/src/test/py/bazel/testdata/runfiles_test/foo/foo.py
index d526531..e6422b7 100644
--- a/src/test/py/bazel/testdata/runfiles_test/foo/foo.py
+++ b/src/test/py/bazel/testdata/runfiles_test/foo/foo.py
@@ -53,7 +53,7 @@
   else:
     env = {}
   env.update(r.EnvVars())
-  for lang in ["py", "java", "sh"]:
+  for lang in ["py", "java", "sh", "cc"]:
     p = subprocess.Popen(
         [r.Rlocation(ChildBinaryName(lang))],
         env=env,
diff --git a/src/test/py/bazel/testdata/runfiles_test/foo/foo.sh b/src/test/py/bazel/testdata/runfiles_test/foo/foo.sh
index ec934ed..687d612 100755
--- a/src/test/py/bazel/testdata/runfiles_test/foo/foo.sh
+++ b/src/test/py/bazel/testdata/runfiles_test/foo/foo.sh
@@ -68,7 +68,7 @@
   if is_windows; then
     export SYSTEMROOT="${SYSTEMROOT:-}"
   fi
-  for lang in py java sh; do
+  for lang in py java sh cc; do
     child_bin="$(rlocation "$(child_binary_name $lang)")"
     if ! "$child_bin"; then
       echo >&2 "ERROR: error running bar-$lang"
diff --git a/tools/cpp/runfiles/BUILD b/tools/cpp/runfiles/BUILD
index a8f47b9..7373b03 100644
--- a/tools/cpp/runfiles/BUILD
+++ b/tools/cpp/runfiles/BUILD
@@ -17,6 +17,7 @@
     srcs = [
         "BUILD.tools",
         "runfiles.cc",
+        "runfiles.h",
     ],
     visibility = ["//tools:__pkg__"],
 )
diff --git a/tools/cpp/runfiles/BUILD.tools b/tools/cpp/runfiles/BUILD.tools
index 8f0a9cf..ef9518c 100644
--- a/tools/cpp/runfiles/BUILD.tools
+++ b/tools/cpp/runfiles/BUILD.tools
@@ -1,10 +1,8 @@
 # This package will host the C++ runfiles library when it's finally released.
 
-# TODO(laszlocsomor): uncomment the cc_library below when the C++ runfiles library is ready to be
-# released.
-# cc_library(
-#     name = "runfiles",
-#     srcs = ["runfiles.cc"],
-#     hdrs = ["runfiles.h"],
-#     visibility = ["//visibility:public"],
-# )
+cc_library(
+    name = "runfiles",
+    srcs = ["runfiles.cc"],
+    hdrs = ["runfiles.h"],
+    visibility = ["//visibility:public"],
+)
diff --git a/tools/cpp/runfiles/runfiles.h b/tools/cpp/runfiles/runfiles.h
index 8c016a3..b2bfe30 100644
--- a/tools/cpp/runfiles/runfiles.h
+++ b/tools/cpp/runfiles/runfiles.h
@@ -44,13 +44,20 @@
 //
 //   std::unique_ptr<Runfiles> runfiles(Runfiles::Create(argv[0], &error));
 //
-//   for (const auto i : runfiles->EnvVars()) {
-//     setenv(i.first, i.second, 1);
-//   }
 //   std::string path = runfiles->Rlocation("path/to/binary"));
 //   if (!path.empty()) {
+//     ... // create "args" argument vector for execv
+//     const auto envvars = runfiles->EnvVars();
 //     pid_t child = fork();
-//     ...
+//     if (child) {
+//       int status;
+//       waitpid(child, &status, 0);
+//     } else {
+//       for (const auto i : envvars) {
+//         setenv(i.first.c_str(), i.second.c_str(), 1);
+//       }
+//       execv(args[0], args);
+//     }
 
 #ifndef TOOLS_CPP_RUNFILES_RUNFILES_H_
 #define TOOLS_CPP_RUNFILES_RUNFILES_H_ 1