diff options
-rw-r--r-- | src/main/cpp/util/BUILD | 1 | ||||
-rw-r--r-- | src/tools/runfiles/BUILD | 1 | ||||
-rw-r--r-- | src/tools/runfiles/runfiles.cc | 83 | ||||
-rw-r--r-- | src/tools/runfiles/runfiles.h | 8 | ||||
-rw-r--r-- | src/tools/runfiles/runfiles_test.cc | 207 |
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 |