Add long path support for windows

Change-Id: I84a25207a08e8590288855c757b0241c4a8a9fed
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/16600
Reviewed-by: David Turner <digit@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Takuto Ikuta <tikuta@google.com>
diff --git a/AUTHORS b/AUTHORS
index 5e1e5e6..f7f74c1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -49,5 +49,6 @@
 Tripta Gupta <tripta.g@samsung.com>
 Wink Saville <wink@saville.com>
 Yuriy Taraday <yorik.sar@gmail.com>
+Zhongwei Wang <carolwolfking@gmail.com>
 Oleksandr Motsok <boramaabak@gmail.com>
 Ihor Karavan <ihorkaravan96@gmail.com>
diff --git a/build/gen.py b/build/gen.py
index 07b699b..5250954 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -148,7 +148,11 @@
           result.append('%s=%s' % (long_option, item))
       else:
         assert action is None, "Unsupported action " + action
-    return ' '.join(shell_quote(item) for item in result)
+
+    if platform.system() == "Windows":
+      return ' '.join(result)
+    else:
+      return ' '.join(shell_quote(item) for item in result)
 
 
 def main(argv):
@@ -175,7 +179,8 @@
                     help='Enable the use of UndefinedBehaviorSanitizer')
   args_list.add('--no-last-commit-position', action='store_true',
                     help='Do not generate last_commit_position.h.')
-  args_list.add('--out-path',
+  args_list.add('--out-path', type=str,
+                    default=os.path.join(REPO_ROOT, 'out'),
                     help='The path to generate the build files in.')
   args_list.add('--no-strip', action='store_true',
                     help='Don\'t strip release build. Useful for profiling.')
@@ -214,7 +219,7 @@
   else:
     host = platform
 
-  out_dir = options.out_path or os.path.join(REPO_ROOT, 'out')
+  out_dir = options.out_path
   if not os.path.isdir(out_dir):
     os.makedirs(out_dir)
   if not options.no_last_commit_position:
@@ -566,7 +571,10 @@
         '/D_HAS_EXCEPTIONS=0',
     ])
 
