Skip to content

Commit

Permalink
runfiles,C++: add a Runfiles* factory method
Browse files Browse the repository at this point in the history
Implement Runfiles::Create method, which inspects
the RUNFILES_* envvars as well as the availability
of a runfiles manifest or -directory near argv[0],
and creates a Runfiles* object based on that.

Also add documentation and usage examples.

Subsequent commit will add integration tests.

See #4460

Change-Id: I2aa433d460826999e698597205ae7663b0e84dd3
PiperOrigin-RevId: 189555342
  • Loading branch information
laszlocsomor authored and Copybara-Service committed Mar 19, 2018
1 parent 3ef60b7 commit 1d69870
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 4 deletions.
107 changes: 104 additions & 3 deletions src/tools/runfiles/runfiles.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,28 @@
// limitations under the License.
#include "tools/runfiles/runfiles.h"

#ifdef COMPILER_MSVC
#include <windows.h>
#else // not COMPILER_MSVC
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif // COMPILER_MSVC

#include <fstream>
#include <map>
#include <sstream>
#include <vector>

#ifdef COMPILER_MSVC
#include <memory>
#endif // COMPILER_MSVC

namespace bazel {
namespace runfiles {

using std::function;
using std::map;
using std::pair;
using std::string;
Expand All @@ -30,9 +44,9 @@ namespace {

class RunfilesImpl : public Runfiles {
public:
// TODO(laszlocsomor): implement Create(
// const string& argv0, function<string(const string&)> env_lookup, string*
// error);
static Runfiles* Create(const string& argv0,
function<string(const string&)> env_lookup,
string* error);

string Rlocation(const string& path) const override;

Expand Down Expand Up @@ -92,6 +106,58 @@ class DirectoryBased : public RunfilesImpl {
const string runfiles_path_;
};

bool IsReadableFile(const string& path) {
return std::ifstream(path).is_open();
}

bool IsDirectory(const string& path) {
#ifdef COMPILER_MSVC
DWORD attrs = GetFileAttributesA(path.c_str());
return (attrs != INVALID_FILE_ATTRIBUTES) &&
(attrs & FILE_ATTRIBUTE_DIRECTORY);
#else
struct stat buf;
return stat(path.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode);
#endif
}

Runfiles* RunfilesImpl::Create(const string& argv0,
function<string(const string&)> env_lookup,
string* error) {
string manifest(std::move(env_lookup("RUNFILES_MANIFEST_FILE")));
if (!manifest.empty()) {
return ManifestBased::Create(manifest, error);
}

string directory(std::move(env_lookup("RUNFILES_DIR")));
if (!directory.empty()) {
return new DirectoryBased(directory);
}

manifest = argv0 + ".runfiles_manifest";
if (IsReadableFile(manifest)) {
return CreateManifestBased(manifest, error);
}

manifest = argv0 + ".runfiles/MANIFEST";
if (IsReadableFile(manifest)) {
return CreateManifestBased(manifest, error);
}

directory = argv0 + ".runfiles";
if (IsDirectory(directory)) {
return CreateDirectoryBased(std::move(directory), error);
}

if (error) {
std::ostringstream err;
err << "ERROR: " << __FILE__ << "(" << __LINE__
<< "): cannot find runfiles (argv0=\"" << argv0 << "\")";
*error = err.str();
}
return nullptr;
}

bool IsAbsolute(const string& path) {
if (path.empty()) {
return false;
Expand All @@ -102,6 +168,21 @@ bool IsAbsolute(const string& path) {
path[1] == ':' && (path[2] == '\\' || path[2] == '/'));
}

string GetEnv(const string& key) {
#ifdef COMPILER_MSVC
DWORD size = ::GetEnvironmentVariableA(key.c_str(), NULL, 0);
if (size == 0) {
return std::move(string()); // unset or empty envvar
}
std::unique_ptr<char[]> value(new char[size]);
::GetEnvironmentVariableA(key.c_str(), value.get(), size);
return move(string(value.get()));
#else
char* result = getenv(key.c_str());
return std::move((result == NULL) ? string() : string(result));
#endif
}

string RunfilesImpl::Rlocation(const string& path) const {
if (path.empty() || path.find("..") != string::npos) {
return std::move(string());
Expand Down Expand Up @@ -195,10 +276,30 @@ vector<pair<string, string> > DirectoryBased::EnvVars() const {

namespace testing {

Runfiles* TestOnly_CreateRunfiles(const std::string& argv0,
function<string(const string&)> env_lookup,
string* error) {
return RunfilesImpl::Create(argv0, env_lookup, error);
}

bool TestOnly_IsAbsolute(const string& path) { return IsAbsolute(path); }

} // namespace testing

Runfiles* Runfiles::Create(const string& argv0, string* error) {
return RunfilesImpl::Create(
argv0,
[](const string& key) {
if (key == "RUNFILES_MANIFEST_FILE" || key == "RUNFILES_DIR") {
string val(GetEnv(key));
return std::move(val);
} else {
return std::move(string());
}
},
error);
}

Runfiles* Runfiles::CreateManifestBased(const string& manifest_path,
string* error) {
return ManifestBased::Create(manifest_path, error);
Expand Down
89 changes: 88 additions & 1 deletion src/tools/runfiles/runfiles.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,57 @@

// Runfiles lookup library for Bazel-built C++ binaries and tests.
//
// TODO(laszlocsomor): add usage information and examples.
// Usage:
//
// #include "tools/runfiles/runfiles.h"
// ...
//
// int main(int argc, char** argv) {
// std::string error;
// std::unique_ptr<bazel::runfiles::Runfiles> runfiles(
// bazel::runfiles::Runfiles::Create(argv[0], &error));
// if (runfiles == nullptr) {
// ... // error handling
// }
// std::string path(runfiles->Rlocation("io_bazel/src/bazel"));
// std::ifstream data(path);
// if (data.is_open()) {
// ... // use the runfile
//
// The code above creates a manifest- or directory-based implementations
// depending on it finding a runfiles manifest or -directory near argv[0] or
// finding appropriate environment variables that tell it where to find the
// manifest or directory. See `Runfiles::Create` for more info.
//
// If you want to explicitly create a manifest- or directory-based
// implementation, you can do so as follows:
//
// std::unique_ptr<bazel::runfiles::Runfiles> runfiles1(
// bazel::runfiles::Runfiles::CreateManifestBased(
// "path/to/foo.runfiles/MANIFEST", &error));
//
// std::unique_ptr<bazel::runfiles::Runfiles> runfiles2(
// bazel::runfiles::Runfiles::CreateDirectoryBased(
// "path/to/foo.runfiles", &error));
//
// If you want to start child processes that also need runfiles, you need to set
// the right environment variables for them:
//
// std::unique_ptr<bazel::runfiles::Runfiles> runfiles(
// bazel::runfiles::Runfiles::Create(argv[0], &error));
//
// for (const auto i : runfiles->EnvVars()) {
// setenv(i.first, i.second, 1);
// }
// std::string path(runfiles->Rlocation("path/to/binary"));
// if (!path.empty()) {
// pid_t child = fork();
// ...

#ifndef BAZEL_SRC_TOOLS_RUNFILES_RUNFILES_H_
#define BAZEL_SRC_TOOLS_RUNFILES_RUNFILES_H_ 1

#include <functional>
#include <memory>
#include <string>
#include <vector>
Expand All @@ -30,6 +76,32 @@ class Runfiles {
public:
virtual ~Runfiles() {}

// Returns a new `Runfiles` instance.
//
// The returned object is either:
// - manifest-based, meaning it looks up runfile paths from a manifest file,
// or
// - directory-based, meaning it looks up runfile paths under a given
// directory path
//
// This method:
// 1. checks the RUNFILES_MANIFEST_FILE or RUNFILES_DIR environment variables;
// if either is non-empty, returns a manifest- or directory-based Runfiles
// object; otherwise
// 2. checks if there's a runfiles manifest (argv0 + ".runfiles_manifest") or
// runfiles directory (argv0 + ".runfiles") next to this binary; if so,
// returns a manifest- or directory-based Runfiles object; otherwise
// 3. returns nullptr.
//
// The manifest-based Runfiles object eagerly reads and caches the whole
// manifest file upon instantiation; this may be relevant for performance
// consideration.
//
// Returns nullptr on error. If `error` is provided, the method prints an
// error message into it.
static Runfiles* Create(const std::string& argv0,
std::string* error = nullptr);

// Returns a new manifest-based `Runfiles` instance.
// Returns nullptr on error. If `error` is provided, the method prints an
// error message into it.
Expand Down Expand Up @@ -83,6 +155,21 @@ class Runfiles {
// These functions and their interface may change without notice.
namespace testing {

// For testing only.
//
// Create a new Runfiles instance, looking up environment variables using
// `env_lookup`.
//
// Args:
// argv0: name of the binary; if this string is not empty, then the function
// looks for a runfiles manifest or directory next to this
// env_lookup: a function that returns envvar values if an envvar is known, or
// empty string otherwise
Runfiles* TestOnly_CreateRunfiles(
const std::string& argv0,
std::function<std::string(const std::string&)> env_lookup,
std::string* error);

// 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
Expand Down
Loading

0 comments on commit 1d69870

Please sign in to comment.