GN: Add Windows support to bootstrap.py

Previously GN could only be bootstrapped on Linux/Mac.
This CL adds support for Windows (Visual Studio only at this time).

Note that bootstrap.py expects to be run inside a VS2015 Command Prompt
(i.e. vcvarsall.bat has been run)

Example:
> call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\vc\vcvarsall.bat" x86
> tools/gn/bootstrap/bootstrap.py

BUG=

Review-Url: https://codereview.chromium.org/2064513002
Cr-Original-Commit-Position: refs/heads/master@{#403499}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 3a32f0297bbd2c5673cf02923244ecfeda927b32
diff --git a/tools/gn/bootstrap/bootstrap.py b/tools/gn/bootstrap/bootstrap.py
index 72ffd60..9a3740e 100755
--- a/tools/gn/bootstrap/bootstrap.py
+++ b/tools/gn/bootstrap/bootstrap.py
@@ -29,12 +29,18 @@
 GN_ROOT = os.path.dirname(BOOTSTRAP_DIR)
 SRC_ROOT = os.path.dirname(os.path.dirname(GN_ROOT))
 
+is_win = sys.platform.startswith('win')
 is_linux = sys.platform.startswith('linux')
 is_mac = sys.platform.startswith('darwin')
 is_posix = is_linux or is_mac
 
 def check_call(cmd, **kwargs):
   logging.debug('Running: %s', ' '.join(cmd))
+
+  # With shell=False, subprocess expects an executable on Windows
+  if is_win and cmd and cmd[0].endswith('.py'):
+    cmd.insert(0, sys.executable)
+
   subprocess.check_call(cmd, cwd=GN_ROOT, **kwargs)
 
 def mkdir_p(path):
@@ -66,6 +72,10 @@
   temp_gn = os.path.join(tempdir, 'gn')
   out_gn = os.path.join(build_root, 'gn')
 
+  if is_win:
+    temp_gn += '.exe'
+    out_gn += '.exe'
+
   if options.no_rebuild:
     mkdir_p(build_root)
     shutil.copy2(temp_gn, out_gn)
@@ -113,22 +123,36 @@
     return 1
   return 0
 
+def write_compiled_message(root_gen_dir, source):
+  path = os.path.join(root_gen_dir, os.path.dirname(source))
+  mkdir_p(path)
+  check_call([
+      'mc.exe',
+      '-r', path, '-h', path,
+      '-u', '-um',
+      os.path.join(SRC_ROOT, source),
+  ])
 
 def write_buildflag_header_manually(root_gen_dir, header, flags):
   mkdir_p(os.path.join(root_gen_dir, os.path.dirname(header)))
-  with tempfile.NamedTemporaryFile() as f:
+
+  # Don't use tempfile.NamedTemporaryFile() here.
+  # It doesn't work correctly on Windows.
+  # see: http://bugs.python.org/issue14243
+  temp_path = os.path.join(root_gen_dir, header + '.tmp')
+  with open(temp_path, 'w') as f:
     f.write('--flags')
     for name,value in flags.items():
       f.write(' ' + name + '=' + value)
-    f.flush()
 
-    check_call([
-        os.path.join(SRC_ROOT, 'build', 'write_buildflag_header.py'),
-        '--output', header,
-        '--gen-dir', root_gen_dir,
-        '--definitions', f.name,
-    ])
+  check_call([
+      os.path.join(SRC_ROOT, 'build', 'write_buildflag_header.py'),
+      '--output', header,
+      '--gen-dir', root_gen_dir,
+      '--definitions', temp_path,
+  ])
 
+  os.remove(temp_path)
 
 def build_gn_with_ninja_manually(tempdir, options):
   root_gen_dir = os.path.join(tempdir, 'gen')
@@ -150,19 +174,125 @@
         'default'
     ])
 
-  write_ninja(os.path.join(tempdir, 'build.ninja'), root_gen_dir, options)
+  if is_win:
+    write_buildflag_header_manually(root_gen_dir, 'base/win/base_features.h',
+        {'SINGLE_MODULE_MODE_HANDLE_VERIFIER': 'true'})
+
+    write_compiled_message(root_gen_dir,
+        'base/trace_event/etw_manifest/chrome_events_win.man')
+
+  write_gn_ninja(os.path.join(tempdir, 'build.ninja'),
+                 root_gen_dir, options)
   cmd = ['ninja', '-C', tempdir]
   if options.verbose:
     cmd.append('-v')
