Skip to content

Commit

Permalink
Use std::filesystem (Merge PR #789)
Browse files Browse the repository at this point in the history
Reformulate unix-specific file operations with std::filesystem.

For now only enabled for Windows until we have all release targets use new enough compilers.

Issues #307 #765
  • Loading branch information
hzeller authored Apr 23, 2021
2 parents 7ffb56d + a4bf534 commit 495dda7
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 46 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/windows-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ jobs:
# Windows support is in progress, only run "working" tests
- name: Run known-to-work tests
run: |
bazel test //common/analysis:command_file_lexer_test `
bazel test --test_output=errors `
//common/util:file_util_test `
//common/analysis:command_file_lexer_test `
//common/analysis:file_analyzer_test `
//common/analysis:line_linter_test `
//common/analysis:lint_rule_status_test `
Expand Down
147 changes: 131 additions & 16 deletions common/util/file_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@

#include "common/util/file_util.h"

#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <algorithm>
#include <fstream>
Expand All @@ -36,6 +32,29 @@
#include "absl/strings/string_view.h"
#include "common/util/logging.h"

/*
* We're in the transition to use std::filesystem for all file operations.
* However some ancient compilers in releasing/ can't handle that yet. Until
* that is figured out, have a dual implementation.
* For now, only enable the std::filesystem implementation on Windows
* TODO(hzeller): remove the non-std::filesystem implementation once the release
* issues have been resolved.
*/
#ifdef _WIN32
#define USE_CPP_17_FILESYSTEM 1
#endif

#ifdef USE_CPP_17_FILESYSTEM
#include <filesystem>
#include <system_error>
namespace fs = std::filesystem;
#else
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif

namespace verible {
namespace file {
absl::string_view Basename(absl::string_view filename) {
Expand Down Expand Up @@ -64,10 +83,12 @@ absl::string_view Stem(absl::string_view filename) {

// This will always return an error, even if we can't determine anything
// from errno. Returns "fallback_msg" in that case.
static absl::Status CreateErrorStatusFromErrno(const char *fallback_msg) {
static absl::Status CreateErrorStatusFromSysError(const char *fallback_msg,
int sys_error) {
using absl::StatusCode;
const char *const system_msg = errno == 0 ? fallback_msg : strerror(errno);
switch (errno) {
const char *const system_msg =
sys_error == 0 ? fallback_msg : strerror(sys_error);
switch (sys_error) {
case EPERM:
case EACCES:
return absl::Status(StatusCode::kPermissionDenied, system_msg);
Expand All @@ -82,9 +103,39 @@ static absl::Status CreateErrorStatusFromErrno(const char *fallback_msg) {
}
}

// TODO (after bump to c++17) rewrite this function to use std::filesystem
static absl::Status CreateErrorStatusFromErrno(const char *fallback_msg) {
return CreateErrorStatusFromSysError(fallback_msg, errno);
}

#ifdef USE_CPP_17_FILESYSTEM
static absl::Status CreateErrorStatusFromErr(const char *fallback_msg,
const std::error_code &err) {
// TODO: this assumes that err.value() returns errno-like values. Might not
// always be the case.
return CreateErrorStatusFromSysError(fallback_msg, err.value());
}
#endif

absl::Status UpwardFileSearch(absl::string_view start,
absl::string_view filename, std::string *result) {
#if USE_CPP_17_FILESYSTEM
std::error_code err;
const std::string search_file(filename);
fs::path absolute_path = fs::absolute(std::string(start), err);
if (err.value() != 0)
return CreateErrorStatusFromErr("invalid config path specified.", err);

fs::path probe_dir = absolute_path;
for (;;) {
*result = (probe_dir / search_file).string();
if (FileExists(*result).ok()) return absl::OkStatus();
fs::path one_up = probe_dir.parent_path();
if (one_up == probe_dir) break;
probe_dir = one_up;
}
return absl::NotFoundError("No matching file found.");
#else
// Unix specific implementation.
static constexpr char dir_separator[] = "/";

/* Convert to absolute path */
Expand Down Expand Up @@ -123,9 +174,28 @@ absl::Status UpwardFileSearch(absl::string_view start,
}

return absl::NotFoundError("No matching file found.");
#endif
}

absl::Status FileExists(const std::string &filename) {
#if USE_CPP_17_FILESYSTEM
std::error_code err;
fs::file_status stat = fs::status(filename, err);

if (err.value() != 0) {
return absl::NotFoundError(absl::StrCat(filename, ":", err.message()));
}

if (fs::is_regular_file(stat) || fs::is_fifo(stat)) {
return absl::OkStatus();
}

if (fs::is_directory(stat)) {
return absl::InvalidArgumentError(
absl::StrCat(filename, ": is a directory, not a file"));
}
#else
// Unix specific implementation.
struct stat file_info;
if (stat(filename.c_str(), &file_info) != 0) {
return absl::NotFoundError(absl::StrCat(filename, ": No such file."));
Expand All @@ -137,18 +207,17 @@ absl::Status FileExists(const std::string &filename) {
return absl::InvalidArgumentError(
absl::StrCat(filename, ": is a directory, not a file"));
}
#endif
return absl::InvalidArgumentError(
absl::StrCat(filename, ": not a regular file."));
}

absl::Status GetContents(absl::string_view filename, std::string *content) {
std::ifstream fs;
std::istream *stream = nullptr;
const bool use_stdin = filename == "-";
const bool use_stdin = filename == "-"; // convention: honor "-" as stdin
if (use_stdin) {
// convention: honor "-" as stdin
stream = &std::cin;
if (isatty(0)) std::cerr << "Enter input (terminate with Ctrl-D):\n";
} else {
const std::string filename_str = std::string(filename);
absl::Status usable_file = FileExists(filename_str);
Expand All @@ -174,9 +243,22 @@ absl::Status SetContents(absl::string_view filename,
}

std::string JoinPath(absl::string_view base, absl::string_view name) {
// TODO: this will certainly be different on Windows
#if USE_CPP_17_FILESYSTEM
// Make sure the second element is not already absolute, otherwise
// the fs::path() uses this as toplevel path. This is only an issue with
// Unix paths. Windows paths will have a c:\ for explicit full-pathness
while (!name.empty() && name[0] == '/') {
name = name.substr(1);
}

fs::path p = fs::path(std::string(base)) / fs::path(std::string(name));
return p.lexically_normal().string();
#else
// Unix specific implementation
static constexpr absl::string_view kFileSeparator = "/";

if (base.empty()) return std::string(name);

// Make sure that we don't concatenate multiple superfluous separators.
// Note, since we're using string_view, the substr() operations are cheap.
while (!base.empty() && base[base.length() - 1] == kFileSeparator[0]) {
Expand All @@ -186,26 +268,57 @@ std::string JoinPath(absl::string_view base, absl::string_view name) {
name = name.substr(1);
}
return absl::StrCat(base, kFileSeparator, name);
#endif
}

absl::Status CreateDir(absl::string_view dir) {
#if USE_CPP_17_FILESYSTEM
const std::string path(dir);
std::error_code err;
if (fs::create_directory(path, err) || err.value() == 0)
return absl::OkStatus();

return CreateErrorStatusFromErr("can't create directory", err);
#else
const std::string path(dir);
int ret = mkdir(path.c_str(), 0755);
if (ret == 0 || errno == EEXIST) return absl::OkStatus();
return CreateErrorStatusFromErrno("can't create directory");
#endif
}

absl::StatusOr<Directory> ListDir(absl::string_view dir) {
#if USE_CPP_17_FILESYSTEM
std::error_code err;
Directory d;

d.path = dir.empty() ? "." : std::string(dir);

fs::file_status stat = fs::status(d.path, err);
if (err.value() != 0)
return CreateErrorStatusFromErr("Opening directory", err);
if (!fs::is_directory(stat)) {
return absl::InvalidArgumentError(absl::StrCat(dir, ": not a directory"));
}

for (const fs::directory_entry entry : fs::directory_iterator(d.path)) {
const std::string entry_name = entry.path().string();
if (entry.is_directory()) {
d.directories.push_back(entry_name);
} else {
d.files.push_back(entry_name);
}
}
#else
Directory d;
d.path = dir.empty() ? "." : std::string(dir);

// Reset the errno and open the directory
errno = 0;
DIR *handle = opendir(d.path.c_str());
if (handle == nullptr) {
if (errno == ENOTDIR) return absl::NotFoundError(d.path);
return absl::InternalError(absl::StrCat("Failed to open the directory '",
d.path, "'. Got error: ", errno));
if (errno == ENOTDIR) return absl::InvalidArgumentError(d.path);
return CreateErrorStatusFromErrno("Failure to open directory");
}
struct stat statbuf;
while (true) {
Expand Down Expand Up @@ -251,6 +364,8 @@ absl::StatusOr<Directory> ListDir(absl::string_view dir) {
}
}
closedir(handle);
#endif

std::sort(d.files.begin(), d.files.end());
std::sort(d.directories.begin(), d.directories.end());
return d;
Expand All @@ -259,7 +374,7 @@ absl::StatusOr<Directory> ListDir(absl::string_view dir) {
namespace testing {

std::string RandomFileBasename(absl::string_view prefix) {
return absl::StrCat(prefix, "-", getpid(), "-", random());
return absl::StrCat(prefix, "-", rand());
}

ScopedTestFile::ScopedTestFile(absl::string_view base_dir,
Expand Down
Loading

0 comments on commit 495dda7

Please sign in to comment.