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