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}