-  cmd.append('gn')
+
+  if is_win:
+    cmd.append('gn.exe')
+  else:
+    cmd.append('gn')
+
   check_call(cmd)
 
-def write_ninja(path, root_gen_dir, options):
-  cc = os.environ.get('CC', '')
-  cxx = os.environ.get('CXX', '')
+def write_generic_ninja(path, static_libraries, executables,
+                        cc, cxx, ar, ld,
+                        cflags=[], cflags_cc=[], ldflags=[],
+                        include_dirs=[], solibs=[]):
+  ninja_header_lines = [
+    'cc = ' + cc,
+    'cxx = ' + cxx,
+    'ar = ' + ar,
+    'ld = ' + ld,
+    '',
+  ]
+
+  if is_win:
+    template_filename = 'build_vs.ninja.template'
+  elif is_mac:
+    template_filename = 'build_mac.ninja.template'
+  else:
+    template_filename = 'build.ninja.template'
+
+  with open(os.path.join(GN_ROOT, 'bootstrap', template_filename)) as f:
+    ninja_template = f.read()
+
+  if is_win:
+    executable_ext = '.exe'
+    library_ext = '.lib'
+    object_ext = '.obj'
+  else:
+    executable_ext = ''
+    library_ext = '.a'
+    object_ext = '.o'
+
+  def escape_path_ninja(path):
+      return path.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
+
+  def src_to_obj(path):
+    return escape_path_ninja('%s' % os.path.splitext(path)[0] + object_ext)
+
+  def library_to_a(library):
+    return '%s%s' % (library, library_ext)
+
+  ninja_lines = []
+  def build_source(src_file, settings):
+    ninja_lines.extend([
+        'build %s: %s %s' % (src_to_obj(src_file),
+                             settings['tool'],
+                             escape_path_ninja(
+                                 os.path.join(SRC_ROOT, src_file))),
+        '  includes = %s' % ' '.join(
+            ['-I' + escape_path_ninja(dirname) for dirname in
+             include_dirs + settings.get('include_dirs', [])]),
+        '  cflags = %s' % ' '.join(cflags + settings.get('cflags', [])),
+        '  cflags_cc = %s' %
+            ' '.join(cflags_cc + settings.get('cflags_cc', [])),
+    ])
+
+  for library, settings in static_libraries.iteritems():
+    for src_file in settings['sources']:
+      build_source(src_file, settings)
+
+    ninja_lines.append('build %s: alink_thin %s' % (
+        library_to_a(library),
+        ' '.join([src_to_obj(src_file) for src_file in settings['sources']])))
+
+  for executable, settings in executables.iteritems():
+    for src_file in settings['sources']:
+      build_source(src_file, settings)
+
+    ninja_lines.extend([
+      'build %s%s: link %s | %s' % (
+          executable, executable_ext,
+          ' '.join([src_to_obj(src_file) for src_file in settings['sources']]),
+          ' '.join([library_to_a(library) for library in settings['libs']])),
+      '  ldflags = %s' % ' '.join(ldflags),
+      '  solibs = %s' % ' '.join(solibs),
+      '  libs = %s' % ' '.join(
+          [library_to_a(library) for library in settings['libs']]),
+    ])
+
+  ninja_lines.append('')  # Make sure the file ends with a newline.
+
+  with open(path, 'w') as f:
+    f.write('\n'.join(ninja_header_lines))
+    f.write(ninja_template)
+    f.write('\n'.join(ninja_lines))
+
+def write_gn_ninja(path, root_gen_dir, options):
+  if is_win:
+    cc = os.environ.get('CC', 'cl.exe')
+    cxx = os.environ.get('CXX', 'cl.exe')
+    ld = os.environ.get('LD', 'link.exe')
+    ar = os.environ.get('AR', 'lib.exe')
+  else:
+    cc = os.environ.get('CC', 'cc')
+    cxx = os.environ.get('CXX', 'c++')
+    ld = os.environ.get('LD', cxx)
+    ar = os.environ.get('AR', 'ar')
+
   cflags = os.environ.get('CFLAGS', '').split()
   cflags_cc = os.environ.get('CXXFLAGS', '').split()
-  ld = os.environ.get('LD', cxx)
   ldflags = os.environ.get('LDFLAGS', '').split()
   include_dirs = [root_gen_dir, SRC_ROOT]
   libs = []