-    ldflags.extend(['/DEBUG', '/MACHINE:x64'])
+    win_manifest = os.path.relpath(
+      os.path.join(REPO_ROOT, "build/windows.manifest.xml"), options.out_path)
+    ldflags.extend(['/DEBUG', '/MACHINE:x64', '/MANIFEST:EMBED',
+                    f'/MANIFESTINPUT:{win_manifest}'])
 
   static_libraries = {
       'base': {'sources': [
diff --git a/build/windows.manifest.xml b/build/windows.manifest.xml
new file mode 100644
index 0000000..6058750
--- /dev/null
+++ b/build/windows.manifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+This is a Windows application manifest file.
+See: https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests
+-->
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+    <!-- Remove (most) legacy path limits -->
+    <asmv3:application>
+        <asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
+            <ws2:longPathAware>true</ws2:longPathAware>
+        </asmv3:windowsSettings>
+    </asmv3:application>
+</assembly>
\ No newline at end of file
diff --git a/src/gn/file_writer.cc b/src/gn/file_writer.cc
index 6c77821..7f0bb3f 100644
--- a/src/gn/file_writer.cc
+++ b/src/gn/file_writer.cc
@@ -11,6 +11,7 @@
 #if defined(OS_WIN)
 #include <windows.h>
 #include "base/strings/utf_string_conversions.h"
+#include "util/sys_info.h"
 #else
 #include <fcntl.h>
 #include <unistd.h>
@@ -30,14 +31,24 @@
   // replacing the entire contents of the file) which lets us continue even if
   // another program has the file open for reading. See
   // http://crbug.com/468437
-  file_path_ = base::UTF16ToUTF8(file_path.value());
+  const std::u16string& path = file_path.value();
+
+  file_path_ = base::UTF16ToUTF8(path);
   file_ = base::win::ScopedHandle(::CreateFile(
-      reinterpret_cast<LPCWSTR>(file_path.value().c_str()), GENERIC_WRITE,
+      reinterpret_cast<LPCWSTR>(path.c_str()), GENERIC_WRITE,
       FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL));
 
   valid_ = file_.IsValid();
   if (!valid_) {
     PLOG(ERROR) << "CreateFile failed for path " << file_path_;
+
+    // Determine whether the path need long path support.
+    if (path.size() >= MAX_PATH && !IsLongPathsSupportEnabled()) {
+      LOG(ERROR) << "You might need to enable Long Path Support on Windows: "
+        << "https://learn.microsoft.com/en-us/windows/win32/fileio/"
+        "maximum-file-path-limitation?tabs=registry#enable-long-paths"
+        "-in-windows-10-version-1607-and-later";
+    }
   }
   return valid_;
 }
diff --git a/src/gn/file_writer_unittest.cc b/src/gn/file_writer_unittest.cc
index 49f533a..16f333d 100644
--- a/src/gn/file_writer_unittest.cc
+++ b/src/gn/file_writer_unittest.cc
@@ -8,13 +8,15 @@
 #include "base/files/scoped_temp_dir.h"
 #include "gn/filesystem_utils.h"
 
+#include "util/build_config.h"
+#include "util/sys_info.h"
 #include "util/test/test.h"
 
 TEST(FileWriter, SingleWrite) {
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
 
-  std::string data = "foo";
+  const std::string data = "foo";
 
   base::FilePath file_path = temp_dir.GetPath().AppendASCII("foo.txt");
 
@@ -30,7 +32,7 @@
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
 
-  std::string data = "Hello World!";
+  const std::string data = "Hello World!";
 
   base::FilePath file_path = temp_dir.GetPath().AppendASCII("foo.txt");
 
@@ -42,3 +44,26 @@
 
   EXPECT_TRUE(ContentsEqual(file_path, data));
 }
+
+#if defined(OS_WIN)
+TEST(FileWriter, LongPathWrite) {
+  if (!IsLongPathsSupportEnabled())
+    return;
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  const std::string data = "Hello World!";
+
+  base::FilePath file_path = temp_dir.GetPath().AppendASCII(std::string(255, 'A'));
+
+  EXPECT_GE(file_path.value().size(), MAX_PATH);
+
+  FileWriter writer;
+  EXPECT_TRUE(writer.Create(file_path));
+  EXPECT_TRUE(writer.Write(data));
+  EXPECT_TRUE(writer.Close());
+
+  EXPECT_TRUE(ContentsEqual(file_path, data));
+}
+#endif
\ No newline at end of file
diff --git a/src/util/sys_info.cc b/src/util/sys_info.cc
index c8dc590..4cf71db 100644
--- a/src/util/sys_info.cc
+++ b/src/util/sys_info.cc
@@ -14,8 +14,47 @@
 
 #if defined(OS_WIN)
 #include <windows.h>
+#include "base/win/registry.h"
 #endif
 
+bool IsLongPathsSupportEnabled() {
+#if defined(OS_WIN)
+  struct LongPathSupport {
+    LongPathSupport() {
+      // Probe ntdll.dll for RtlAreLongPathsEnabled, and call it if it exists.
+      HINSTANCE ntdll_lib = GetModuleHandleW(L"ntdll");
+      if (ntdll_lib) {
+        using FunctionType = BOOLEAN(WINAPI*)();
+        auto func_ptr = reinterpret_cast<FunctionType>(
+            GetProcAddress(ntdll_lib, "RtlAreLongPathsEnabled"));
+        if (func_ptr) {
+          supported = func_ptr();
+          return;
+        }
+      }
+
+      // If the ntdll approach failed, the registry approach is still reliable,
+      // because the manifest should've always be linked with gn.exe in Windows.
+      const char16_t key_name[] = uR"(SYSTEM\CurrentControlSet\Control\FileSystem)";
+      const char16_t value_name[] = u"LongPathsEnabled";
+
+      base::win::RegKey key(HKEY_LOCAL_MACHINE, key_name, KEY_READ);
+      DWORD value;
+      if (key.ReadValueDW(value_name, &value) == ERROR_SUCCESS) {
+        supported = value == 1;
+      }
+    }
+
+    bool supported = false;
+  };
+
+  static LongPathSupport s_long_paths;  // constructed lazily
+  return s_long_paths.supported;
+#else
+  return true;
+#endif
+}
+
 std::string OperatingSystemArchitecture() {
 #if defined(OS_POSIX)
   struct utsname info;
diff --git a/src/util/sys_info.h b/src/util/sys_info.h
index 68133e1..7a6924d 100644
--- a/src/util/sys_info.h
+++ b/src/util/sys_info.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+bool IsLongPathsSupportEnabled();
 std::string OperatingSystemArchitecture();
 int NumberOfProcessors();