From 9f2b052d93bfd188687f28fe6771f390d3626936 Mon Sep 17 00:00:00 2001 From: Laszlo Csomor Date: Wed, 2 May 2018 08:07:14 -0700 Subject: [PATCH] runfiles,cc: add a method to discover runfiles The new method discovers the runfiles manifest and runfiles directory using the values of the RUNFILES_MANIFEST_FILE and RUNFILES_DIR envvars (if specified), and if needed, also looks for them next to argv[0]. See https://github.com/bazelbuild/bazel/issues/4460 Change-Id: Ie9af0a92aa6879b1d9afdee2789f625de1b0d595 Closes #5140. Change-Id: Ie9af0a92aa6879b1d9afdee2789f625de1b0d595 PiperOrigin-RevId: 195093387 --- tools/cpp/runfiles/runfiles.cc | 54 +++++++++++++++ tools/cpp/runfiles/runfiles.h | 21 ++++++ tools/cpp/runfiles/runfiles_test.cc | 103 ++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) diff --git a/tools/cpp/runfiles/runfiles.cc b/tools/cpp/runfiles/runfiles.cc index 2f25b53ae5cdb4..1e6f0583bc95e7 100644 --- a/tools/cpp/runfiles/runfiles.cc +++ b/tools/cpp/runfiles/runfiles.cc @@ -348,6 +348,60 @@ Runfiles* Runfiles::CreateDirectoryBased(const string& directory_path, return new DirectoryBased(directory_path); } +bool Runfiles::PathsFrom(const string& argv0, + function env_lookup, + function is_runfiles_manifest, + function is_runfiles_directory, + string* out_manifest, string* out_directory) { + out_manifest->clear(); + out_directory->clear(); + string mf = env_lookup("RUNFILES_MANIFEST_FILE"); + string dir = env_lookup("RUNFILES_DIR"); + + bool mfValid = is_runfiles_manifest(mf); + bool dirValid = is_runfiles_directory(dir); + + if (!mfValid && !dirValid) { + mf = argv0 + ".runfiles/MANIFEST"; + dir = argv0 + ".runfiles"; + mfValid = is_runfiles_manifest(mf); + dirValid = is_runfiles_directory(dir); + if (!mfValid) { + mf = argv0 + ".runfiles_manifest"; + mfValid = is_runfiles_manifest(mf); + } + } + + if (!mfValid && !dirValid) { + return false; + } + + if (!mfValid) { + mf = dir + "/MANIFEST"; + mfValid = is_runfiles_manifest(mf); + if (!mfValid) { + mf = dir + "_manifest"; + mfValid = is_runfiles_manifest(mf); + } + } + + if (!dirValid) { + static const size_t kSubstrLen = 9; // "_manifest" or "/MANIFEST" + dir = mf.substr(0, mf.size() - kSubstrLen); + dirValid = is_runfiles_directory(dir); + } + + if (mfValid) { + *out_manifest = mf; + } + + if (dirValid) { + *out_directory = dir; + } + + return true; +} + } // namespace runfiles } // namespace cpp } // namespace tools diff --git a/tools/cpp/runfiles/runfiles.h b/tools/cpp/runfiles/runfiles.h index 2fad6df3dcc330..d161ef16e306f3 100644 --- a/tools/cpp/runfiles/runfiles.h +++ b/tools/cpp/runfiles/runfiles.h @@ -138,6 +138,27 @@ class Runfiles { // need to use runfiles. virtual std::vector > EnvVars() const = 0; + // Computes the path of the runfiles manifest and the runfiles directory. + // + // If the method finds both a valid manifest and valid directory according to + // `is_runfiles_manifest` and `is_runfiles_directory`, then the method sets + // the corresponding values to `out_manifest` and `out_directory` and returns + // true. + // + // If the method only finds a valid manifest or a valid directory, but not + // both, then it sets the corresponding output variable (`out_manifest` or + // `out_directory`) to the value while clearing the other output variable. The + // method still returns true in this case. + // + // If the method cannot find either a valid manifest or valid directory, it + // clears both output variables and returns false. + static bool PathsFrom( + const std::string& argv0, + std::function env_lookup, + std::function is_runfiles_manifest, + std::function is_runfiles_directory, + std::string* out_manifest, std::string* out_directory); + protected: Runfiles() {} diff --git a/tools/cpp/runfiles/runfiles_test.cc b/tools/cpp/runfiles/runfiles_test.cc index 2177e02afc8697..02135abfb3858a 100644 --- a/tools/cpp/runfiles/runfiles_test.cc +++ b/tools/cpp/runfiles/runfiles_test.cc @@ -486,6 +486,109 @@ TEST_F(RunfilesTest, IsAbsolute) { EXPECT_TRUE(TestOnly_IsAbsolute("x:\\")); } +TEST_F(RunfilesTest, PathsFromEnvVars) { + string mf, dir; + + static const function kEnvVars = [](string key) { + if (key == "TEST_SRCDIR") { + return "always ignored"; + } else if (key == "RUNFILES_MANIFEST_FILE") { + return "mock1/MANIFEST"; + } else if (key == "RUNFILES_DIR") { + return "mock2"; + } else { + return ""; + } + }; + + // Both envvars have a valid value. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, + [](const string& path) { return path == "mock1/MANIFEST"; }, + [](const string& path) { return path == "mock2"; }, &mf, &dir)); + EXPECT_EQ(mf, "mock1/MANIFEST"); + EXPECT_EQ(dir, "mock2"); + + // RUNFILES_MANIFEST_FILE is invalid but RUNFILES_DIR is good and there's a + // runfiles manifest in the runfiles directory. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, + [](const string& path) { return path == "mock2/MANIFEST"; }, + [](const string& path) { return path == "mock2"; }, &mf, &dir)); + EXPECT_EQ(mf, "mock2/MANIFEST"); + EXPECT_EQ(dir, "mock2"); + + // RUNFILES_MANIFEST_FILE is invalid but RUNFILES_DIR is good, but there's no + // runfiles manifest in the runfiles directory. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, [](const string& path) { return false; }, + [](const string& path) { return path == "mock2"; }, &mf, &dir)); + EXPECT_EQ(mf, ""); + EXPECT_EQ(dir, "mock2"); + + // RUNFILES_DIR is invalid but RUNFILES_MANIFEST_FILE is good, and it is in + // a valid-looking runfiles directory. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, + [](const string& path) { return path == "mock1/MANIFEST"; }, + [](const string& path) { return path == "mock1"; }, &mf, &dir)); + EXPECT_EQ(mf, "mock1/MANIFEST"); + EXPECT_EQ(dir, "mock1"); + + // RUNFILES_DIR is invalid but RUNFILES_MANIFEST_FILE is good, but it is not + // in any valid-looking runfiles directory. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, + [](const string& path) { return path == "mock1/MANIFEST"; }, + [](const string& path) { return false; }, &mf, &dir)); + EXPECT_EQ(mf, "mock1/MANIFEST"); + EXPECT_EQ(dir, ""); + + // Both envvars are invalid, but there's a manifest in a runfiles directory + // next to argv0, however there's no other content in the runfiles directory. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, + [](const string& path) { return path == "argv0.runfiles/MANIFEST"; }, + [](const string& path) { return false; }, &mf, &dir)); + EXPECT_EQ(mf, "argv0.runfiles/MANIFEST"); + EXPECT_EQ(dir, ""); + + // Both envvars are invalid, but there's a manifest next to argv0. There's + // no runfiles tree anywhere. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, + [](const string& path) { return path == "argv0.runfiles_manifest"; }, + [](const string& path) { return false; }, &mf, &dir)); + EXPECT_EQ(mf, "argv0.runfiles_manifest"); + EXPECT_EQ(dir, ""); + + // Both envvars are invalid, but there's a valid manifest next to argv0, and a + // valid runfiles directory (without a manifest in it). + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, + [](const string& path) { return path == "argv0.runfiles_manifest"; }, + [](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir)); + EXPECT_EQ(mf, "argv0.runfiles_manifest"); + EXPECT_EQ(dir, "argv0.runfiles"); + + // Both envvars are invalid, but there's a valid runfiles directory next to + // argv0, though no manifest in it. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, [](const string& path) { return false; }, + [](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir)); + EXPECT_EQ(mf, ""); + EXPECT_EQ(dir, "argv0.runfiles"); + + // Both envvars are invalid, but there's a valid runfiles directory next to + // argv0 with a valid manifest in it. + EXPECT_TRUE(Runfiles::PathsFrom( + "argv0", kEnvVars, + [](const string& path) { return path == "argv0.runfiles/MANIFEST"; }, + [](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir)); + EXPECT_EQ(mf, "argv0.runfiles/MANIFEST"); + EXPECT_EQ(dir, "argv0.runfiles"); +} + } // namespace } // namespace runfiles } // namespace cpp