@@ -179,16 +309,43 @@
 
     cflags.extend([
         '-D_FILE_OFFSET_BITS=64',
+        '-D__STDC_CONSTANT_MACROS', '-D__STDC_FORMAT_MACROS',
         '-pthread',
         '-pipe',
         '-fno-exceptions'
     ])
     cflags_cc.extend(['-std=c++11', '-Wno-c++11-narrowing'])
+  elif is_win:
+    if not options.debug:
+      cflags.extend(['/Ox', '/DNDEBUG', '/GL'])
+      ldflags.extend(['/LTCG', '/OPT:REF', '/OPT:ICF'])
+
+    cflags.extend([
+        '/FS',
+        '/Gy',
+        '/W3', '/wd4244',
+        '/Zi',
+        '/DWIN32_LEAN_AND_MEAN', '/DNOMINMAX',
+        '/D_CRT_SECURE_NO_DEPRECATE', '/D_SCL_SECURE_NO_DEPRECATE',
+        '/D_WIN32_WINNT=0x0A00', '/DWINVER=0x0A00',
+        '/DUNICODE', '/D_UNICODE',
+    ])
+    cflags_cc.extend([
+        '/GR-',
+        '/D_HAS_EXCEPTIONS=0',
+    ])
+    # TODO(tim): Support for 64bit builds?
+    ldflags.extend(['/MACHINE:x86', '/DEBUG'])
 
   static_libraries = {
       'base': {'sources': [], 'tool': 'cxx', 'include_dirs': []},
       'dynamic_annotations': {'sources': [], 'tool': 'cc', 'include_dirs': []},
-      'gn': {'sources': [], 'tool': 'cxx', 'include_dirs': []},
+      'gn_lib': {'sources': [], 'tool': 'cxx', 'include_dirs': []},
+  }
+
+  executables = {
+      'gn': {'sources': ['tools/gn/gn_main.cc'],
+             'tool': 'cxx', 'include_dirs': [], 'libs': []},
   }
 
   for name in os.listdir(GN_ROOT):
@@ -198,8 +355,10 @@
       continue
     if name == 'run_all_unittests.cc':
       continue
+    if name == 'gn_main.cc':
+      continue
     full_path = os.path.join(GN_ROOT, name)
-    static_libraries['gn']['sources'].append(
+    static_libraries['gn_lib']['sources'].append(
         os.path.relpath(full_path, SRC_ROOT))
 
   static_libraries['dynamic_annotations']['sources'].extend([
@@ -272,7 +431,6 @@
       'base/sequenced_task_runner.cc',
       'base/sha1.cc',
       'base/strings/pattern.cc',
-      'base/strings/string16.cc',
       'base/strings/string_number_conversions.cc',
       'base/strings/string_piece.cc',
       'base/strings/string_split.cc',
@@ -352,6 +510,7 @@
         'base/process/process_handle_posix.cc',
         'base/process/process_metrics_posix.cc',
         'base/process/process_posix.cc',
+        'base/strings/string16.cc',
         'base/synchronization/condition_variable_posix.cc',
         'base/synchronization/lock_impl_posix.cc',
         'base/synchronization/read_write_lock_posix.cc',
@@ -386,9 +545,8 @@
         'cflags': cflags + ['-DHAVE_CONFIG_H'],
     }
 
-
   if is_linux:
-    libs.extend(['-lrt'])
+    libs.extend(['-lrt', '-latomic'])
     ldflags.extend(['-pthread'])
 
     static_libraries['xdg_user_dirs'] = {
@@ -454,65 +612,105 @@
         'base/third_party/libevent/kqueue.c',
     ])
 
-
-  if is_mac:
-    template_filename = 'build_mac.ninja.template'
-  else:
-    template_filename = 'build.ninja.template'
-
-  with open(os.path.join(GN_ROOT, 'bootstrap', template_filename)) as f:
-    ninja_template = f.read()
-
-  def src_to_obj(path):
-    return '%s' % os.path.splitext(path)[0] + '.o'
-
-  ninja_lines = []
-  for library, settings in static_libraries.iteritems():
-    for src_file in settings['sources']:
-      ninja_lines.extend([
-          'build %s: %s %s' % (src_to_obj(src_file),
-                               settings['tool'],
-                               os.path.join(SRC_ROOT, src_file)),
-          '  includes = %s' % ' '.join(
-              ['-I' + dirname for dirname in
-               include_dirs + settings.get('include_dirs', [])]),
-          '  cflags = %s' % ' '.join(cflags + settings.get('cflags', [])),
-          '  cflags_cc = %s' %
-              ' '.join(cflags_cc + settings.get('cflags_cc', [])),
-      ])
-      if cc:
-        ninja_lines.append('  cc = %s' % cc)
-      if cxx:
-        ninja_lines.append('  cxx = %s' % cxx)
-
-    ninja_lines.append('build %s.a: alink_thin %s' % (
-        library,
-        ' '.join([src_to_obj(src_file) for src_file in settings['sources']])))
-
-  if is_mac:
     libs.extend([
         '-framework', 'AppKit',
         '-framework', 'CoreFoundation',
         '-framework', 'Foundation',
         '-framework', 'Security',
-    ]);
+    ])
 
