Do more slash normalization in GN.
This makes GN more aggressive about converting backslashes into forward-slashes, and makes the path handling functions mostly indifferent with regard to the two types of slashes.
The previous behavior was a bit confusing in that you would end up with strings like "//out\Debug/" on Windows, and certain operations on this would fail. With this change, the backslash will be more aggressively converted. The downside is that Posix filenames with literal backslashes in them are now unrepresentable in GN. But I think for a cross-platform system on constrained input (build files) this is a reasonable tradeoff for more consistent path handling.
[email protected]
Review URL: https://codereview.chromium.org/177003008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@253969 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc
index af3027f..fbc8506 100644
--- a/tools/gn/filesystem_utils.cc
+++ b/tools/gn/filesystem_utils.cc
@@ -43,7 +43,7 @@
*consumed_len = 1;
return DIRECTORY_CUR;
}
- if (path[after_dot] == '/') {
+ if (IsSlash(path[after_dot])) {
// Single dot followed by a slash.
*consumed_len = 2; // Consume the slash
return DIRECTORY_CUR;
@@ -56,7 +56,7 @@
*consumed_len = 2;
return DIRECTORY_UP;
}
- if (path[after_dot + 1] == '/') {
+ if (IsSlash(path[after_dot + 1])) {
// Double dot folowed by a slash.
*consumed_len = 3;
return DIRECTORY_UP;
@@ -104,7 +104,7 @@
path[0] >= 'a' && path[0] <= 'z'))
return false;
- if (path[2] != '/' && path[2] != '\\')
+ if (!IsSlash(path[2]))
return false;
return true;
}
@@ -133,7 +133,7 @@
// don't want the slash in there. This doesn't support input like "C:foo"
// which means foo relative to the current directory of the C drive but
// that's basically legacy DOS behavior we don't need to support.
- if (result.size() >= 2 && result[1] == L"/" || result[1] == L"\\")
+ if (result.size() >= 2 && result[1].size() == 1 && IsSlash(result[1][0]))
result.erase(result.begin() + 1);
#endif
@@ -267,7 +267,7 @@
size_t FindExtensionOffset(const std::string& path) {
for (int i = static_cast<int>(path.size()); i >= 0; i--) {
- if (path[i] == '/')
+ if (IsSlash(path[i]))
break;
if (path[i] == '.')
return i + 1;
@@ -285,7 +285,7 @@
size_t FindFilenameOffset(const std::string& path) {
for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) {
- if (path[i] == '/')
+ if (IsSlash(path[i]))
return i + 1;
}
return 0; // No filename found means everything was the filename.
@@ -319,7 +319,7 @@
}
bool EndsWithSlash(const std::string& s) {
- return !s.empty() && s[s.size() - 1] == '/';
+ return !s.empty() && IsSlash(s[s.size() - 1]);
}
base::StringPiece FindDir(const std::string* path) {
@@ -355,18 +355,19 @@
if (path.empty())
return false;
- if (path[0] != '/') {
+ if (!IsSlash(path[0])) {
#if defined(OS_WIN)
// Check for Windows system paths like "C:\foo".
- if (path.size() > 2 &&
- path[1] == ':' && (path[2] == '/' || path[2] == '\\'))
+ if (path.size() > 2 && path[1] == ':' && IsSlash(path[2]))
return true;
#endif
return false; // Doesn't begin with a slash, is relative.
}
+ // Double forward slash at the beginning means source-relative (we don't
+ // allow backslashes for denoting this).
if (path.size() > 1 && path[1] == '/')
- return false; // Double slash at the beginning means source-relative.
+ return false;
return true;
}
@@ -383,9 +384,11 @@
return false; // The source root is longer: the path can never be inside.
#if defined(OS_WIN)
- // Source root should be canonical on Windows.
+ // Source root should be canonical on Windows. Note that the initial slash
+ // must be forward slash, but that the other ones can be either forward or
+ // backward.
DCHECK(source_root.size() > 2 && source_root[0] != '/' &&
- source_root[1] == ':' && source_root[2] =='\\');
+ source_root[1] == ':' && IsSlash(source_root[2]));
size_t after_common_index = std::string::npos;
if (DoesBeginWindowsDriveLetter(path)) {
@@ -413,8 +416,7 @@
// The base may or may not have a trailing slash, so skip all slashes from
// the path after our prefix match.
size_t first_after_slash = after_common_index;
- while (first_after_slash < path.size() &&
- (path[first_after_slash] == '/' || path[first_after_slash] == '\\'))
+ while (first_after_slash < path.size() && IsSlash(path[first_after_slash]))
first_after_slash++;
dest->assign("//"); // Result is source root relative.
@@ -430,7 +432,7 @@
// The base may or may not have a trailing slash, so skip all slashes from
// the path after our prefix match.
size_t first_after_slash = source_root.size();
- while (first_after_slash < path.size() && path[first_after_slash] == '/')
+ while (first_after_slash < path.size() && IsSlash(path[first_after_slash]))
first_after_slash++;
dest->assign("//"); // Result is source root relative.
@@ -451,13 +453,13 @@
size_t begin_index = 1;
// If the input begins with two slashes, skip over both (this is a
- // source-relative dir).
+ // source-relative dir). These must be forward slashes only.
if (value.size() > 1 && value[1] == '/')
begin_index = 2;
std::string ret;
for (size_t i = begin_index; i < value.size(); i++) {
- if (value[i] == '/')
+ if (IsSlash(value[i]))
ret.append("../");
}
return ret;
@@ -485,7 +487,7 @@
size_t dest_i = top_index;
for (size_t src_i = top_index; src_i < path->size(); /* nothing */) {
if (pathbuf[src_i] == '.') {
- if (src_i == 0 || pathbuf[src_i - 1] == '/') {
+ if (src_i == 0 || IsSlash(pathbuf[src_i - 1])) {
// Slash followed by a dot, see if it's something special.
size_t consumed_len;
switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) {
@@ -524,7 +526,7 @@
// allow ".." to go up another level and just eat it.
} else {
// Just find the previous slash or the beginning of input.
- while (dest_i > 0 && pathbuf[dest_i - 1] != '/')
+ while (dest_i > 0 && !IsSlash(pathbuf[dest_i - 1]))
dest_i--;
}
src_i += consumed_len;
@@ -533,13 +535,15 @@
// Dot not preceeded by a slash, copy it literally.
pathbuf[dest_i++] = pathbuf[src_i++];
}
- } else if (pathbuf[src_i] == '/') {
- if (src_i > 0 && pathbuf[src_i - 1] == '/') {
+ } else if (IsSlash(pathbuf[src_i])) {
+ if (src_i > 0 && IsSlash(pathbuf[src_i - 1])) {
// Two slashes in a row, skip over it.
src_i++;
} else {
- // Just one slash, copy it.
- pathbuf[dest_i++] = pathbuf[src_i++];
+ // Just one slash, copy it, normalizing to foward slash.
+ pathbuf[dest_i] = '/';
+ dest_i++;
+ src_i++;
}
} else {
// Input nothing special, just copy it.
@@ -578,8 +582,7 @@
size_t common_prefix_len = 2; // The beginning two "//" are always the same.
size_t max_common_length = std::min(input.size(), dest.size());
for (size_t i = common_prefix_len; i < max_common_length; i++) {
- if ((input[i] == '/' || input[i] == '\\') &&
- (dest[i] == '/' || dest[i] == '\\'))
+ if (IsSlash(input[i]) && IsSlash(dest[i]))
common_prefix_len = i + 1;
else if (input[i] != dest[i])
break;
@@ -588,7 +591,7 @@
// Invert the dest dir starting from the end of the common prefix.
std::string ret;
for (size_t i = common_prefix_len; i < dest.size(); i++) {
- if (dest[i] == '/' || dest[i] == '\\')
+ if (IsSlash(dest[i]))
ret.append("../");
}
diff --git a/tools/gn/filesystem_utils.h b/tools/gn/filesystem_utils.h
index ba08635..f7209870 100644
--- a/tools/gn/filesystem_utils.h
+++ b/tools/gn/filesystem_utils.h
@@ -76,6 +76,13 @@
// preserved.
void RemoveFilename(std::string* path);
+// Returns if the given character is a slash. This allows both slashes and
+// backslashes for consistency between Posix and Windows (as opposed to
+// FilePath::IsSeparator which is based on the current platform).
+inline bool IsSlash(const char ch) {
+ return ch == '/' || ch == '\\';
+}
+
// Returns true if the given path ends with a slash.
bool EndsWithSlash(const std::string& s);
diff --git a/tools/gn/filesystem_utils_unittest.cc b/tools/gn/filesystem_utils_unittest.cc
index 69ae67c..3416e165 100644
--- a/tools/gn/filesystem_utils_unittest.cc
+++ b/tools/gn/filesystem_utils_unittest.cc
@@ -130,6 +130,7 @@
EXPECT_TRUE(InvertDir(SourceDir("//")) == "");
EXPECT_TRUE(InvertDir(SourceDir("//foo/bar")) == "../../");
+ EXPECT_TRUE(InvertDir(SourceDir("//foo\\bar")) == "../../");
EXPECT_TRUE(InvertDir(SourceDir("/foo/bar/")) == "../../");
}
@@ -171,7 +172,7 @@
NormalizePath(&input);
EXPECT_EQ("/foo", input);
- input = "//../foo"; // Don't go aboe the root dir.
+ input = "//../foo"; // Don't go above the root dir.
NormalizePath(&input);
EXPECT_EQ("//foo", input);
@@ -194,6 +195,11 @@
input = "../";
NormalizePath(&input);
EXPECT_EQ("../", input);
+
+ // Backslash normalization.
+ input = "foo\\..\\..\\bar";
+ NormalizePath(&input);
+ EXPECT_EQ("../bar", input);
}
TEST(FilesystemUtils, RebaseSourceAbsolutePath) {
diff --git a/tools/gn/function_rebase_path.cc b/tools/gn/function_rebase_path.cc
index 2f0e4b4e..aefedd50 100644
--- a/tools/gn/function_rebase_path.cc
+++ b/tools/gn/function_rebase_path.cc
@@ -17,49 +17,30 @@
namespace {
enum SeparatorConversion {
- SEP_NO_CHANGE, // Don't change.
+ SEP_TO_SLASH, // All slashes to forward.
SEP_TO_SYSTEM, // Slashes to system ones.
- SEP_FROM_SYSTEM // System ones to slashes.
};
// Does the specified path separator conversion in-place.
void ConvertSlashes(std::string* str, SeparatorConversion mode) {
#if defined(OS_WIN)
- switch (mode) {
- case SEP_NO_CHANGE:
- break;
- case SEP_TO_SYSTEM:
- for (size_t i = 0; i < str->size(); i++) {
- if ((*str)[i] == '/')
- (*str)[i] = '\\';
- }
- break;
- case SEP_FROM_SYSTEM:
- for (size_t i = 0; i < str->size(); i++) {
- if ((*str)[i] == '\\')
- (*str)[i] = '/';
- }
- break;
- }
-#else
- DCHECK(str->find('\\') == std::string::npos)
- << "Filename contains a backslash on a non-Windows platform.";
+ if (mode == SEP_TO_SYSTEM)
+ std::replace(str->begin(), str->end(), '/', '\\');
+ else
#endif
-}
-
-bool EndsInSlash(const std::string& s) {
- return !s.empty() && (s[s.size() - 1] == '/' || s[s.size() - 1] == '\\');
+ if (mode == SEP_TO_SLASH)
+ std::replace(str->begin(), str->end(), '\\', '/');
}
// We want the output to match the input in terms of ending in a slash or not.
// Through all the transformations, these can get added or removed in various
// cases.
void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
- if (EndsInSlash(input)) {
- if (!EndsInSlash(*output)) // Preserve same slash type as input.
+ if (EndsWithSlash(input)) {
+ if (!EndsWithSlash(*output)) // Preserve same slash type as input.
output->push_back(input[input.size() - 1]);
} else {
- if (EndsInSlash(*output))
+ if (EndsWithSlash(*output))
output->resize(output->size() - 1);
}
}
@@ -79,8 +60,7 @@
if (num_dots == value.size())
return true; // String is all dots.
- if (value[value_size - num_dots - 1] == '/' ||
- value[value_size - num_dots - 1] == '\\')
+ if (IsSlash(value[value_size - num_dots - 1]))
return true; // String is a [back]slash followed by 0 or more dots.
// Anything else.
@@ -152,7 +132,7 @@
" converted = rebase_path(input,\n"
" new_base = \"\",\n"
" current_base = \".\",\n"
- " path_separators = \"none\")\n"
+ " path_separators = \"to_slash\")\n"
"\n"
" Takes a string argument representing a file name, or a list of such\n"
" strings and converts it/them to be relative to a different base\n"
@@ -197,12 +177,10 @@
" On Windows systems, indicates whether and how path separators\n"
" should be converted as part of the transformation. It can be one\n"
" of the following strings:\n"
- " - \"none\" Perform no changes on path separators. This is the\n"
- " default if this argument is unspecified.\n"
+ " - \"to_slash\" Normalize all types of slashes to forward slashes.\n"
+ " This is the default if this argument is unspecified.\n"
" - \"to_system\" Convert to the system path separators\n"
" (backslashes on Windows).\n"
- " - \"from_system\" Convert system path separators to forward\n"
- " slashes.\n"
"\n"
" On Posix systems there are no path separator transformations\n"
" applied. If the new_base is empty (specifying absolute output)\n"
@@ -295,7 +273,7 @@
}
// Path conversion.
- SeparatorConversion sep_conversion = SEP_NO_CHANGE;
+ SeparatorConversion sep_conversion = SEP_TO_SLASH;
if (args.size() > kArgIndexPathConversion) {
if (convert_to_system_absolute) {
*err = Err(function, "Can't specify slash conversion.",
@@ -309,14 +287,14 @@
return result;
const std::string& sep_string =
args[kArgIndexPathConversion].string_value();
- if (sep_string == "to_system") {
+ if (sep_string == "to_slash") {
+ sep_conversion = SEP_TO_SLASH;
+ } else if (sep_string == "to_system") {
sep_conversion = SEP_TO_SYSTEM;
- } else if (sep_string == "from_system") {
- sep_conversion = SEP_FROM_SYSTEM;
- } else if (sep_string != "none") {
+ } else {
*err = Err(args[kArgIndexPathConversion],
"Invalid path separator conversion mode.",
- "I was expecting \"none\", \"to_system\", or \"from_system\" and\n"
+ "I was expecting \"to_slash\" or \"to_system\" and\n"
"you gave me \"" + args[kArgIndexPathConversion].string_value() +
"\".");
return result;
diff --git a/tools/gn/function_rebase_path_unittest.cc b/tools/gn/function_rebase_path_unittest.cc
index fd0c6bef..a29d270 100644
--- a/tools/gn/function_rebase_path_unittest.cc
+++ b/tools/gn/function_rebase_path_unittest.cc
@@ -60,18 +60,11 @@
EXPECT_EQ("../foo", RebaseOne(scope, "//foo", "//foo", "//"));
// Test slash conversion.
+ EXPECT_EQ("foo/bar", RebaseOne(scope, "foo/bar", ".", ".", "to_slash"));
+ EXPECT_EQ("foo/bar", RebaseOne(scope, "foo\\bar", ".", ".", "to_slash"));
#if defined(OS_WIN)
- EXPECT_EQ("foo/bar", RebaseOne(scope, "foo/bar", ".", ".", "none"));
EXPECT_EQ("foo\\bar", RebaseOne(scope, "foo/bar", ".", ".", "to_system"));
- EXPECT_EQ("foo/bar", RebaseOne(scope, "foo/bar", ".", ".", "from_system"));
-
- EXPECT_EQ("foo\\bar", RebaseOne(scope, "foo\\bar", ".", ".", "none"));
EXPECT_EQ("foo\\bar", RebaseOne(scope, "foo\\bar", ".", ".", "to_system"));
- EXPECT_EQ("foo/bar", RebaseOne(scope, "foo\\bar", ".", ".", "from_system"));
-#else // No transformations on Posix.
- EXPECT_EQ("foo/bar", RebaseOne(scope, "foo/bar", ".", ".", "none"));
- EXPECT_EQ("foo/bar", RebaseOne(scope, "foo/bar", ".", ".", "to_system"));
- EXPECT_EQ("foo/bar", RebaseOne(scope, "foo/bar", ".", ".", "from_system"));
#endif
// Test system path output.
diff --git a/tools/gn/setup.cc b/tools/gn/setup.cc
index 91201bb..5f30cf8b 100644
--- a/tools/gn/setup.cc
+++ b/tools/gn/setup.cc
@@ -294,9 +294,11 @@
}
bool Setup::FillBuildDir(const std::string& build_dir) {
+ std::string normalized_build_dir = PathToSystem(build_dir);
+
SourceDir resolved =
SourceDirForCurrentDirectory(build_settings_.root_path()).
- ResolveRelativeDir(build_dir);
+ ResolveRelativeDir(normalized_build_dir);
if (resolved.is_null()) {
Err(Location(), "Couldn't resolve build directory.",
"The build directory supplied (\"" + build_dir + "\") was not valid.").
diff --git a/tools/gn/source_dir_unittest.cc b/tools/gn/source_dir_unittest.cc
index 6b3b6d2c..d2ea3aa 100644
--- a/tools/gn/source_dir_unittest.cc
+++ b/tools/gn/source_dir_unittest.cc
@@ -31,7 +31,7 @@
// This could potentially be changed in the future which would mean we should
// just change the expected result.
EXPECT_TRUE(base.ResolveRelativeFile("C:\\foo\\bar.txt") ==
- SourceFile("/C:\\foo\\bar.txt"));
+ SourceFile("/C:/foo/bar.txt"));
#endif
}
@@ -55,6 +55,6 @@
// Note that we don't canonicalize the existing backslashes to forward
// slashes. This could potentially be changed in the future which would mean
// we should just change the expected result.
- EXPECT_TRUE(base.ResolveRelativeDir("C:\\foo") == SourceDir("/C:\\foo/"));
+ EXPECT_TRUE(base.ResolveRelativeDir("C:\\foo") == SourceDir("/C:/foo/"));
#endif
}