aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/cpp/util/BUILD1
-rw-r--r--src/tools/runfiles/BUILD1
-rw-r--r--src/tools/runfiles/runfiles.cc83
-rw-r--r--src/tools/runfiles/runfiles.h8
-rw-r--r--src/tools/runfiles/runfiles_test.cc207
5 files changed, 296 insertions, 4 deletions
diff --git a/src/main/cpp/util/BUILD b/src/main/cpp/util/BUILD
index 0c97a045e5..11db4ca047 100644
--- a/src/main/cpp/util/BUILD
+++ b/src/main/cpp/util/BUILD
@@ -47,6 +47,7 @@ cc_library(
":ijar",
"//src/test/cpp/util:__pkg__",
"//src/tools/launcher:__subpackages__",
+ "//src/tools/runfiles:__pkg__",
"//src/tools/singlejar:__pkg__",
"//third_party/def_parser:__pkg__",
],
diff --git a/src/tools/runfiles/BUILD b/src/tools/runfiles/BUILD
index c6ec162139..d3545fd3e7 100644
--- a/src/tools/runfiles/BUILD
+++ b/src/tools/runfiles/BUILD
@@ -64,6 +64,7 @@ cc_test(
srcs = ["runfiles_test.cc"],
deps = [
":cc-runfiles",
+ "//src/main/cpp/util:file",
"//third_party:gtest",
],
)
diff --git a/src/tools/runfiles/runfiles.cc b/src/tools/runfiles/runfiles.cc
index 9ff1f3f4c5..1de2f1a1f4 100644
--- a/src/tools/runfiles/runfiles.cc
+++ b/src/tools/runfiles/runfiles.cc
@@ -13,9 +13,14 @@
// limitations under the License.
#include "tools/runfiles/runfiles.h"
+#include <fstream>
+#include <map>
+#include <sstream>
+
namespace bazel {
namespace runfiles {
+using std::map;
using std::string;
namespace {
@@ -39,7 +44,31 @@ class RunfilesImpl : public Runfiles {
virtual ~RunfilesImpl() {}
};
-// TODO(laszlocsomor): derive a ManifestBased class from RunfilesImpl.
+// Runfiles implementation that parses a runfiles-manifest to look up runfiles.
+class ManifestBased : public RunfilesImpl {
+ public:
+ // Returns a new `ManifestBased` instance.
+ // Reads the file at `manifest_path` to build a map of the runfiles.
+ // Returns nullptr upon failure.
+ static ManifestBased* Create(const string& manifest_path, string* error);
+
+ string RlocationChecked(const string& path) const override;
+
+ private:
+ ManifestBased(const string& manifest_path, map<string, string>&& runfiles_map)
+ : manifest_path_(manifest_path), runfiles_map_(runfiles_map) {}
+
+ ManifestBased(const ManifestBased&) = delete;
+ ManifestBased(ManifestBased&&) = delete;
+ ManifestBased& operator=(const ManifestBased&) = delete;
+ ManifestBased& operator=(ManifestBased&&) = delete;
+
+ static bool ParseManifest(const string& path, map<string, string>* result,
+ string* error);
+
+ const string manifest_path_;
+ const map<string, string> runfiles_map_;
+};
// Runfiles implementation that appends runfiles paths to the runfiles root.
class DirectoryBased : public RunfilesImpl {
@@ -77,6 +106,53 @@ string RunfilesImpl::Rlocation(const string& path) const {
return RlocationChecked(path);
}
+ManifestBased* ManifestBased::Create(const string& manifest_path,
+ string* error) {
+ map<string, string> runfiles;
+ return ParseManifest(manifest_path, &runfiles, error)
+ ? new ManifestBased(manifest_path, std::move(runfiles))
+ : nullptr;
+}
+
+string ManifestBased::RlocationChecked(const string& path) const {
+ const auto value = runfiles_map_.find(path);
+ return std::move(value == runfiles_map_.end() ? string() : value->second);
+}
+
+bool ManifestBased::ParseManifest(const string& path,
+ map<string, string>* result, string* error) {
+ std::ifstream stm(path);
+ if (!stm.is_open()) {
+ if (error) {
+ std::ostringstream err;
+ err << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): cannot open runfiles manifest \"" << path << "\"";
+ *error = err.str();
+ }
+ return false;
+ }
+ string line;
+ std::getline(stm, line);
+ size_t line_count = 1;
+ while (!line.empty()) {
+ string::size_type idx = line.find_first_of(' ');
+ if (idx == string::npos) {
+ if (error) {
+ std::ostringstream err;
+ err << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): bad runfiles manifest entry in \"" << path << "\" line #"
+ << line_count << ": \"" << line << "\"";
+ *error = err.str();
+ }
+ return false;
+ }
+ (*result)[line.substr(0, idx)] = line.substr(idx + 1);
+ std::getline(stm, line);
+ ++line_count;
+ }
+ return true;
+}
+
string DirectoryBased::RlocationChecked(const string& path) const {
return std::move(runfiles_path_ + "/" + path);
}
@@ -89,6 +165,11 @@ bool TestOnly_IsAbsolute(const string& path) { return IsAbsolute(path); }
} // namespace testing
+Runfiles* Runfiles::CreateManifestBased(const string& manifest_path,
+ string* error) {
+ return ManifestBased::Create(manifest_path, error);
+}
+
Runfiles* Runfiles::CreateDirectoryBased(const string& directory_path,
string* error) {
// Note: `error` is intentionally unused because we don't expect any errors
diff --git a/src/tools/runfiles/runfiles.h b/src/tools/runfiles/runfiles.h
index c15dec39e0..821cc1aa6f 100644
--- a/src/tools/runfiles/runfiles.h
+++ b/src/tools/runfiles/runfiles.h
@@ -32,8 +32,12 @@ class Runfiles {
// Runfiles* Create(const string& argv0, string* error);
// TODO(laszlocsomor): implement:
// vector<pair<string, string>> EnvVars();
- // TODO(laszlocsomor): implement:
- // Runfiles* CreateManifestBased(const string& path, string* error);
+
+ // Returns a new manifest-based `Runfiles` instance.
+ // Returns nullptr on error. If `error` is provided, the method prints an
+ // error message into it.
+ static Runfiles* CreateManifestBased(const std::string& manifest_path,
+ std::string* error = nullptr);
// Returns a new directory-based `Runfiles` instance.
// Returns nullptr on error. If `error` is provided, the method prints an
diff --git a/src/tools/runfiles/runfiles_test.cc b/src/tools/runfiles/runfiles_test.cc
index e286fa370f..408dfde2e1 100644
--- a/src/tools/runfiles/runfiles_test.cc
+++ b/src/tools/runfiles/runfiles_test.cc
@@ -14,10 +14,17 @@
#include "src/tools/runfiles/runfiles.h"
+#ifdef COMPILER_MSVC
+#include <windows.h>
+#endif // COMPILER_MSVC
+
+#include <fstream>
#include <memory>
#include <string>
+#include <vector>
#include "gtest/gtest.h"
+#include "src/main/cpp/util/file.h"
#define _T(x) #x
#define T(x) _T(x)
@@ -27,10 +34,127 @@ namespace bazel {
namespace runfiles {
namespace {
+using bazel::runfiles::testing::TestOnly_IsAbsolute;
+using std::cerr;
+using std::endl;
+using std::pair;
using std::string;
using std::unique_ptr;
+using std::vector;
+
+class RunfilesTest : public ::testing::Test {
+ protected:
+ // Create a temporary file that is deleted with the destructor.
+ class MockFile {
+ public:
+ // Create an empty file with the given name under $TEST_TMPDIR.
+ static MockFile* Create(const string& name);
+
+ // Create a file with the given name and contents under $TEST_TMPDIR.
+ // The method ensures to create all parent directories, so `name` is allowed
+ // to contain directory components.
+ static MockFile* Create(const string& name, const vector<string>& lines);
+
+ ~MockFile();
+ const string& Path() const { return path_; }
+
+ private:
+ MockFile(const string& path) : path_(path) {}
+ MockFile(const MockFile&) = delete;
+ MockFile(MockFile&&) = delete;
+ MockFile& operator=(const MockFile&) = delete;
+ MockFile& operator=(MockFile&&) = delete;
+
+ const string path_;
+ };
+
+ static string GetTemp();
+};
+
+string RunfilesTest::GetTemp() {
+#ifdef COMPILER_MSVC
+ DWORD size = ::GetEnvironmentVariableA("TEST_TMPDIR", NULL, 0);
+ if (size == 0) {
+ return std::move(string()); // unset or empty envvar
+ }
+ unique_ptr<char[]> value(new char[size]);
+ ::GetEnvironmentVariableA("TEST_TMPDIR", value.get(), size);
+ return std::move(string(value.get()));
+#else
+ char* result = getenv("TEST_TMPDIR");
+ return result != NULL ? std::move(string(result)) : std::move(string());
+#endif
+}
+
+RunfilesTest::MockFile* RunfilesTest::MockFile::Create(const string& name) {
+ return Create(name, vector<string>());
+}
+
+RunfilesTest::MockFile* RunfilesTest::MockFile::Create(
+ const string& name, const vector<string>& lines) {
+ if (name.find("..") != string::npos || TestOnly_IsAbsolute(name)) {
+ cerr << "WARNING: " << __FILE__ << "(" << __LINE__ << "): bad name: \""
+ << name << "\"" << endl;
+ return nullptr;
+ }
+
+ string tmp(std::move(RunfilesTest::GetTemp()));
+ if (tmp.empty()) {
+ cerr << "WARNING: " << __FILE__ << "(" << __LINE__
+ << "): $TEST_TMPDIR is empty" << endl;
+ return nullptr;
+ }
+ string path(tmp + "/" + name);
+ string dirname = blaze_util::Dirname(path);
+ if (!blaze_util::MakeDirectories(dirname, 0777)) {
+ cerr << "WARNING: " << __FILE__ << "(" << __LINE__ << "): MakeDirectories("
+ << dirname << ") failed" << endl;
+ return nullptr;
+ }
+
+ auto stm = std::ofstream(path);
+ for (auto i : lines) {
+ stm << i << std::endl;
+ }
+ return new MockFile(path);
+}
+
+RunfilesTest::MockFile::~MockFile() { std::remove(path_.c_str()); }
+
+TEST_F(RunfilesTest, CannotCreateManifestBasedRunfilesDueToBadManifest) {
+ unique_ptr<MockFile> mf(
+ MockFile::Create("foo" LINE() ".runfiles_manifest", {"a b", "nospace"}));
+ EXPECT_TRUE(mf != nullptr);
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::CreateManifestBased(mf->Path(), &error));
+ ASSERT_EQ(r, nullptr);
+ EXPECT_NE(error.find("bad runfiles manifest entry"), string::npos);
+ EXPECT_NE(error.find("line #2: \"nospace\""), string::npos);
+}
+
+TEST_F(RunfilesTest, ManifestBasedRunfilesRlocation) {
+ unique_ptr<MockFile> mf(
+ MockFile::Create("foo" LINE() ".runfiles_manifest", {"a/b c/d"}));
+ EXPECT_TRUE(mf != nullptr);
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::CreateManifestBased(mf->Path(), &error));
+ ASSERT_NE(r, nullptr);
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+ EXPECT_EQ(r->Rlocation("c/d"), "");
+ EXPECT_EQ(r->Rlocation(""), "");
+ EXPECT_EQ(r->Rlocation("foo"), "");
+ EXPECT_EQ(r->Rlocation("foo/"), "");
+ EXPECT_EQ(r->Rlocation("foo/bar"), "");
+ EXPECT_EQ(r->Rlocation("foo/.."), "");
+ EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
+ EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
+ EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
+}
-TEST(RunfilesTest, DirectoryBasedRunfilesRlocation) {
+TEST_F(RunfilesTest, DirectoryBasedRunfilesRlocation) {
string error;
unique_ptr<Runfiles> r(Runfiles::CreateDirectoryBased("whatever", &error));
ASSERT_NE(r, nullptr);
@@ -48,6 +172,87 @@ TEST(RunfilesTest, DirectoryBasedRunfilesRlocation) {
EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
}
+TEST_F(RunfilesTest, FailsToCreateManifestBasedBecauseManifestDoesNotExist) {
+ string error;
+ unique_ptr<Runfiles> r(
+ Runfiles::CreateManifestBased("non-existent-file", &error));
+ ASSERT_EQ(r, nullptr);
+ EXPECT_NE(error.find("cannot open runfiles manifest"), string::npos);
+}
+
+TEST_F(RunfilesTest, MockFileTest) {
+ {
+ unique_ptr<MockFile> mf(MockFile::Create(string("foo" LINE() "/..")));
+ EXPECT_TRUE(mf == nullptr);
+ }
+
+ {
+ unique_ptr<MockFile> mf(MockFile::Create(string("/Foo" LINE())));
+ EXPECT_TRUE(mf == nullptr);
+ }
+
+ {
+ unique_ptr<MockFile> mf(MockFile::Create(string("C:/Foo" LINE())));
+ EXPECT_TRUE(mf == nullptr);
+ }
+
+ string path;
+ {
+ unique_ptr<MockFile> mf(MockFile::Create(string("foo" LINE() "/bar1/qux")));
+ EXPECT_TRUE(mf != nullptr);
+ path = mf->Path();
+
+ std::ifstream stm(path);
+ EXPECT_TRUE(stm.good());
+ string actual;
+ stm >> actual;
+ EXPECT_TRUE(actual.empty());
+ }
+ {
+ std::ifstream stm(path);
+ EXPECT_FALSE(stm.good());
+ }
+
+ {
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE() "/bar2/qux"), vector<string>()));
+ EXPECT_TRUE(mf != nullptr);
+ path = mf->Path();
+
+ std::ifstream stm(path);
+ EXPECT_TRUE(stm.good());
+ string actual;
+ stm >> actual;
+ EXPECT_TRUE(actual.empty());
+ }
+ {
+ std::ifstream stm(path);
+ EXPECT_FALSE(stm.good());
+ }
+
+ {
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE() "/bar3/qux"),
+ {"hello world", "you are beautiful"}));
+ EXPECT_TRUE(mf != nullptr);
+ path = mf->Path();
+
+ std::ifstream stm(path);
+ EXPECT_TRUE(stm.good());
+ string actual;
+ std::getline(stm, actual);
+ EXPECT_EQ("hello world", actual);
+ std::getline(stm, actual);
+ EXPECT_EQ("you are beautiful", actual);
+ std::getline(stm, actual);
+ EXPECT_EQ("", actual);
+ }
+ {
+ std::ifstream stm(path);
+ EXPECT_FALSE(stm.good());
+ }
+}
+
} // namespace
} // namespace runfiles
} // namespace bazel