-  ninja_lines.extend([
-      'build gn: link %s' % (
-          ' '.join(['%s.a' % library for library in static_libraries])),
-      '  ldflags = %s' % ' '.join(ldflags),
-      '  libs = %s' % ' '.join(libs),
-  ])
-  if ld:
-    ninja_lines.append('  ld = %s' % ld)
-  else:
-    ninja_lines.append('  ld = $ldxx')
+  if is_win:
+    static_libraries['base']['sources'].extend([
+        'base/base_paths_win.cc',
+        'base/cpu.cc',
+        'base/debug/close_handle_hook_win.cc',
+        'base/debug/debugger.cc',
+        'base/debug/debugger_win.cc',
+        'base/debug/profiler.cc',
+        'base/debug/stack_trace_win.cc',
+        'base/file_version_info_win.cc',
+        'base/files/file_enumerator_win.cc',
+        'base/files/file_path_watcher_win.cc',
+        'base/files/file_util_win.cc',
+        'base/files/file_win.cc',
+        'base/files/memory_mapped_file_win.cc',
+        'base/logging_win.cc',
+        'base/memory/memory_pressure_monitor_win.cc',
+        'base/memory/shared_memory_handle_win.cc',
+        'base/memory/shared_memory_win.cc',
+        'base/message_loop/message_pump_win.cc',
+        'base/native_library_win.cc',
+        'base/power_monitor/power_monitor_device_source_win.cc',
+        'base/process/kill_win.cc',
+        'base/process/launch_win.cc',
+        'base/process/memory_win.cc',
+        'base/process/process_handle_win.cc',
+        'base/process/process_info_win.cc',
+        'base/process/process_iterator_win.cc',
+        'base/process/process_metrics_win.cc',
+        'base/process/process_win.cc',
+        'base/profiler/native_stack_sampler_win.cc',
+        'base/profiler/win32_stack_frame_unwinder.cc',
+        'base/rand_util.cc',
+        'base/rand_util_win.cc',
+        'base/strings/sys_string_conversions_win.cc',
+        'base/sync_socket_win.cc',
+        'base/synchronization/condition_variable_win.cc',
+        'base/synchronization/lock_impl_win.cc',
+        'base/synchronization/read_write_lock_win.cc',
+        'base/synchronization/waitable_event_watcher_win.cc',
+        'base/synchronization/waitable_event_win.cc',
+        'base/sys_info_win.cc',
+        'base/threading/platform_thread_win.cc',
+        'base/threading/thread_local_storage_win.cc',
+        'base/threading/thread_local_win.cc',
+        'base/threading/worker_pool_win.cc',
+        'base/time/time_win.cc',
+        'base/timer/hi_res_timer_manager_win.cc',
+        'base/trace_event/heap_profiler_allocation_register_win.cc',
+        'base/trace_event/trace_event_etw_export_win.cc',
+        'base/trace_event/winheap_dump_provider_win.cc',
+        'base/win/enum_variant.cc',
+        'base/win/event_trace_controller.cc',
+        'base/win/event_trace_provider.cc',
+        'base/win/i18n.cc',
+        'base/win/iat_patch_function.cc',
+        'base/win/iunknown_impl.cc',
+        'base/win/message_window.cc',
+        'base/win/object_watcher.cc',
+        'base/win/pe_image.cc',
+        'base/win/process_startup_helper.cc',
+        'base/win/registry.cc',
+        'base/win/resource_util.cc',
+        'base/win/scoped_bstr.cc',
+        'base/win/scoped_handle.cc',
+        'base/win/scoped_process_information.cc',
+        'base/win/scoped_variant.cc',
+        'base/win/shortcut.cc',
+        'base/win/startup_information.cc',
+        'base/win/wait_chain.cc',
+        'base/win/win_util.cc',
+        'base/win/windows_version.cc',
+        'base/win/wrapped_window_proc.cc',
+    ])
 
