From 9dfb1ee2fc1b6e312105ad2062ec963d28b916e6 Mon Sep 17 00:00:00 2001 From: Laszlo Csomor Date: Mon, 26 Feb 2018 09:17:02 -0800 Subject: [PATCH] runfiles,C++: implement runfiles lib foundations Implement the foundation of the C++ runfiles library, along with a directory-based Runfiles implementation. Subsequent commits will add more feataures: - manifest-based runfiles handling - creating list of envvars to pass to child processes - automatic Runfiles creation based on argv[0] and the envvars of this process See https://github.com/bazelbuild/bazel/issues/4460 Change-Id: Id5a38619a1ae874499f04523863081559360410c PiperOrigin-RevId: 187031518 --- src/tools/runfiles/BUILD | 30 +++++++++ src/tools/runfiles/runfiles.cc | 101 ++++++++++++++++++++++++++++ src/tools/runfiles/runfiles.h | 88 ++++++++++++++++++++++++ src/tools/runfiles/runfiles_test.cc | 53 +++++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 src/tools/runfiles/runfiles.cc create mode 100644 src/tools/runfiles/runfiles.h create mode 100644 src/tools/runfiles/runfiles_test.cc diff --git a/src/tools/runfiles/BUILD b/src/tools/runfiles/BUILD index 5ac490d1e2e67b..c6ec162139a5ec 100644 --- a/src/tools/runfiles/BUILD +++ b/src/tools/runfiles/BUILD @@ -20,6 +20,8 @@ filegroup( name = "embedded_tools", srcs = [ "BUILD.tools", + "runfiles.cc", + "runfiles.h", "runfiles.py", "//src/tools/runfiles/java/com/google/devtools/build/runfiles:embedded_tools", ], @@ -38,6 +40,34 @@ py_test( deps = [":py-runfiles"], ) +cc_library( + name = "cc-runfiles", + srcs = ["runfiles.cc"], + hdrs = ["runfiles.h"], + # This library is available to clients under + # @bazel_tools//tools/runfiles:cc-runfiles, with the same source files. + # The include statement in runfiles.cc that includes runfiles.h must work + # both here in the //src/tools/runfiles package, and in the + # @bazel_tools//tools/runfiles package. + # runfiles.cc includes "tools/runfiles/runfiles.h" so we need to tell the + # compiler to prepend "src" to it so the include path is valid. + # Alternatively we could omit this "copts" attribute here and add some + # include path manipulating attributes to + # @bazel_tools//tools/runfiles:cc-runfiles instead -- that would work too, + # but I (laszlocsomor@) find this solution (i.e. the "copts" attribute on + # this rule) to be simpler. + copts = ["-Isrc"], +) + +cc_test( + name = "cc-runfiles-test", + srcs = ["runfiles_test.cc"], + deps = [ + ":cc-runfiles", + "//third_party:gtest", + ], +) + sh_library( name = "runfiles_sh_lib", srcs = ["runfiles.sh"], diff --git a/src/tools/runfiles/runfiles.cc b/src/tools/runfiles/runfiles.cc new file mode 100644 index 00000000000000..9ff1f3f4c5efb1 --- /dev/null +++ b/src/tools/runfiles/runfiles.cc @@ -0,0 +1,101 @@ +// 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. +#include "tools/runfiles/runfiles.h" + +namespace bazel { +namespace runfiles { + +using std::string; + +namespace { + +class RunfilesImpl : public Runfiles { + public: + // TODO(laszlocsomor): implement Create( + // const string& argv0, function env_lookup, string* + // error); + + string Rlocation(const string& path) const override; + + // Returns the runtime-location of a given runfile. + // + // This method assumes that the caller already validated the `path`. See + // Runfiles::Rlocation for requirements. + virtual string RlocationChecked(const string& path) const = 0; + + protected: + RunfilesImpl() {} + virtual ~RunfilesImpl() {} +}; + +// TODO(laszlocsomor): derive a ManifestBased class from RunfilesImpl. + +// Runfiles implementation that appends runfiles paths to the runfiles root. +class DirectoryBased : public RunfilesImpl { + public: + DirectoryBased(string runfiles_path) + : runfiles_path_(std::move(runfiles_path)) {} + string RlocationChecked(const string& path) const override; + + private: + DirectoryBased(const DirectoryBased&) = delete; + DirectoryBased(DirectoryBased&&) = delete; + DirectoryBased& operator=(const DirectoryBased&) = delete; + DirectoryBased& operator=(DirectoryBased&&) = delete; + + const string runfiles_path_; +}; + +bool IsAbsolute(const string& path) { + if (path.empty()) { + return false; + } + char c = path.front(); + return (c == '/') || (path.size() >= 3 && + ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && + path[1] == ':' && (path[2] == '\\' || path[2] == '/')); +} + +string RunfilesImpl::Rlocation(const string& path) const { + if (path.empty() || path.find("..") != string::npos) { + return std::move(string()); + } + if (IsAbsolute(path)) { + return path; + } + return RlocationChecked(path); +} + +string DirectoryBased::RlocationChecked(const string& path) const { + return std::move(runfiles_path_ + "/" + path); +} + +} // namespace + +namespace testing { + +bool TestOnly_IsAbsolute(const string& path) { return IsAbsolute(path); } + +} // namespace testing + +Runfiles* Runfiles::CreateDirectoryBased(const string& directory_path, + string* error) { + // Note: `error` is intentionally unused because we don't expect any errors + // here. We expect an `error` pointer so that we may use it in the future if + // need be, without having to change the API. + return new DirectoryBased(directory_path); +} + +} // namespace runfiles +} // namespace bazel diff --git a/src/tools/runfiles/runfiles.h b/src/tools/runfiles/runfiles.h new file mode 100644 index 00000000000000..c15dec39e08f60 --- /dev/null +++ b/src/tools/runfiles/runfiles.h @@ -0,0 +1,88 @@ +// 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. + +// Runfiles lookup library for Bazel-built C++ binaries and tests. +// +// TODO(laszlocsomor): add usage information and examples. + +#ifndef BAZEL_SRC_TOOLS_RUNFILES_RUNFILES_H_ +#define BAZEL_SRC_TOOLS_RUNFILES_RUNFILES_H_ 1 + +#include + +namespace bazel { +namespace runfiles { + +class Runfiles { + public: + virtual ~Runfiles() {} + + // TODO(laszlocsomor): implement: + // Runfiles* Create(const string& argv0, string* error); + // TODO(laszlocsomor): implement: + // vector> EnvVars(); + // TODO(laszlocsomor): implement: + // Runfiles* CreateManifestBased(const string& path, string* error); + + // Returns a new directory-based `Runfiles` instance. + // Returns nullptr on error. If `error` is provided, the method prints an + // error message into it. + static Runfiles* CreateDirectoryBased(const std::string& directory_path, + std::string* error = nullptr); + + // Returns the runtime path of a runfile. + // + // Runfiles are data-dependencies of Bazel-built binaries and tests. + // + // The returned path may not be valid. The caller should check the path's + // validity and that the path exists. + // + // The function may return an empty string. In that case the caller can be + // sure that the Runfiles object does not know about this data-dependency. + // + // Args: + // path: runfiles-root-relative path of the runfile; must not be empty and + // must not contain uplevel references. + // Returns: + // the path to the runfile, which the caller should check for existence, or + // an empty string if the method doesn't know about this runfile + virtual std::string Rlocation(const std::string& path) const = 0; + + protected: + Runfiles() {} + + private: + Runfiles(const Runfiles&) = delete; + Runfiles(Runfiles&&) = delete; + Runfiles& operator=(const Runfiles&) = delete; + Runfiles& operator=(Runfiles&&) = delete; +}; + +// The "testing" namespace contains functions that allow unit testing the code. +// Do not use these outside of runfiles_test.cc, because they may change without +// notice. +namespace testing { + +// For testing only. +// Returns true if `path` is an absolute Unix or Windows path. +// For Windows paths, this function does not regard drive-less absolute paths +// (i.e. absolute-on-current-drive, e.g. "\foo\bar") as absolute and returns +// false for these. +bool TestOnly_IsAbsolute(const std::string& path); + +} // namespace testing +} // namespace runfiles +} // namespace bazel + +#endif // BAZEL_SRC_TOOLS_RUNFILES_RUNFILES_H_ diff --git a/src/tools/runfiles/runfiles_test.cc b/src/tools/runfiles/runfiles_test.cc new file mode 100644 index 00000000000000..e286fa370ffd2b --- /dev/null +++ b/src/tools/runfiles/runfiles_test.cc @@ -0,0 +1,53 @@ +// 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. + +#include "src/tools/runfiles/runfiles.h" + +#include +#include + +#include "gtest/gtest.h" + +#define _T(x) #x +#define T(x) _T(x) +#define LINE() T(__LINE__) + +namespace bazel { +namespace runfiles { +namespace { + +using std::string; +using std::unique_ptr; + +TEST(RunfilesTest, DirectoryBasedRunfilesRlocation) { + string error; + unique_ptr r(Runfiles::CreateDirectoryBased("whatever", &error)); + ASSERT_NE(r, nullptr); + EXPECT_TRUE(error.empty()); + + EXPECT_EQ(r->Rlocation("a/b"), "whatever/a/b"); + EXPECT_EQ(r->Rlocation("c/d"), "whatever/c/d"); + EXPECT_EQ(r->Rlocation(""), ""); + EXPECT_EQ(r->Rlocation("foo"), "whatever/foo"); + EXPECT_EQ(r->Rlocation("foo/"), "whatever/foo/"); + EXPECT_EQ(r->Rlocation("foo/bar"), "whatever/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"); +} + +} // namespace +} // namespace runfiles +} // namespace bazel