get_cache_files_internal(dir, 1, progress_receiver, files);
}
+std::string
+get_relative_path(string_view dir, string_view path)
+{
+ assert(Util::is_absolute_path(dir));
+ assert(Util::is_absolute_path(path));
+
+#ifdef _WIN32
+ // Paths can be escaped by a slash for use with e.g. -isystem.
+ if (dir.length() >= 3 && dir[0] == '/' && dir[2] == ':') {
+ dir = dir.substr(1);
+ }
+ if (path.length() >= 3 && path[0] == '/' && path[2] == ':') {
+ path = path.substr(1);
+ }
+ if (dir[0] != path[0]) {
+ // Drive letters differ.
+ return std::string(path);
+ }
+ dir = dir.substr(2);
+ path = path.substr(2);
+#endif
+
+ std::string result;
+ size_t common_prefix_len = Util::common_dir_prefix_length(dir, path);
+ if (common_prefix_len > 0 || dir != "/") {
+ for (size_t i = common_prefix_len; i < dir.length(); ++i) {
+ if (dir[i] == '/') {
+ if (!result.empty()) {
+ result += '/';
+ }
+ result += "..";
+ }
+ }
+ }
+ if (path.length() > common_prefix_len) {
+ if (!result.empty()) {
+ result += '/';
+ }
+ result += std::string(path.substr(common_prefix_len + 1));
+ }
+ result.erase(result.find_last_not_of('/') + 1);
+ return result.empty() ? "." : result;
+}
+
std::string
get_path_in_cache(string_view cache_dir,
uint32_t levels,
const ProgressReceiver& progress_receiver,
std::vector<std::shared_ptr<CacheFile>>& files);
+// Compute a relative path from `dir` (an absolute path to a directory) to
+// `path` (an absolute path). Assumes that both `dir` and `path` are normalized.
+// The algorithm does *not* follow symlinks, so the result may not actually
+// resolve to the same file as `path`.
+std::string get_relative_path(nonstd::string_view dir,
+ nonstd::string_view path);
+
// Join `cache_dir`, a '/', `name`, and `suffix` into a single path and return
// it. Additionally `levels` single-character, '/'-separated subpaths are split
// from the beginning of `name` before joining them all.
std::string canon_path = Util::real_path(path, true);
if (!canon_path.empty()) {
- char* relpath =
- get_relative_path(ctx.actual_cwd.c_str(), canon_path.c_str());
+ std::string relpath = Util::get_relative_path(ctx.actual_cwd, canon_path);
if (path_suffix) {
result = fmt::format("{}/{}", relpath, path_suffix);
} else {
result = relpath;
}
- free(relpath);
} else {
// path doesn't exist, so leave it as it is.
result = path;
#endif
}
-// Compute a relative path from from (an absolute path to a directory) to to (a
-// path). Assumes that both from and to are well-formed and canonical. Caller
-// frees.
-char*
-get_relative_path(const char* from, const char* to)
-{
- size_t common_prefix_len;
- char* result;
-
- assert(from && Util::is_absolute_path(from));
- assert(to);
-
- if (!*to || !Util::is_absolute_path(to)) {
- return x_strdup(to);
- }
-
-#ifdef _WIN32
- // Paths can be escaped by a slash for use with -isystem.
- if (from[0] == '/') {
- from++;
- }
- if (to[0] == '/') {
- to++;
- }
- // Both paths are absolute, drop the drive letters.
- assert(from[0] == to[0]); // Assume the same drive letter.
- from += 2;
- to += 2;
-#endif
-
- result = x_strdup("");
- common_prefix_len = Util::common_dir_prefix_length(from, to);
- if (common_prefix_len > 0 || !str_eq(from, "/")) {
- const char* p;
- for (p = from + common_prefix_len; *p; p++) {
- if (*p == '/') {
- reformat(&result, "../%s", result);
- }
- }
- }
- if (strlen(to) > common_prefix_len) {
- reformat(&result, "%s%s", result, to + common_prefix_len + 1);
- }
- for (int i = strlen(result) - 1; i >= 0 && result[i] == '/'; i--) {
- result[i] = '\0';
- }
- if (str_eq(result, "")) {
- free(result);
- result = x_strdup(".");
- }
- return result;
-}
-
// Return whether the argument is a full path.
bool
is_full_path(const char* path)
FILE* create_tmp_file(char** fname, const char* mode);
const char* get_home_directory();
bool same_executable_name(const char* s1, const char* s2);
-char* get_relative_path(const char* from, const char* to);
bool is_full_path(const char* path);
void update_mtime(const char* path);
void x_exit(int status) ATTR_NORETURN;
}
}
+TEST_CASE("Util::get_relative_path")
+{
+#ifdef _WIN32
+ CHECK(Util::get_relative_path("C:/a", "C:/a") == ".");
+ CHECK(Util::get_relative_path("C:/a", "Z:/a") == "Z:/a");
+ CHECK(Util::get_relative_path("C:/a/b", "C:/a") == "..");
+ CHECK(Util::get_relative_path("C:/a", "C:/a/b") == "b");
+ CHECK(Util::get_relative_path("C:/a", "C:/a/b/c") == "b/c");
+ CHECK(Util::get_relative_path("C:/a/b", "C:/a/c") == "../c");
+ CHECK(Util::get_relative_path("C:/a/b", "C:/a/c/d") == "../c/d");
+ CHECK(Util::get_relative_path("C:/a/b/c", "C:/a/c/d") == "../../c/d");
+ CHECK(Util::get_relative_path("C:/a/b", "C:/") == "../..");
+ CHECK(Util::get_relative_path("C:/a/b", "C:/c") == "../../c");
+ CHECK(Util::get_relative_path("C:/", "C:/a/b") == "a/b");
+ CHECK(Util::get_relative_path("C:/a", "D:/a/b") == "D:/a/b");
+#else
+ CHECK(Util::get_relative_path("/a", "/a") == ".");
+ CHECK(Util::get_relative_path("/a/b", "/a") == "..");
+ CHECK(Util::get_relative_path("/a", "/a/b") == "b");
+ CHECK(Util::get_relative_path("/a", "/a/b/c") == "b/c");
+ CHECK(Util::get_relative_path("/a/b", "/a/c") == "../c");
+ CHECK(Util::get_relative_path("/a/b", "/a/c/d") == "../c/d");
+ CHECK(Util::get_relative_path("/a/b/c", "/a/c/d") == "../../c/d");
+ CHECK(Util::get_relative_path("/a/b", "/") == "../..");
+ CHECK(Util::get_relative_path("/a/b", "/c") == "../../c");
+ CHECK(Util::get_relative_path("/", "/a/b") == "a/b");
+#endif
+}
+
TEST_CASE("Util::get_path_in_cache")
{
CHECK(Util::get_path_in_cache("/zz/ccache", 1, "ABCDEF", ".suffix")
CHECK_STR_EQ_FREE2("dir1/dir2", x_dirname("dir1/dir2/"));
}
-TEST(get_relative_path)
-{
-#ifdef _WIN32
- CHECK_STR_EQ_FREE2("a", get_relative_path("C:/doesn't matter", "a"));
- CHECK_STR_EQ_FREE2("a/b", get_relative_path("C:/doesn't matter", "a/b"));
- CHECK_STR_EQ_FREE2(".", get_relative_path("C:/a", "C:/a"));
- CHECK_STR_EQ_FREE2("..", get_relative_path("C:/a/b", "C:/a"));
- CHECK_STR_EQ_FREE2("b", get_relative_path("C:/a", "C:/a/b"));
- CHECK_STR_EQ_FREE2("b/c", get_relative_path("C:/a", "C:/a/b/c"));
- CHECK_STR_EQ_FREE2("../c", get_relative_path("C:/a/b", "C:/a/c"));
- CHECK_STR_EQ_FREE2("../c/d", get_relative_path("C:/a/b", "C:/a/c/d"));
- CHECK_STR_EQ_FREE2("../../c/d", get_relative_path("C:/a/b/c", "C:/a/c/d"));
- CHECK_STR_EQ_FREE2("../..", get_relative_path("C:/a/b", "C:/"));
- CHECK_STR_EQ_FREE2("../../c", get_relative_path("C:/a/b", "C:/c"));
- CHECK_STR_EQ_FREE2("a/b", get_relative_path("C:/", "C:/a/b"));
-#else
- CHECK_STR_EQ_FREE2("a", get_relative_path("/doesn't matter", "a"));
- CHECK_STR_EQ_FREE2("a/b", get_relative_path("/doesn't matter", "a/b"));
- CHECK_STR_EQ_FREE2(".", get_relative_path("/a", "/a"));
- CHECK_STR_EQ_FREE2("..", get_relative_path("/a/b", "/a"));
- CHECK_STR_EQ_FREE2("b", get_relative_path("/a", "/a/b"));
- CHECK_STR_EQ_FREE2("b/c", get_relative_path("/a", "/a/b/c"));
- CHECK_STR_EQ_FREE2("../c", get_relative_path("/a/b", "/a/c"));
- CHECK_STR_EQ_FREE2("../c/d", get_relative_path("/a/b", "/a/c/d"));
- CHECK_STR_EQ_FREE2("../../c/d", get_relative_path("/a/b/c", "/a/c/d"));
- CHECK_STR_EQ_FREE2("../..", get_relative_path("/a/b", "/"));
- CHECK_STR_EQ_FREE2("../../c", get_relative_path("/a/b", "/c"));
- CHECK_STR_EQ_FREE2("a/b", get_relative_path("/", "/a/b"));
-#endif
-}
-
TEST(subst_env_in_string)
{
char* errmsg;