-  ninja_lines.append('')  # Make sure the file ends with a newline.
+    libs.extend([
+        'kernel32.lib',
+        'user32.lib',
+        'shell32.lib',
+        'ole32.lib',
+        'winmm.lib',
+        'ws2_32.lib',
+        'userenv.lib',
+        'version.lib',
+        'dbghelp.lib',
+    ])
 
-  with open(path, 'w') as f:
-    f.write(ninja_template + '\n'.join(ninja_lines))
+  # we just build static libraries that GN needs
+  executables['gn']['libs'].extend(static_libraries.keys())
 
+  write_generic_ninja(path, static_libraries, executables, cc, cxx, ar, ld,
+                      cflags, cflags_cc, ldflags, include_dirs, libs)
 
 def build_gn_with_gn(temp_gn, build_dir, options):
   gn_gen_args = options.gn_gen_args or ''
@@ -527,7 +725,7 @@
   cmd.append('gn')
   check_call(cmd)
 
-  if not options.debug:
+  if not options.debug and not is_win:
     check_call(['strip', os.path.join(build_dir, 'gn')])
 
 
diff --git a/tools/gn/bootstrap/build.ninja.template b/tools/gn/bootstrap/build.ninja.template
index 2ef5301..e59854b 100644
--- a/tools/gn/bootstrap/build.ninja.template
+++ b/tools/gn/bootstrap/build.ninja.template
@@ -1,9 +1,3 @@
-cc = cc
-cxx = c++
-ld = $cc
-ldxx = $cxx
-ar = ar
-
 rule cc
   command = $cc -MMD -MF $out.d $defines $includes $cflags $cflags_c -c $in -o $out
   description = CC $out
@@ -21,5 +15,5 @@
   description = AR $out
 
 rule link
-  command = $ld $ldflags -o $out -Wl,--start-group $in $solibs -Wl,--end-group $libs
+  command = $ld $ldflags -o $out -Wl,--start-group $in $libs -Wl,--end-group $solibs
   description = LINK $out
diff --git a/tools/gn/bootstrap/build_mac.ninja.template b/tools/gn/bootstrap/build_mac.ninja.template
index 409ea75..ff82eb8 100644
--- a/tools/gn/bootstrap/build_mac.ninja.template
+++ b/tools/gn/bootstrap/build_mac.ninja.template
@@ -1,9 +1,3 @@
-cc = cc
-cxx = c++
-ld = $cc
-ldxx = $cxx
-ar = ar
-
 rule cc
   command = $cc -MMD -MF $out.d $defines $includes $cflags $cflags_c -c $in -o $out
   description = CC $out
diff --git a/tools/gn/bootstrap/build_vs.ninja.template b/tools/gn/bootstrap/build_vs.ninja.template
new file mode 100644
index 0000000..6e98575
--- /dev/null
+++ b/tools/gn/bootstrap/build_vs.ninja.template
@@ -0,0 +1,25 @@
+rule cc
+  command = $cc /nologo /showIncludes /FC @${out}.rsp /c ${in} /Fo${out}
+  description = CC ${out}
+  rspfile = ${out}.rsp
+  rspfile_content = ${defines} ${includes} ${cflags} ${cflags_c}
+  deps = msvc
+
+rule cxx
+  command = $cxx /nologo /showIncludes /FC @${out}.rsp /c ${in} /Fo${out}
+  description = CXX ${out}
+  rspfile = ${out}.rsp
+  rspfile_content = ${defines} ${includes} ${cflags} ${cflags_cc}
+  deps = msvc
+
+rule alink_thin
+  command = $ar /nologo /ignore:4221 /OUT:${out} @${out}.rsp
+  description = LIB ${out}
+  rspfile = ${out}.rsp
+  rspfile_content = ${in_newline}
+
+rule link
+  command = $ld /nologo /OUT:${out} /PDB:${out}.pdb @${out}.rsp
+  description = LINK ${out}
+  rspfile = ${out}.rsp
+  rspfile_content = ${in_newline} ${libs} ${solibs} ${ldflags}