diff --git a/.gitignore b/.gitignore index fdca01513f..e2bbc7eaee 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ /.clangd/ /compile_commands.json /.cache/ +/.vs +/out/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 70fc5e99f0..42094d2fd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,14 @@ else() endif() endif() +if(MSVC OR MINGW) + option(BUILD_UNICODE "Build with Unicode support. This enables long path support on windows." ON) + + if(BUILD_UNICODE) + add_compile_definitions(UNICODE _UNICODE) + endif() +endif() + # --- optional re2c find_program(RE2C re2c) if(RE2C) @@ -101,6 +109,7 @@ add_library(libninja OBJECT src/disk_interface.cc src/edit_distance.cc src/eval_env.cc + src/file_path.cc src/graph.cc src/graphviz.cc src/json.cc @@ -152,7 +161,7 @@ add_executable(ninja src/ninja.cc) target_link_libraries(ninja PRIVATE libninja libninja-re2c) if(WIN32) - target_sources(ninja PRIVATE windows/ninja.manifest) + target_sources(ninja PRIVATE windows/ninja.manifest) endif() # Adds browse mode into the ninja binary if it's supported by the host platform. @@ -183,53 +192,53 @@ endif() include(CTest) if(BUILD_TESTING) - # Tests all build into ninja_test executable. - add_executable(ninja_test - src/build_log_test.cc - src/build_test.cc - src/clean_test.cc - src/clparser_test.cc - src/depfile_parser_test.cc - src/deps_log_test.cc - src/disk_interface_test.cc - src/dyndep_parser_test.cc - src/edit_distance_test.cc - src/graph_test.cc - src/json_test.cc - src/lexer_test.cc - src/manifest_parser_test.cc - src/missing_deps_test.cc - src/ninja_test.cc - src/state_test.cc - src/string_piece_util_test.cc - src/subprocess_test.cc - src/test.cc - src/util_test.cc - ) - if(WIN32) - target_sources(ninja_test PRIVATE src/includes_normalize_test.cc src/msvc_helper_test.cc) - endif() - target_link_libraries(ninja_test PRIVATE libninja libninja-re2c) - - foreach(perftest - build_log_perftest - canon_perftest - clparser_perftest - depfile_parser_perftest - hash_collision_bench - manifest_parser_perftest - ) - add_executable(${perftest} src/${perftest}.cc) - target_link_libraries(${perftest} PRIVATE libninja libninja-re2c) - endforeach() - - if(CMAKE_SYSTEM_NAME STREQUAL "AIX" AND CMAKE_SIZEOF_VOID_P EQUAL 4) - # These tests require more memory than will fit in the standard AIX shared stack/heap (256M) - target_link_options(hash_collision_bench PRIVATE "-Wl,-bmaxdata:0x80000000") - target_link_options(manifest_parser_perftest PRIVATE "-Wl,-bmaxdata:0x80000000") - endif() - - add_test(NAME NinjaTest COMMAND ninja_test) + # Tests all build into ninja_test executable. + add_executable(ninja_test + src/build_log_test.cc + src/build_test.cc + src/clean_test.cc + src/clparser_test.cc + src/depfile_parser_test.cc + src/deps_log_test.cc + src/disk_interface_test.cc + src/dyndep_parser_test.cc + src/edit_distance_test.cc + src/graph_test.cc + src/json_test.cc + src/lexer_test.cc + src/manifest_parser_test.cc + src/missing_deps_test.cc + src/ninja_test.cc + src/state_test.cc + src/string_piece_util_test.cc + src/subprocess_test.cc + src/test.cc + src/util_test.cc + ) + if(WIN32) + target_sources(ninja_test PRIVATE src/includes_normalize_test.cc src/msvc_helper_test.cc) + endif() + target_link_libraries(ninja_test PRIVATE libninja libninja-re2c) + + foreach(perftest + build_log_perftest + canon_perftest + clparser_perftest + depfile_parser_perftest + hash_collision_bench + manifest_parser_perftest + ) + add_executable(${perftest} src/${perftest}.cc) + target_link_libraries(${perftest} PRIVATE libninja libninja-re2c) + endforeach() + + if(CMAKE_SYSTEM_NAME STREQUAL "AIX" AND CMAKE_SIZEOF_VOID_P EQUAL 4) + # These tests require more memory than will fit in the standard AIX shared stack/heap (256M) + target_link_options(hash_collision_bench PRIVATE "-Wl,-bmaxdata:0x80000000") + target_link_options(manifest_parser_perftest PRIVATE "-Wl,-bmaxdata:0x80000000") + endif() + + add_test(NAME NinjaTest COMMAND ninja_test) endif() install(TARGETS ninja) diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000000..23d8f237b3 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,26 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + } + ] +} \ No newline at end of file diff --git a/configure.py b/configure.py index 43904349a8..e79585ff44 100755 --- a/configure.py +++ b/configure.py @@ -325,6 +325,7 @@ def binary(name): '/wd4267', '/DNOMINMAX', '/D_CRT_SECURE_NO_WARNINGS', '/D_HAS_EXCEPTIONS=0', + '/DUNICODE', '/D_UNICODE', '/DNINJA_PYTHON="%s"' % options.with_python] if platform.msvc_needs_fs(): cflags.append('/FS') @@ -356,7 +357,7 @@ def binary(name): except: pass if platform.is_mingw(): - cflags += ['-D_WIN32_WINNT=0x0601', '-D__USE_MINGW_ANSI_STDIO=1'] + cflags += ['-D_WIN32_WINNT=0x0601', '-D__USE_MINGW_ANSI_STDIO=1', '-DUNICODE', '-D_UNICODE'] ldflags = ['-L$builddir'] if platform.uses_usr_local(): cflags.append('-I/usr/local/include') @@ -505,6 +506,7 @@ def has_re2c(): 'dyndep_parser', 'edit_distance', 'eval_env', + 'file_path', 'graph', 'graphviz', 'json', diff --git a/src/build_log.cc b/src/build_log.cc index 4dcd6cee53..b069bfaa5a 100644 --- a/src/build_log.cc +++ b/src/build_log.cc @@ -186,7 +186,7 @@ bool BuildLog::OpenForWriteIfNeeded() { if (log_file_ || log_file_path_.empty()) { return true; } - log_file_ = fopen(log_file_path_.c_str(), "ab"); + log_file_ = fopen(ToPathWidth(log_file_path_).c_str(), "ab"); if (!log_file_) { return false; } @@ -260,7 +260,7 @@ struct LineReader { LoadStatus BuildLog::Load(const string& path, string* err) { METRIC_RECORD(".ninja_log load"); - FILE* file = fopen(path.c_str(), "r"); + FILE* file = fopen(ToPathWidth(path).c_str(), "r"); if (!file) { if (errno == ENOENT) return LOAD_NOT_FOUND; @@ -283,7 +283,7 @@ LoadStatus BuildLog::Load(const string& path, string* err) { *err = ("build log version invalid, perhaps due to being too old; " "starting over"); fclose(file); - unlink(path.c_str()); + unlink(ToPathWidth(path).c_str()); // Don't report this as a failure. An empty build log will cause // us to rebuild the outputs anyway. return LOAD_SUCCESS; @@ -393,7 +393,7 @@ bool BuildLog::Recompact(const string& path, const BuildLogUser& user, Close(); string temp_path = path + ".recompact"; - FILE* f = fopen(temp_path.c_str(), "wb"); + FILE* f = fopen(ToPathWidth(temp_path).c_str(), "wb"); if (!f) { *err = strerror(errno); return false; @@ -423,7 +423,7 @@ bool BuildLog::Recompact(const string& path, const BuildLogUser& user, entries_.erase(dead_outputs[i]); fclose(f); - if (unlink(path.c_str()) < 0) { + if (unlink(ToPathWidth(path).c_str()) < 0) { *err = strerror(errno); return false; } @@ -444,7 +444,7 @@ bool BuildLog::Restat(const StringPiece path, Close(); std::string temp_path = path.AsString() + ".restat"; - FILE* f = fopen(temp_path.c_str(), "wb"); + FILE* f = fopen(ToPathWidth(temp_path).c_str(), "wb"); if (!f) { *err = strerror(errno); return false; @@ -480,7 +480,7 @@ bool BuildLog::Restat(const StringPiece path, } fclose(f); - if (unlink(path.str_) < 0) { + if (unlink(ToPathWidth(path.str_).c_str()) < 0) { *err = strerror(errno); return false; } diff --git a/src/build_log_perftest.cc b/src/build_log_perftest.cc index 5a936198fb..2547cabac1 100644 --- a/src/build_log_perftest.cc +++ b/src/build_log_perftest.cc @@ -144,7 +144,7 @@ int main() { printf("min %dms max %dms avg %.1fms\n", min, max, total / times.size()); - unlink(kTestFilename); + unlink(ToPathWidth(kTestFilename).c_str()); return 0; } diff --git a/src/build_log_test.cc b/src/build_log_test.cc index 37182994d2..a3e66e6061 100644 --- a/src/build_log_test.cc +++ b/src/build_log_test.cc @@ -31,15 +31,15 @@ using namespace std; namespace { -const char kTestFilename[] = "BuildLogTest-tempfile"; +const file_string kTestFilename(TEXT("BuildLogTest-tempfile")); struct BuildLogTest : public StateTestWithBuiltinRules, public BuildLogUser { virtual void SetUp() { // In case a crashing test left a stale file behind. - unlink(kTestFilename); + unlink(kTestFilename.c_str()); } virtual void TearDown() { - unlink(kTestFilename); + unlink(kTestFilename.c_str()); } virtual bool IsPathDead(StringPiece s) const { return false; } }; @@ -51,14 +51,14 @@ TEST_F(BuildLogTest, WriteRead) { BuildLog log1; string err; - EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err)); + EXPECT_TRUE(log1.OpenForWrite(NarrowPathNoVerify(kTestFilename), *this, &err)); ASSERT_EQ("", err); log1.RecordCommand(state_.edges_[0], 15, 18); log1.RecordCommand(state_.edges_[1], 20, 25); log1.Close(); BuildLog log2; - EXPECT_TRUE(log2.Load(kTestFilename, &err)); + EXPECT_TRUE(log2.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_EQ("", err); ASSERT_EQ(2u, log1.entries().size()); @@ -79,23 +79,23 @@ TEST_F(BuildLogTest, FirstWriteAddsSignature) { BuildLog log; string contents, err; - EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err)); + EXPECT_TRUE(log.OpenForWrite(NarrowPathNoVerify(kTestFilename), *this, &err)); ASSERT_EQ("", err); log.Close(); - ASSERT_EQ(0, ReadFile(kTestFilename, &contents, &err)); + ASSERT_EQ(0, ReadFile(NarrowPathNoVerify(kTestFilename), &contents, &err)); ASSERT_EQ("", err); if (contents.size() >= kVersionPos) contents[kVersionPos] = 'X'; EXPECT_EQ(kExpectedVersion, contents); // Opening the file anew shouldn't add a second version string. - EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err)); + EXPECT_TRUE(log.OpenForWrite(NarrowPathNoVerify(kTestFilename), *this, &err)); ASSERT_EQ("", err); log.Close(); contents.clear(); - ASSERT_EQ(0, ReadFile(kTestFilename, &contents, &err)); + ASSERT_EQ(0, ReadFile(NarrowPathNoVerify(kTestFilename), &contents, &err)); ASSERT_EQ("", err); if (contents.size() >= kVersionPos) contents[kVersionPos] = 'X'; @@ -111,7 +111,7 @@ TEST_F(BuildLogTest, DoubleEntry) { string err; BuildLog log; - EXPECT_TRUE(log.Load(kTestFilename, &err)); + EXPECT_TRUE(log.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_EQ("", err); BuildLog::LogEntry* e = log.LookupByOutput("out"); @@ -127,7 +127,8 @@ TEST_F(BuildLogTest, Truncate) { { BuildLog log1; string err; - EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err)); + EXPECT_TRUE( + log1.OpenForWrite(NarrowPathNoVerify(kTestFilename), *this, &err)); ASSERT_EQ("", err); log1.RecordCommand(state_.edges_[0], 15, 18); log1.RecordCommand(state_.edges_[1], 20, 25); @@ -135,7 +136,7 @@ TEST_F(BuildLogTest, Truncate) { } struct stat statbuf; - ASSERT_EQ(0, stat(kTestFilename, &statbuf)); + ASSERT_EQ(0, stat(NarrowPathNoVerify(kTestFilename).c_str(), &statbuf)); ASSERT_GT(statbuf.st_size, 0); // For all possible truncations of the input file, assert that we don't @@ -143,17 +144,20 @@ TEST_F(BuildLogTest, Truncate) { for (off_t size = statbuf.st_size; size > 0; --size) { BuildLog log2; string err; - EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err)); + EXPECT_TRUE( + log2.OpenForWrite(NarrowPathNoVerify(kTestFilename), *this, &err)); ASSERT_EQ("", err); log2.RecordCommand(state_.edges_[0], 15, 18); log2.RecordCommand(state_.edges_[1], 20, 25); log2.Close(); - ASSERT_TRUE(Truncate(kTestFilename, size, &err)); + ASSERT_TRUE(Truncate(NarrowPathNoVerify(kTestFilename), size, &err)); BuildLog log3; err.clear(); - ASSERT_TRUE(log3.Load(kTestFilename, &err) == LOAD_SUCCESS || !err.empty()); + ASSERT_TRUE(log3.Load(NarrowPathNoVerify(kTestFilename), &err) == + LOAD_SUCCESS || + !err.empty()); } } @@ -165,7 +169,7 @@ TEST_F(BuildLogTest, ObsoleteOldVersion) { string err; BuildLog log; - EXPECT_TRUE(log.Load(kTestFilename, &err)); + EXPECT_TRUE(log.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_NE(err.find("version"), string::npos); } @@ -177,7 +181,7 @@ TEST_F(BuildLogTest, SpacesInOutputV4) { string err; BuildLog log; - EXPECT_TRUE(log.Load(kTestFilename, &err)); + EXPECT_TRUE(log.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_EQ("", err); BuildLog::LogEntry* e = log.LookupByOutput("out with space"); @@ -201,7 +205,7 @@ TEST_F(BuildLogTest, DuplicateVersionHeader) { string err; BuildLog log; - EXPECT_TRUE(log.Load(kTestFilename, &err)); + EXPECT_TRUE(log.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_EQ("", err); BuildLog::LogEntry* e = log.LookupByOutput("out"); @@ -248,7 +252,7 @@ TEST_F(BuildLogTest, Restat) { fclose(f); std::string err; BuildLog log; - EXPECT_TRUE(log.Load(kTestFilename, &err)); + EXPECT_TRUE(log.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_EQ("", err); BuildLog::LogEntry* e = log.LookupByOutput("out"); ASSERT_EQ(3, e->mtime); @@ -256,12 +260,13 @@ TEST_F(BuildLogTest, Restat) { TestDiskInterface testDiskInterface; char out2[] = { 'o', 'u', 't', '2', 0 }; char* filter2[] = { out2 }; - EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 1, filter2, &err)); + EXPECT_TRUE(log.Restat(NarrowPathNoVerify(kTestFilename), testDiskInterface, 1, filter2, &err)); ASSERT_EQ("", err); e = log.LookupByOutput("out"); ASSERT_EQ(3, e->mtime); // unchanged, since the filter doesn't match - EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 0, NULL, &err)); + EXPECT_TRUE( + log.Restat(NarrowPathNoVerify(kTestFilename), testDiskInterface, 0, NULL, &err)); ASSERT_EQ("", err); e = log.LookupByOutput("out"); ASSERT_EQ(4, e->mtime); @@ -281,7 +286,7 @@ TEST_F(BuildLogTest, VeryLongInputLine) { string err; BuildLog log; - EXPECT_TRUE(log.Load(kTestFilename, &err)); + EXPECT_TRUE(log.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_EQ("", err); BuildLog::LogEntry* e = log.LookupByOutput("out"); @@ -326,7 +331,8 @@ TEST_F(BuildLogRecompactTest, Recompact) { BuildLog log1; string err; - EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err)); + EXPECT_TRUE( + log1.OpenForWrite(NarrowPathNoVerify(kTestFilename), *this, &err)); ASSERT_EQ("", err); // Record the same edge several times, to trigger recompaction // the next time the log is opened. @@ -337,18 +343,19 @@ TEST_F(BuildLogRecompactTest, Recompact) { // Load... BuildLog log2; - EXPECT_TRUE(log2.Load(kTestFilename, &err)); + EXPECT_TRUE(log2.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_EQ("", err); ASSERT_EQ(2u, log2.entries().size()); ASSERT_TRUE(log2.LookupByOutput("out")); ASSERT_TRUE(log2.LookupByOutput("out2")); // ...and force a recompaction. - EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err)); + EXPECT_TRUE( + log2.OpenForWrite(NarrowPathNoVerify(kTestFilename), *this, &err)); log2.Close(); // "out2" is dead, it should've been removed. BuildLog log3; - EXPECT_TRUE(log2.Load(kTestFilename, &err)); + EXPECT_TRUE(log2.Load(NarrowPathNoVerify(kTestFilename), &err)); ASSERT_EQ("", err); ASSERT_EQ(1u, log2.entries().size()); ASSERT_TRUE(log2.LookupByOutput("out")); diff --git a/src/clean_test.cc b/src/clean_test.cc index e99909c0d0..e1de421a6b 100644 --- a/src/clean_test.cc +++ b/src/clean_test.cc @@ -469,11 +469,11 @@ TEST_F(CleanTest, CleanDepFileAndRspFileWithSpaces) { struct CleanDeadTest : public CleanTest, public BuildLogUser{ virtual void SetUp() { // In case a crashing test left a stale file behind. - unlink(kTestFilename); + unlink(ToPathWidth(kTestFilename).c_str()); CleanTest::SetUp(); } virtual void TearDown() { - unlink(kTestFilename); + unlink(ToPathWidth(kTestFilename).c_str()); } virtual bool IsPathDead(StringPiece) const { return false; } }; diff --git a/src/deps_log.cc b/src/deps_log.cc index 7e48b38513..e194c42e12 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -152,7 +152,7 @@ void DepsLog::Close() { LoadStatus DepsLog::Load(const string& path, State* state, string* err) { METRIC_RECORD(".ninja_deps load"); char buf[kMaxRecordSize + 1]; - FILE* f = fopen(path.c_str(), "rb"); + FILE* f = fopen(ToPathWidth(path).c_str(), "rb"); if (!f) { if (errno == ENOENT) return LOAD_NOT_FOUND; @@ -175,7 +175,7 @@ LoadStatus DepsLog::Load(const string& path, State* state, string* err) { else *err = "bad deps log signature or version; starting over"; fclose(f); - unlink(path.c_str()); + unlink(ToPathWidth(path).c_str()); // Don't report this as a failure. An empty deps log will cause // us to rebuild the outputs anyway. return LOAD_SUCCESS; @@ -316,7 +316,7 @@ bool DepsLog::Recompact(const string& path, string* err) { // OpenForWrite() opens for append. Make sure it's not appending to a // left-over file from a previous recompaction attempt that crashed somehow. - unlink(temp_path.c_str()); + unlink(ToPathWidth(temp_path).c_str()); DepsLog new_log; if (!new_log.OpenForWrite(temp_path, err)) @@ -348,7 +348,7 @@ bool DepsLog::Recompact(const string& path, string* err) { deps_.swap(new_log.deps_); nodes_.swap(new_log.nodes_); - if (unlink(path.c_str()) < 0) { + if (unlink(ToPathWidth(path).c_str()) < 0) { *err = strerror(errno); return false; } @@ -420,7 +420,7 @@ bool DepsLog::OpenForWriteIfNeeded() { if (file_path_.empty()) { return true; } - file_ = fopen(file_path_.c_str(), "ab"); + file_ = fopen(ToPathWidth(file_path_).c_str(), "ab"); if (!file_) { return false; } diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 13fcc788b6..e8d9ca2be9 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -32,10 +32,10 @@ const char kTestFilename[] = "DepsLogTest-tempfile"; struct DepsLogTest : public testing::Test { virtual void SetUp() { // In case a crashing test left a stale file behind. - unlink(kTestFilename); + unlink(ToPathWidth(kTestFilename).c_str()); } virtual void TearDown() { - unlink(kTestFilename); + unlink(ToPathWidth(kTestFilename).c_str()); } }; @@ -335,7 +335,7 @@ TEST_F(DepsLogTest, InvalidHeader) { }; for (size_t i = 0; i < sizeof(kInvalidHeaders) / sizeof(kInvalidHeaders[0]); ++i) { - FILE* deps_log = fopen(kTestFilename, "wb"); + FILE* deps_log = fopen(ToPathWidth(kTestFilename).c_str(), "wb"); ASSERT_TRUE(deps_log != NULL); ASSERT_EQ( strlen(kInvalidHeaders[i]), diff --git a/src/disk_interface.cc b/src/disk_interface.cc index e73d901c11..2ad1a35b8e 100644 --- a/src/disk_interface.cc +++ b/src/disk_interface.cc @@ -56,7 +56,7 @@ string DirName(const string& path) { int MakeDir(const string& path) { #ifdef _WIN32 - return _mkdir(path.c_str()); + return mkdir(ToPathWidth(path).c_str()); #else return mkdir(path.c_str(), 0777); #endif @@ -75,7 +75,7 @@ TimeStamp TimeStampFromFileTime(const FILETIME& filetime) { TimeStamp StatSingleFile(const string& path, string* err) { WIN32_FILE_ATTRIBUTE_DATA attrs; - if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attrs)) { + if (!GetFileAttributesEx(ToPathWidth(path).c_str(), GetFileExInfoStandard, &attrs)) { DWORD win_err = GetLastError(); if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) return 0; @@ -104,28 +104,35 @@ bool StatAllFilesInDir(const string& dir, map* stamps, static_cast(1); FINDEX_INFO_LEVELS level = can_use_basic_info ? kFindExInfoBasic : FindExInfoStandard; - WIN32_FIND_DATAA ffd; - HANDLE find_handle = FindFirstFileExA((dir + "\\*").c_str(), level, &ffd, + WIN32_FIND_DATA ffd; + HANDLE find_handle = FindFirstFileEx(ToPathWidth((dir + "\\*")).c_str(), level, &ffd, FindExSearchNameMatch, NULL, 0); if (find_handle == INVALID_HANDLE_VALUE) { DWORD win_err = GetLastError(); if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) return true; - *err = "FindFirstFileExA(" + dir + "): " + GetLastErrorString(); + *err = "FindFirstFileExW(" + dir + "): " + GetLastErrorString(); return false; } do { - string lowername = ffd.cFileName; + file_string lowername = ffd.cFileName; if (lowername == "..") { // Seems to just copy the timestamp for ".." from ".", which is wrong. // This is the case at least on NTFS under Windows 7. continue; } transform(lowername.begin(), lowername.end(), lowername.begin(), ::tolower); - stamps->insert(make_pair(lowername, - TimeStampFromFileTime(ffd.ftLastWriteTime))); - } while (FindNextFileA(find_handle, &ffd)); + + std::string narrowPath; + std::string narrowErr; + if (!NarrowPath(lowername, &narrowPath, &narrowErr)) { + Warning(narrowErr.c_str()); + continue; + } + stamps->insert( + make_pair(narrowPath, TimeStampFromFileTime(ffd.ftLastWriteTime))); + } while (FindNextFile(find_handle, &ffd)); FindClose(find_handle); return true; } @@ -162,9 +169,9 @@ TimeStamp RealDiskInterface::Stat(const string& path, string* err) const { #ifdef _WIN32 // MSDN: "Naming Files, Paths, and Namespaces" // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx - if (!path.empty() && path[0] != '\\' && path.size() > MAX_PATH) { + if (!path.empty() && path[0] != '\\' && path.size() > PATH_MAX) { ostringstream err_stream; - err_stream << "Stat(" << path << "): Filename longer than " << MAX_PATH + err_stream << "Stat(" << path << "): Filename longer than " << PATH_MAX << " characters"; *err = err_stream.str(); return -1; @@ -221,7 +228,7 @@ TimeStamp RealDiskInterface::Stat(const string& path, string* err) const { } bool RealDiskInterface::WriteFile(const string& path, const string& contents) { - FILE* fp = fopen(path.c_str(), "w"); + FILE* fp = fopen(ToPathWidth(path).c_str(), "w"); if (fp == NULL) { Error("WriteFile(%s): Unable to create file. %s", path.c_str(), strerror(errno)); @@ -267,7 +274,8 @@ FileReader::Status RealDiskInterface::ReadFile(const string& path, int RealDiskInterface::RemoveFile(const string& path) { #ifdef _WIN32 - DWORD attributes = GetFileAttributes(path.c_str()); + const file_string pathT = ToPathWidth(path); + DWORD attributes = GetFileAttributes(pathT.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES) { DWORD win_err = GetLastError(); if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) { @@ -278,7 +286,7 @@ int RealDiskInterface::RemoveFile(const string& path) { // On Windows Ninja should behave the same: // https://github.com/ninja-build/ninja/issues/1886 // Skip error checking. If this fails, accept whatever happens below. - SetFileAttributes(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY); + SetFileAttributes(pathT.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY); } if (attributes & FILE_ATTRIBUTE_DIRECTORY) { // remove() deletes both files and directories. On Windows we have to @@ -286,7 +294,7 @@ int RealDiskInterface::RemoveFile(const string& path) { // used on a directory) // This fixes the behavior of ninja -t clean in some cases // https://github.com/ninja-build/ninja/issues/828 - if (!RemoveDirectory(path.c_str())) { + if (!RemoveDirectory(pathT.c_str())) { DWORD win_err = GetLastError(); if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) { return 1; @@ -296,7 +304,7 @@ int RealDiskInterface::RemoveFile(const string& path) { return -1; } } else { - if (!DeleteFile(path.c_str())) { + if (!DeleteFile(pathT.c_str())) { DWORD win_err = GetLastError(); if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) { return 1; diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc index 5e952edde5..575204f1b8 100644 --- a/src/disk_interface_test.cc +++ b/src/disk_interface_test.cc @@ -38,7 +38,7 @@ struct DiskInterfaceTest : public testing::Test { } bool Touch(const char* path) { - FILE *f = fopen(path, "w"); + FILE *f = fopen(ToPathWidth(path).c_str(), "w"); if (!f) return false; return fclose(f) == 0; @@ -178,7 +178,7 @@ TEST_F(DiskInterfaceTest, ReadFile) { err.clear(); const char* kTestFile = "testfile"; - FILE* f = fopen(kTestFile, "wb"); + FILE* f = fopen(ToPathWidth(kTestFile).c_str(), "wb"); ASSERT_TRUE(f); const char* kTestContent = "test content\nok"; fprintf(f, "%s", kTestContent); @@ -193,13 +193,13 @@ TEST_F(DiskInterfaceTest, ReadFile) { TEST_F(DiskInterfaceTest, MakeDirs) { string path = "path/with/double//slash/"; EXPECT_TRUE(disk_.MakeDirs(path)); - FILE* f = fopen((path + "a_file").c_str(), "w"); + FILE* f = fopen(ToPathWidth((path + "a_file")).c_str(), "w"); EXPECT_TRUE(f); EXPECT_EQ(0, fclose(f)); #ifdef _WIN32 string path2 = "another\\with\\back\\\\slashes\\"; EXPECT_TRUE(disk_.MakeDirs(path2.c_str())); - FILE* f2 = fopen((path2 + "a_file").c_str(), "w"); + FILE* f2 = fopen(ToPathWidth((path2 + "a_file")).c_str(), "w"); EXPECT_TRUE(f2); EXPECT_EQ(0, fclose(f2)); #endif diff --git a/src/file_path.cc b/src/file_path.cc new file mode 100644 index 0000000000..3c71e2b63e --- /dev/null +++ b/src/file_path.cc @@ -0,0 +1,37 @@ +// Copyright 2021 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "file_path.h" +#include "util.h" + +#include +#include + +#ifdef UNICODE + +#define WIN32_LEAN_AND_MEAN +#include + +bool NarrowPath(const std::wstring& path, std::string* narrowPath, std::string* err) { + std::vector pathChars(PATH_MAX, '\0'); + if (!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, path.c_str(), -1, pathChars.data(), pathChars.size(), nullptr, nullptr)) { + *err = std::string("Failed to narrow path: ") + GetLastErrorString(); + return false; + } + + *narrowPath = std::string(pathChars.data()); + return true; +} + +#endif diff --git a/src/file_path.h b/src/file_path.h new file mode 100644 index 0000000000..69e2b4f2c3 --- /dev/null +++ b/src/file_path.h @@ -0,0 +1,124 @@ +// Copyright 2021 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NINJA_FILE_PATH_H_ +#define NINJA_FILE_PATH_H_ + +#include + +#ifndef TEXT +#ifdef UNICODE +#define __TEXT(quote) L##quote +#else /* UNICODE */ +#define __TEXT(quote) quote +#endif /* UNICODE */ +#define TEXT(quote) __TEXT(quote) +#endif + +#ifndef _TCHAR_DEFINED +#ifdef UNICODE +typedef wchar_t TCHAR, *PTCHAR; +#else +typedef char TCHAR, *PTCHAR; +#endif +#define _TCHAR_DEFINED +#endif /* !_TCHAR_DEFINED */ + +#ifdef UNICODE +typedef std::wstring file_string_t; +#else +typedef std::string file_string_t; +#endif + +#ifdef UNICODE + +bool NarrowPath(const std::wstring& path, std::string* narrowPath, std::string* err); + +#endif + +inline bool NarrowPath(const std::string& path, std::string* narrowPath, std::string* err) { + *narrowPath = path; + return true; +} + +inline std::wstring WidenPath(const std::string& path) { + return std::wstring(path.begin(), path.end()); +} + +inline std::wstring WidenPath(const std::wstring& path) { + return path; +} + +#ifdef UNICODE +inline std::wstring ToPathWidth(const std::string& path) { + return WidenPath(path); +} + +inline std::wstring ToPathWidth(const std::wstring& path) { + return path; +} +#else +inline std::string ToPathWidth(const std::string& path) { + return path; +} +#endif + +struct file_string : public file_string_t { + file_string() : file_string_t() {} + file_string(const std::string& path) : file_string_t(ToPathWidth(path)) {} + file_string(const char* path) : file_string_t(ToPathWidth(path)) {} + + #ifdef UNICODE + + file_string(const std::wstring& path) : file_string_t(ToPathWidth(path)) {} + file_string(const wchar_t* path) : file_string_t(ToPathWidth(path)) {} + + file_string operator+(const std::wstring& r) { + return this->append(ToPathWidth(r)); + } + + file_string operator+(const wchar_t* r) { + return this->append(ToPathWidth(r)); + } + + bool operator==(const std::wstring& r) { + return this->compare(ToPathWidth(r)) == 0; + } + + bool operator==(const wchar_t* r) { + return this->compare(ToPathWidth(r)) == 0; + } + +#endif + + operator const TCHAR*() const { return c_str(); } + + file_string operator+(const std::string& r) { + return this->append(ToPathWidth(r)); + } + + file_string operator+(const char* r) { + return this->append(ToPathWidth(r)); + } + + bool operator==(const std::string& r) { + return this->compare(ToPathWidth(r)) == 0; + } + + bool operator==(const char* r) { + return this->compare(ToPathWidth(r)) == 0; + } +}; + +#endif // NINJA_FILE_PATH_H_ diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc index 081e364ac3..5e82ff647a 100644 --- a/src/includes_normalize-win32.cc +++ b/src/includes_normalize-win32.cc @@ -28,12 +28,12 @@ using namespace std; namespace { -bool InternalGetFullPathName(const StringPiece& file_name, char* buffer, +bool InternalGetFullPathName(const StringPiece& file_name, TCHAR* buffer, size_t buffer_length, string *err) { - DWORD result_size = GetFullPathNameA(file_name.AsString().c_str(), + DWORD result_size = GetFullPathName(ToPathWidth(file_name.AsString()).c_str(), buffer_length, buffer, NULL); if (result_size == 0) { - *err = "GetFullPathNameA(" + file_name.AsString() + "): " + + *err = "GetFullPathName(" + file_name.AsString() + "): " + GetLastErrorString(); return false; } else if (result_size > buffer_length) { @@ -76,19 +76,19 @@ bool SameDrive(StringPiece a, StringPiece b, string* err) { return true; } - char a_absolute[_MAX_PATH]; - char b_absolute[_MAX_PATH]; - if (!InternalGetFullPathName(a, a_absolute, sizeof(a_absolute), err)) { + TCHAR a_absolute[PATH_MAX]; + TCHAR b_absolute[PATH_MAX]; + if (!InternalGetFullPathName(a, a_absolute, _countof(a_absolute), err)) { return false; } - if (!InternalGetFullPathName(b, b_absolute, sizeof(b_absolute), err)) { + if (!InternalGetFullPathName(b, b_absolute, _countof(b_absolute), err)) { return false; } - char a_drive[_MAX_DIR]; - char b_drive[_MAX_DIR]; - _splitpath(a_absolute, a_drive, NULL, NULL, NULL); - _splitpath(b_absolute, b_drive, NULL, NULL, NULL); - return _stricmp(a_drive, b_drive) == 0; + TCHAR a_drive[_MAX_DIR]; + TCHAR b_drive[_MAX_DIR]; + t_splitpath(a_absolute, a_drive, NULL, NULL, NULL); + t_splitpath(b_absolute, b_drive, NULL, NULL, NULL); + return t_stricmp(a_drive, b_drive) == 0; } // Check path |s| is FullPath style returned by GetFullPathName. @@ -146,14 +146,19 @@ string IncludesNormalize::AbsPath(StringPiece s, string* err) { return result; } - char result[_MAX_PATH]; - if (!InternalGetFullPathName(s, result, sizeof(result), err)) { + TCHAR result[PATH_MAX]; + if (!InternalGetFullPathName(s, result, _countof(result), err)) { return ""; } - for (char* c = result; *c; ++c) - if (*c == '\\') - *c = '/'; - return result; + for (TCHAR* c = result; *c; ++c) + if (*c == TEXT('\\')) + *c = TEXT('/'); + + std::string narrowPath; + if (!NarrowPath(result, &narrowPath, err)) { + return ""; + } + return narrowPath; } string IncludesNormalize::Relativize( @@ -183,9 +188,9 @@ string IncludesNormalize::Relativize( bool IncludesNormalize::Normalize(const string& input, string* result, string* err) const { - char copy[_MAX_PATH + 1]; + char copy[PATH_MAX + 1]; size_t len = input.size(); - if (len > _MAX_PATH) { + if (len > PATH_MAX) { *err = "path too long"; return false; } diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc index 9214f53495..a6fb2fca7b 100644 --- a/src/includes_normalize_test.cc +++ b/src/includes_normalize_test.cc @@ -27,9 +27,10 @@ using namespace std; namespace { string GetCurDir() { - char buf[_MAX_PATH]; - _getcwd(buf, sizeof(buf)); - vector parts = SplitStringPiece(buf, '\\'); + TCHAR buf[PATH_MAX]; + getcwd(buf, _countof(buf)); + string narrowPath = NarrowPathNoVerify(buf); + vector parts = SplitStringPiece(narrowPath, '\\'); return parts[parts.size() - 1].AsString(); } @@ -106,42 +107,47 @@ TEST(IncludesNormalize, LongInvalidPath) { "pdb (for example, mspdb110.dll) could not be found on your path. This " "is usually a configuration error. Compilation will continue using /Z7 " "instead of /Zi, but expect a similar error when you link your program."; + + string longInputString(kLongInputString); + while (longInputString.size() <= PATH_MAX) { + longInputString += kLongInputString; + } + // Too long, won't be canonicalized. Ensure doesn't crash. string result, err; IncludesNormalize normalizer("."); - EXPECT_FALSE( - normalizer.Normalize(kLongInputString, &result, &err)); + EXPECT_FALSE(normalizer.Normalize(longInputString, &result, &err)); EXPECT_EQ("path too long", err); // Construct max size path having cwd prefix. // kExactlyMaxPath = "$cwd\\a\\aaaa...aaaa\0"; - char kExactlyMaxPath[_MAX_PATH + 1]; - ASSERT_NE(_getcwd(kExactlyMaxPath, sizeof kExactlyMaxPath), NULL); + TCHAR kExactlyMaxPath[PATH_MAX + 1]; + ASSERT_NE(getcwd(kExactlyMaxPath, _countof(kExactlyMaxPath)), NULL); - int cwd_len = strlen(kExactlyMaxPath); - ASSERT_LE(cwd_len + 3 + 1, _MAX_PATH) + int cwd_len = t_strlen(kExactlyMaxPath); + ASSERT_LE(cwd_len + 3 + 1, PATH_MAX) kExactlyMaxPath[cwd_len] = '\\'; kExactlyMaxPath[cwd_len + 1] = 'a'; kExactlyMaxPath[cwd_len + 2] = '\\'; kExactlyMaxPath[cwd_len + 3] = 'a'; - for (int i = cwd_len + 4; i < _MAX_PATH; ++i) { - if (i > cwd_len + 4 && i < _MAX_PATH - 1 && i % 10 == 0) + for (int i = cwd_len + 4; i < PATH_MAX; ++i) { + if (i > cwd_len + 4 && i < PATH_MAX - 1 && i % 10 == 0) kExactlyMaxPath[i] = '\\'; else kExactlyMaxPath[i] = 'a'; } - kExactlyMaxPath[_MAX_PATH] = '\0'; - EXPECT_EQ(strlen(kExactlyMaxPath), _MAX_PATH); + kExactlyMaxPath[PATH_MAX] = '\0'; + EXPECT_EQ(t_strlen(kExactlyMaxPath), PATH_MAX); - string forward_slashes(kExactlyMaxPath); + file_string forward_slashes(kExactlyMaxPath); replace(forward_slashes.begin(), forward_slashes.end(), '\\', '/'); - // Make sure a path that's exactly _MAX_PATH long is canonicalized. - EXPECT_EQ(forward_slashes.substr(cwd_len + 1), - NormalizeAndCheckNoError(kExactlyMaxPath)); + // Make sure a path that's exactly PATH_MAX long is canonicalized. + EXPECT_EQ(NarrowPathNoVerify(forward_slashes.substr(cwd_len + 1)), + NormalizeAndCheckNoError(NarrowPathNoVerify(kExactlyMaxPath))); } TEST(IncludesNormalize, ShortRelativeButTooLongAbsolutePath) { @@ -153,17 +159,22 @@ TEST(IncludesNormalize, ShortRelativeButTooLongAbsolutePath) { // Construct max size path having cwd prefix. // kExactlyMaxPath = "aaaa\\aaaa...aaaa\0"; - char kExactlyMaxPath[_MAX_PATH + 1]; - for (int i = 0; i < _MAX_PATH; ++i) { - if (i < _MAX_PATH - 1 && i % 10 == 4) + char kExactlyMaxPath[PATH_MAX + 1]; + for (int i = 0; i < PATH_MAX; ++i) { + if (i < PATH_MAX - 1 && i % 10 == 4) kExactlyMaxPath[i] = '\\'; else kExactlyMaxPath[i] = 'a'; } - kExactlyMaxPath[_MAX_PATH] = '\0'; - EXPECT_EQ(strlen(kExactlyMaxPath), _MAX_PATH); + kExactlyMaxPath[PATH_MAX] = '\0'; + EXPECT_EQ(strlen(kExactlyMaxPath), PATH_MAX); - // Make sure a path that's exactly _MAX_PATH long fails with a proper error. + // Make sure a path that's exactly PATH_MAX long fails with a proper error. EXPECT_FALSE(normalizer.Normalize(kExactlyMaxPath, &result, &err)); + +#ifdef UNICODE + EXPECT_TRUE(err.find("path too long") != string::npos); +#else EXPECT_TRUE(err.find("GetFullPathName") != string::npos); +#endif } diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc index 853d8e0d5d..98952632c9 100644 --- a/src/manifest_parser_perftest.cc +++ b/src/manifest_parser_perftest.cc @@ -103,7 +103,7 @@ int main(int argc, char* argv[]) { return 1; } - if (chdir(kManifestDir) < 0) + if (chdir(ToPathWidth(kManifestDir).c_str()) < 0) Fatal("chdir: %s", strerror(errno)); const int kNumRepetitions = 5; diff --git a/src/minidump-win32.cc b/src/minidump-win32.cc index 9aea7678b9..6c59088520 100644 --- a/src/minidump-win32.cc +++ b/src/minidump-win32.cc @@ -62,7 +62,7 @@ void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep) { HANDLE hFile = CreateFileA(temp_file, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == NULL) { - Error("failed to create minidump: CreateFileA(%s): %s", + Error("failed to create minidump: CreateFile(%s): %s", temp_file, GetLastErrorString().c_str()); return; } diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc index 1148ae52a5..be9db63653 100644 --- a/src/msvc_helper-win32.cc +++ b/src/msvc_helper-win32.cc @@ -46,7 +46,7 @@ int CLWrapper::Run(const string& command, string* output) { // Must be inheritable so subprocesses can dup to children. HANDLE nul = - CreateFileA("NUL", GENERIC_READ, + CreateFile(TEXT("NUL"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &security_attributes, OPEN_EXISTING, 0, NULL); if (nul == INVALID_HANDLE_VALUE) @@ -60,14 +60,14 @@ int CLWrapper::Run(const string& command, string* output) { Win32Fatal("SetHandleInformation"); PROCESS_INFORMATION process_info = {}; - STARTUPINFOA startup_info = {}; - startup_info.cb = sizeof(STARTUPINFOA); + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(STARTUPINFO); startup_info.hStdInput = nul; startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); startup_info.hStdOutput = stdout_write; startup_info.dwFlags |= STARTF_USESTDHANDLES; - if (!CreateProcessA(NULL, (char*)command.c_str(), NULL, NULL, + if (!CreateProcess(NULL, (TCHAR*)ToPathWidth(command).c_str(), NULL, NULL, /* inherit handles */ TRUE, 0, env_block_, NULL, &startup_info, &process_info)) { diff --git a/src/msvc_helper_main-win32.cc b/src/msvc_helper_main-win32.cc index 7d593071f9..85344a3070 100644 --- a/src/msvc_helper_main-win32.cc +++ b/src/msvc_helper_main-win32.cc @@ -50,19 +50,31 @@ void PushPathIntoEnvironment(const string& env_block) { } } -void WriteDepFileOrDie(const char* object_path, const CLParser& parse) { - string depfile_path = string(object_path) + ".d"; - FILE* depfile = fopen(depfile_path.c_str(), "w"); +void WriteDepFileOrDie(const TCHAR* object_path, const CLParser& parse) { +#ifdef UNICODE + const char writeFormat[] = "%ls: "; + const char writeFailedFormat[] = "writing %ls"; + const char openFailedFormat[] = "opening %ls: %s"; +#else + const char writeFormat[] = "%s: "; + const char writeFailedFormat[] = "writing %s"; + const char openFailedFormat[] = "opening %s: %s"; +#endif + + + file_string depfile_path = file_string(object_path) + TEXT(".d"); + FILE* depfile = fopen(ToPathWidth(depfile_path).c_str(), "w"); if (!depfile) { unlink(object_path); - Fatal("opening %s: %s", depfile_path.c_str(), + Fatal(openFailedFormat, depfile_path.c_str(), GetLastErrorString().c_str()); } - if (fprintf(depfile, "%s: ", object_path) < 0) { + + if (fprintf(depfile, writeFormat, object_path) < 0) { unlink(object_path); fclose(depfile); unlink(depfile_path.c_str()); - Fatal("writing %s", depfile_path.c_str()); + Fatal(writeFailedFormat, depfile_path.c_str()); } const set& headers = parse.includes_; for (set::const_iterator i = headers.begin(); @@ -133,7 +145,7 @@ int MSVCHelperMain(int argc, char** argv) { string err; if (!parser.Parse(output, deps_prefix, &output, &err)) Fatal("%s\n", err.c_str()); - WriteDepFileOrDie(output_filename, parser); + WriteDepFileOrDie(ToPathWidth(output_filename).c_str(), parser); } if (output.empty()) diff --git a/src/ninja.cc b/src/ninja.cc index 71dea210df..7d128e90a2 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -901,19 +901,26 @@ int NinjaMain::ToolCompilationDatabase(const Options* options, int argc, argc -= optind; bool first = true; - vector cwd; - char* success = NULL; + vector cwdChars; + TCHAR* success = NULL; do { - cwd.resize(cwd.size() + 1024); + cwdChars.resize(cwdChars.size() + PATH_MAX); errno = 0; - success = getcwd(&cwd[0], cwd.size()); + success = getcwd(&cwdChars[0], cwdChars.size()); } while (!success && errno == ERANGE); if (!success) { Error("cannot determine working directory: %s", strerror(errno)); return 1; } + std::string cwd; + std::string err; + if (!NarrowPath(cwdChars.data(), &cwd, &err)) { + Error(("Failed to determine working directory: " + err).c_str()); + return 1; + } + putchar('['); for (vector::iterator e = state_.edges_.begin(); e != state_.edges_.end(); ++e) { @@ -923,7 +930,7 @@ int NinjaMain::ToolCompilationDatabase(const Options* options, int argc, if (!first) { putchar(','); } - printCompdb(&cwd[0], *e, eval_mode); + printCompdb(cwd.c_str(), *e, eval_mode); first = false; } else { for (int i = 0; i != argc; ++i) { @@ -931,7 +938,7 @@ int NinjaMain::ToolCompilationDatabase(const Options* options, int argc, if (!first) { putchar(','); } - printCompdb(&cwd[0], *e, eval_mode); + printCompdb(cwd.c_str(), *e, eval_mode); first = false; } } @@ -1474,7 +1481,7 @@ NORETURN void real_main(int argc, char** argv) { // can be piped into a file without this string showing up. if (!options.tool && config.verbosity != BuildConfig::NO_STATUS_UPDATE) status->Info("Entering directory `%s'", options.working_dir); - if (chdir(options.working_dir) < 0) { + if (chdir(ToPathWidth(options.working_dir).c_str()) < 0) { Fatal("chdir to '%s' - %s", options.working_dir, strerror(errno)); } } diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc index ff3baaca7f..8f5faccdac 100644 --- a/src/subprocess-win32.cc +++ b/src/subprocess-win32.cc @@ -39,11 +39,11 @@ Subprocess::~Subprocess() { } HANDLE Subprocess::SetupPipe(HANDLE ioport) { - char pipe_name[100]; - snprintf(pipe_name, sizeof(pipe_name), - "\\\\.\\pipe\\ninja_pid%lu_sp%p", GetCurrentProcessId(), this); + TCHAR pipe_name[100]; + t_snprintf(pipe_name, sizeof(pipe_name), + TEXT("\\\\.\\pipe\\ninja_pid%lu_sp%p"), GetCurrentProcessId(), this); - pipe_ = ::CreateNamedPipeA(pipe_name, + pipe_ = ::CreateNamedPipe(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, PIPE_UNLIMITED_INSTANCES, @@ -62,7 +62,7 @@ HANDLE Subprocess::SetupPipe(HANDLE ioport) { // Get the write end of the pipe as a handle inheritable across processes. HANDLE output_write_handle = - CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + CreateFile(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); HANDLE output_write_child; if (!DuplicateHandle(GetCurrentProcess(), output_write_handle, GetCurrentProcess(), &output_write_child, @@ -83,13 +83,13 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { security_attributes.bInheritHandle = TRUE; // Must be inheritable so subprocesses can dup to children. HANDLE nul = - CreateFileA("NUL", GENERIC_READ, + CreateFile(TEXT("NUL"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &security_attributes, OPEN_EXISTING, 0, NULL); if (nul == INVALID_HANDLE_VALUE) Fatal("couldn't open nul"); - STARTUPINFOA startup_info; + STARTUPINFO startup_info; memset(&startup_info, 0, sizeof(startup_info)); startup_info.cb = sizeof(STARTUPINFO); if (!use_console_) { @@ -109,7 +109,7 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { // Do not prepend 'cmd /c' on Windows, this breaks command // lines greater than 8,191 chars. - if (!CreateProcessA(NULL, (char*)command.c_str(), NULL, NULL, + if (!CreateProcess(NULL, (TCHAR*)ToPathWidth(command).c_str(), NULL, NULL, /* inherit handles */ TRUE, process_flags, NULL, NULL, &startup_info, &process_info)) { diff --git a/src/test.cc b/src/test.cc index 11b1c9ebf0..ec3e522797 100644 --- a/src/test.cc +++ b/src/test.cc @@ -56,7 +56,7 @@ char* mkdtemp(char* name_template) { return NULL; } - err = _mkdir(name_template); + err = mkdir(ToPathWidth(name_template).c_str()); if (err < 0) { perror("mkdir"); return NULL; @@ -68,8 +68,8 @@ char* mkdtemp(char* name_template) { string GetSystemTempDir() { #ifdef _WIN32 - char buf[1024]; - if (!GetTempPath(sizeof(buf), buf)) + char buf[MAX_PATH + 1]; + if (!GetTempPathA(sizeof(buf), buf)) return ""; return buf; #else @@ -200,7 +200,7 @@ void ScopedTempDir::CreateAndEnter(const string& name) { start_dir_ = GetSystemTempDir(); if (start_dir_.empty()) Fatal("couldn't get system temp dir"); - if (chdir(start_dir_.c_str()) < 0) + if (chdir(ToPathWidth(start_dir_).c_str()) < 0) Fatal("chdir: %s", strerror(errno)); // Create a temporary subdirectory of that. @@ -213,7 +213,7 @@ void ScopedTempDir::CreateAndEnter(const string& name) { temp_dir_name_ = tempname; // chdir into the new temporary directory. - if (chdir(temp_dir_name_.c_str()) < 0) + if (chdir(ToPathWidth(temp_dir_name_).c_str()) < 0) Fatal("chdir: %s", strerror(errno)); } @@ -222,7 +222,7 @@ void ScopedTempDir::Cleanup() { return; // Something went wrong earlier. // Move out of the directory we're about to clobber. - if (chdir(start_dir_.c_str()) < 0) + if (chdir(ToPathWidth(start_dir_).c_str()) < 0) Fatal("chdir: %s", strerror(errno)); #ifdef _WIN32 diff --git a/src/test.h b/src/test.h index 4552c34c88..4527e82db8 100644 --- a/src/test.h +++ b/src/test.h @@ -182,4 +182,12 @@ struct ScopedTempDir { std::string temp_dir_name_; }; +inline std::string NarrowPathNoVerify(const std::wstring& path) { + return std::string(path.begin(), path.end()); +} + +inline std::string NarrowPathNoVerify(const std::string& path) { + return path; +} + #endif // NINJA_TEST_H_ diff --git a/src/util.cc b/src/util.cc index a2a0f278d8..d2fbe5c6e5 100644 --- a/src/util.cc +++ b/src/util.cc @@ -140,7 +140,7 @@ void CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits) { return; } - const int kMaxPathComponents = 60; + const int kMaxPathComponents = _MAX_DIR; char* components[kMaxPathComponents]; int component_count = 0; @@ -332,12 +332,12 @@ void GetWin32EscapedString(const string& input, string* result) { result->push_back(kQuote); } -int ReadFile(const string& path, string* contents, string* err) { +int ReadFile(const std::string& path, string* contents, string* err) { #ifdef _WIN32 // This makes a ninja run on a set of 1500 manifest files about 4% faster // than using the generic fopen code below. err->clear(); - HANDLE f = ::CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, + HANDLE f = ::CreateFile(ToPathWidth(path).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (f == INVALID_HANDLE_VALUE) { err->assign(GetLastErrorString()); diff --git a/src/util.h b/src/util.h index 4a7fea2258..0d2b222d2c 100644 --- a/src/util.h +++ b/src/util.h @@ -17,6 +17,7 @@ #ifdef _WIN32 #include "win32port.h" +#include #else #include #endif @@ -26,6 +27,8 @@ #include #include +#include "file_path.h" + #ifdef _MSC_VER #define NORETURN __declspec(noreturn) #else @@ -110,16 +113,58 @@ std::string ElideMiddle(const std::string& str, size_t width); /// Truncates a file to the given size. bool Truncate(const std::string& path, size_t size, std::string* err); -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__) #define snprintf _snprintf #define fileno _fileno +#define strtoull _strtoui64 + + +#ifdef UNICODE +#define unlink _wunlink +#define fopen(path, mode) _wfopen(path, TEXT(mode)) +#define chdir _wchdir +#define getcwd _wgetcwd +#define mkdir _wmkdir + +#define t_snprintf _snwprintf +#define t_splitpath _wsplitpath +#define t_stricmp _wcsicmp +#define t_strlen wcslen + +#ifndef PATH_MAX + +#define PATH_MAX 2048 + +#endif +#else // !UNICODE #define unlink _unlink +#define fopen(path, mode) fopen(path, TEXT(mode)) #define chdir _chdir -#define strtoull _strtoui64 #define getcwd _getcwd +#define mkdir _mkdir + +#define t_snprintf snprintf +#define t_splitpath _splitpath +#define t_stricmp _stricmp +#define t_strlen strlen + +#define PATH_MAX _MAX_PATH +#endif // !UNICODE +#else // !_MSC_VER +#define t_snprintf snprintf +#define t_splitpath _splitpath +#define t_stricmp _stricmp +#define t_strlen strlen + +#ifndef PATH_MAX #define PATH_MAX _MAX_PATH #endif +#ifndef _MAX_DIR +#define _MAX_DIR 256 +#endif +#endif // !_MSC_VER + #ifdef _WIN32 /// Convert the value returned by GetLastError() into a string. std::string GetLastErrorString(); diff --git a/windows/ninja.manifest b/windows/ninja.manifest index dab929e151..aaa0bd362e 100644 --- a/windows/ninja.manifest +++ b/windows/ninja.manifest @@ -3,6 +3,7 @@ UTF-8 + true