Skip to content

Commit

Permalink
runfiles,C++: implement runfiles lib foundations
Browse files Browse the repository at this point in the history
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 #4460

Change-Id: Id5a38619a1ae874499f04523863081559360410c
PiperOrigin-RevId: 187031518
  • Loading branch information
laszlocsomor authored and Copybara-Service committed Feb 26, 2018
1 parent 726df7e commit 9dfb1ee
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 0 deletions.
30 changes: 30 additions & 0 deletions src/tools/runfiles/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
Expand All @@ -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"],
Expand Down
101 changes: 101 additions & 0 deletions src/tools/runfiles/runfiles.cc
Original file line number Diff line number Diff line change
@@ -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<string(const string&)> 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
88 changes: 88 additions & 0 deletions src/tools/runfiles/runfiles.h
Original file line number Diff line number Diff line change
@@ -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 <string>

namespace bazel {
namespace runfiles {

class Runfiles {
public:
virtual ~Runfiles() {}

// TODO(laszlocsomor): implement:
// 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 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_
53 changes: 53 additions & 0 deletions src/tools/runfiles/runfiles_test.cc
Original file line number Diff line number Diff line change
@@ -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 <memory>
#include <string>

#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<Runfiles> 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

0 comments on commit 9dfb1ee

Please sign in to comment.