Skip to content

Commit

Permalink
Make Util::make_relative_path able to find matches for canonical path (
Browse files Browse the repository at this point in the history
…ccache#760)

Scenario:

- /tmp is a symlink to /private/tmp.
- Both apparent and actual CWD is /private/tmp/dir.
- A path (e.g. the object file) on the command line is /tmp/dir/file.o.
- Base directory is set up to match /tmp/dir/file.o.

Ccache then rewrites /tmp/dir/file.o into ../../private/tmp/dir/file.o,
which is correct but not optimal since ./file.o would be a better
relative path. Especially on macOS where, for unknown reasons, the
kernel sometimes disallows opening a file like
../../private/tmp/dir/file.o for writing.

Improve this by letting Util::make_relative_path try to match
real_path(path) against the CWDs and choose the best match.

Closes ccache#724.
  • Loading branch information
jrosdahl authored Jan 6, 2021
1 parent 4878d8e commit 8223ed3
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 18 deletions.
38 changes: 23 additions & 15 deletions src/Util.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
Expand Down Expand Up @@ -895,28 +895,36 @@ make_relative_path(const std::string& base_dir,
// The algorithm for computing relative paths below only works for existing
// paths. If the path doesn't exist, find the first ancestor directory that
// does exist and assemble the path again afterwards.
string_view original_path = path;
std::string path_suffix;

std::vector<std::string> relpath_candidates;
const auto original_path = path;
Stat path_stat;
while (!(path_stat = Stat::stat(std::string(path)))) {
path = Util::dir_name(path);
}
path_suffix = std::string(original_path.substr(path.length()));
const auto path_suffix = std::string(original_path.substr(path.length()));
const auto real_path = Util::real_path(std::string(path));

std::string path_str(path);
std::string normalized_path = Util::normalize_absolute_path(path_str);
std::vector<std::string> relpath_candidates = {
Util::get_relative_path(actual_cwd, normalized_path),
};
if (apparent_cwd != actual_cwd) {
relpath_candidates.emplace_back(
Util::get_relative_path(apparent_cwd, normalized_path));
// Move best (= shortest) match first:
if (relpath_candidates[0].length() > relpath_candidates[1].length()) {
std::swap(relpath_candidates[0], relpath_candidates[1]);
const auto add_relpath_candidates = [&](nonstd::string_view path) {
const std::string normalized_path = Util::normalize_absolute_path(path);
relpath_candidates.push_back(
Util::get_relative_path(actual_cwd, normalized_path));
if (apparent_cwd != actual_cwd) {
relpath_candidates.emplace_back(
Util::get_relative_path(apparent_cwd, normalized_path));
}
};
add_relpath_candidates(path);
if (real_path != path) {
add_relpath_candidates(real_path);
}

// Find best (i.e. shortest existing) match:
std::sort(relpath_candidates.begin(),
relpath_candidates.end(),
[](const std::string& path1, const std::string& path2) {
return path1.length() < path2.length();
});
for (const auto& relpath : relpath_candidates) {
if (Stat::stat(relpath).same_inode_as(path_stat)) {
return relpath + path_suffix;
Expand Down
29 changes: 26 additions & 3 deletions unittest/test_Util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,6 @@ TEST_CASE("Util::is_dir_separator")
#endif
}

#ifndef _WIN32
TEST_CASE("Util::make_relative_path")
{
using Util::make_relative_path;
Expand All @@ -573,11 +572,17 @@ TEST_CASE("Util::make_relative_path")

const std::string cwd = Util::get_actual_cwd();
const std::string actual_cwd = FMT("{}/d", cwd);
#ifdef _WIN32
const std::string apparent_cwd = actual_cwd;
#else
const std::string apparent_cwd = FMT("{}/s", cwd);
#endif

REQUIRE(Util::create_dir("d"));
#ifndef _WIN32
REQUIRE(symlink("d", "s") == 0);
REQUIRE(chdir("s") == 0);
#endif
REQUIRE(chdir("d") == 0);
Util::setenv("PWD", apparent_cwd);

SUBCASE("No base directory")
Expand All @@ -587,22 +592,40 @@ TEST_CASE("Util::make_relative_path")

SUBCASE("Path matches neither actual nor apparent CWD")
{
#ifdef _WIN32
CHECK(make_relative_path("C:/", "C:/a", "C:/b", "C:/x") == "C:/x");
#else
CHECK(make_relative_path("/", "/a", "/b", "/x") == "/x");
#endif
}

SUBCASE("Match of actual CWD")
{
#ifdef _WIN32
CHECK(
make_relative_path(
actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "/x")
== "./x");
#else
CHECK(make_relative_path("/", actual_cwd, apparent_cwd, actual_cwd + "/x")
== "./x");
#endif
}

#ifndef _WIN32
SUBCASE("Match of apparent CWD")
{
CHECK(make_relative_path("/", actual_cwd, apparent_cwd, apparent_cwd + "/x")
== "./x");
}
}

SUBCASE("Match if using resolved (using realpath(3)) path")
{
CHECK(make_relative_path("/", actual_cwd, actual_cwd, apparent_cwd + "/x")
== "./x");
}
#endif
}

TEST_CASE("Util::matches_dir_prefix_or_file")
{
Expand Down

0 comments on commit 8223ed3

Please sign in to comment.