Add a script to bootstrap GN from source.

For now only works on 64-bit Linux.

BUG=336441
R=brettw@chromium.org

Review URL: https://codereview.chromium.org/145133007

Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: f4821400355c5126dda788043ef6e148c067d160
diff --git a/tools/gn/bootstrap/bootstrap.py b/tools/gn/bootstrap/bootstrap.py
new file mode 100755
index 0000000..fd6538e
--- /dev/null
+++ b/tools/gn/bootstrap/bootstrap.py
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import contextlib
+import optparse
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+
+GN_ROOT = os.path.abspath(os.path.join(
+    os.path.dirname(os.path.abspath(__file__)), '..'))
+
+SRC_ROOT = os.path.abspath(os.path.join(
+    GN_ROOT, '..', '..'))
+
+BUILD_ROOT = os.path.abspath(os.path.join(
+    SRC_ROOT, 'out', 'Release'))
+
+
+def is_linux():
+  return sys.platform.startswith('linux')
+
+
+@contextlib.contextmanager
+def scoped_tempdir():
+  path = tempfile.mkdtemp()
+  try:
+    yield path
+  finally:
+    shutil.rmtree(path)
+
+
+def main(argv):
+  parser = optparse.OptionParser()
+  parser.add_option('-o', '--output',
+                    help='place output in PATH', metavar='PATH')
+  options, args = parser.parse_args(argv)
+
+  if not options.output:
+    parser.error('Please specify --output.')
+
+  if args:
+    parser.error('Unrecognized command line arguments: %s.' % ', '.join(args))
+
+  with scoped_tempdir() as tempdir:
+    print 'Building gn manually...'
+    write_ninja(os.path.join(tempdir, 'build.ninja'))
+    subprocess.check_call(['ninja', '-C', tempdir, 'gn'])
+
+    print 'Building gn using itself...'
+    subprocess.check_call([
+        os.path.join(tempdir, 'gn'),
+        '--args=is_debug=false',
+        '--output=%s' % os.path.relpath(BUILD_ROOT, SRC_ROOT)])
+    subprocess.check_call(['ninja', '-C', BUILD_ROOT, 'gn'])
+    subprocess.check_call(['strip', os.path.join(BUILD_ROOT, 'gn')])
+
+    # Preserve the executable permission bit.
+    shutil.copy2(os.path.join(BUILD_ROOT, 'gn'), options.output)
+
+  return 0
+
+
+def write_ninja(path):
+  cflags = os.environ.get('CFLAGS', '').split()
+  ldflags = os.environ.get('LDFLAGS', '').split()
+  include_dirs = [SRC_ROOT]
+  libs = []
+
+  static_libraries = {
+      'base': {'sources': [], 'tool': 'cxx'},
+      'dynamic_annotations': {'sources': [], 'tool': 'cc'},
+      'gn': {'sources': [], 'tool': 'cxx'},
+  }
+
+  for name in os.listdir(GN_ROOT):
+    if not name.endswith('.cc'):
+      continue
+    if name.endswith('_unittest.cc'):
+      continue
+    if name in ['generate_test_gn_data.cc']:
+      continue
+    full_path = os.path.join(GN_ROOT, name)
+    static_libraries['gn']['sources'].append(
+        os.path.relpath(full_path, SRC_ROOT))
+
+  static_libraries['dynamic_annotations']['sources'].extend([
+      'base/third_party/dynamic_annotations/dynamic_annotations.c',
+  ])
+  static_libraries['base']['sources'].extend([
+      'base/at_exit.cc',
+      'base/atomicops_internals_x86_gcc.cc',
+      'base/base_paths.cc',
+      'base/base_switches.cc',
+      'base/callback_internal.cc',
+      'base/command_line.cc',
+      'base/debug/alias.cc',
+      'base/debug/stack_trace.cc',
+      'base/debug/trace_event_impl.cc',
+      'base/debug/trace_event_impl_constants.cc',
+      'base/debug/trace_event_memory.cc',
+      'base/debug/trace_event_synthetic_delay.cc',
+      'base/environment.cc',
+      'base/file_util.cc',
+      'base/files/file.cc',
+      'base/files/file_enumerator.cc',
+      'base/files/file_path.cc',
+      'base/files/file_path_constants.cc',
+      'base/json/json_parser.cc',
+      'base/json/json_reader.cc',
+      'base/json/json_string_value_serializer.cc',
+      'base/json/json_writer.cc',
+      'base/json/string_escape.cc',
+      'base/lazy_instance.cc',
+      'base/location.cc',
+      'base/logging.cc',
+      'base/memory/ref_counted.cc',
+      'base/memory/ref_counted_memory.cc',
+      'base/memory/singleton.cc',
+      'base/memory/weak_ptr.cc',
+      'base/message_loop/incoming_task_queue.cc',
+      'base/message_loop/message_loop.cc',
+      'base/message_loop/message_loop_proxy.cc',
+      'base/message_loop/message_loop_proxy_impl.cc',
+      'base/message_loop/message_pump.cc',
+      'base/message_loop/message_pump_default.cc',
+      'base/metrics/bucket_ranges.cc',
+      'base/metrics/histogram.cc',
+      'base/metrics/histogram_base.cc',
+      'base/metrics/histogram_samples.cc',
+      'base/metrics/sample_map.cc',
+      'base/metrics/sample_vector.cc',
+      'base/metrics/sparse_histogram.cc',
+      'base/metrics/statistics_recorder.cc',
+      'base/path_service.cc',
+      'base/pending_task.cc',
+      'base/pickle.cc',
+      'base/process/kill.cc',
+      'base/process/process_iterator.cc',
+      'base/process/process_metrics.cc',
+      'base/profiler/alternate_timer.cc',
+      'base/profiler/tracked_time.cc',
+      'base/run_loop.cc',
+      'base/sequence_checker_impl.cc',
+      'base/sequenced_task_runner.cc',
+      'base/strings/string16.cc',
+      'base/strings/string_number_conversions.cc',
+      'base/strings/string_piece.cc',
+      'base/strings/string_split.cc',
+      'base/strings/string_util.cc',
+      'base/strings/string_util_constants.cc',
+      'base/strings/stringprintf.cc',
+      'base/strings/utf_string_conversion_utils.cc',
+      'base/strings/utf_string_conversions.cc',
+      'base/synchronization/cancellation_flag.cc',
+      'base/synchronization/lock.cc',
+      'base/sys_info.cc',
+      'base/task_runner.cc',
+      'base/third_party/dmg_fp/dtoa_wrapper.cc',
+      'base/third_party/dmg_fp/g_fmt.cc',
+      'base/third_party/icu/icu_utf.cc',
+      'base/third_party/nspr/prtime.cc',
+      'base/thread_task_runner_handle.cc',
+      'base/threading/non_thread_safe_impl.cc',
+      'base/threading/post_task_and_reply_impl.cc',
+      'base/threading/sequenced_worker_pool.cc',
+      'base/threading/simple_thread.cc',
+      'base/threading/thread_checker_impl.cc',
+      'base/threading/thread_collision_warner.cc',
+      'base/threading/thread_id_name_manager.cc',
+      'base/threading/thread_local_storage.cc',
+      'base/threading/thread_restrictions.cc',
+      'base/time/time.cc',
+      'base/timer/elapsed_timer.cc',
+      'base/timer/timer.cc',
+      'base/tracked_objects.cc',
+      'base/tracking_info.cc',
+      'base/values.cc',
+      'base/vlog.cc',
+  ])
+
+  if is_linux():
+    static_libraries['libevent'] = {
+        'sources': [
+            'third_party/libevent/buffer.c',
+            'third_party/libevent/epoll.c',
+            'third_party/libevent/evbuffer.c',
+            'third_party/libevent/evdns.c',
+            'third_party/libevent/event.c',
+            'third_party/libevent/event_tagging.c',
+            'third_party/libevent/evrpc.c',
+            'third_party/libevent/evutil.c',
+            'third_party/libevent/http.c',
+            'third_party/libevent/log.c',
+            'third_party/libevent/poll.c',
+            'third_party/libevent/select.c',
+            'third_party/libevent/signal.c',
+            'third_party/libevent/strlcpy.c',
+        ],
+        'tool': 'cc',
+        'include_dirs': [
+            os.path.join(SRC_ROOT, 'third_party', 'libevent', 'linux')
+         ],
+        'cflags': cflags + ['-DHAVE_CONFIG_H'],
+    }
+    static_libraries['xdg_user_dirs'] = {
+        'sources': [
+            'base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc',
+        ],
+        'tool': 'cxx',
+    }
+    static_libraries['base']['sources'].extend([
+        'base/base_paths_posix.cc',
+        'base/debug/debugger_posix.cc',
+        'base/debug/stack_trace_posix.cc',
+        'base/file_util_posix.cc',
+        'base/files/file_enumerator_posix.cc',
+        'base/files/file_posix.cc',
+        'base/message_loop/message_pump_glib.cc',
+        'base/message_loop/message_pump_gtk.cc',
+        'base/message_loop/message_pump_libevent.cc',
+        'base/nix/xdg_util.cc',
+        'base/posix/file_descriptor_shuffle.cc',
+        'base/process/internal_linux.cc',
+        'base/process/kill_posix.cc',
+        'base/process/process_handle_linux.cc',
+        'base/process/process_handle_posix.cc',
+        'base/process/process_iterator_linux.cc',
+        'base/process/process_linux.cc',
+        'base/process/process_metrics_linux.cc',
+        'base/process/process_metrics_posix.cc',
+        'base/process/process_posix.cc',
+        'base/safe_strerror_posix.cc',
+        'base/strings/sys_string_conversions_posix.cc',
+        'base/synchronization/condition_variable_posix.cc',
+        'base/synchronization/lock_impl_posix.cc',
+        'base/synchronization/waitable_event_posix.cc',
+        'base/sys_info_linux.cc',
+        'base/sys_info_posix.cc',
+        'base/threading/platform_thread_linux.cc',
+        'base/threading/platform_thread_posix.cc',
+        'base/threading/thread_local_posix.cc',
+        'base/threading/thread_local_storage_posix.cc',
+        'base/time/time_posix.cc',
+    ])
+
+    cflags.extend(['-O2', '-pthread', '-pipe'])
+
+    static_libraries['base'].setdefault('cflags', []).extend(
+        subprocess.check_output(
+            ['pkg-config', 'gtk+-2.0', 'x11', '--cflags']).split())
+    ldflags.extend(['-pthread'])
+    ldflags.extend(subprocess.check_output(
+        ['pkg-config', 'gtk+-2.0', 'x11',
+         '--libs-only-L', '--libs-only-other']).split())
+    libs.extend(subprocess.check_output(
+        ['pkg-config', 'gtk+-2.0', 'x11', '--libs-only-l']).split())
+
+  with open(os.path.join(GN_ROOT, 'bootstrap', 'build.ninja.template')) as f:
+    ninja_template = f.read()
+
+  def src_to_obj(path):
+    return '%s' % os.path.splitext(src_file)[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', [])),
+      ])
+
+    ninja_lines.append('build %s.a: alink_thin %s' % (
+        library,
+        ' '.join([src_to_obj(src_file) for src_file in settings['sources']])))
+
+  ninja_lines.extend([
+      'build gn: link %s' % (
+          ' '.join(['%s.a' % library for library in static_libraries])),
+      '  ld = $ldxx',
+      '  ldflags = %s' % ' '.join(ldflags),
+      '  libs = %s' % ' '.join(libs),
+      '',  # Make sure the file ends with a newline.
+  ])
+
+  with open(path, 'w') as f:
+    f.write(ninja_template + '\n'.join(ninja_lines))
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/gn/bootstrap/build.ninja.template b/tools/gn/bootstrap/build.ninja.template
new file mode 100644
index 0000000..2ef5301
--- /dev/null
+++ b/tools/gn/bootstrap/build.ninja.template
@@ -0,0 +1,25 @@
+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
+  depfile = $out.d
+  deps = gcc
+
+rule cxx
+  command = $cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc -c $in -o $out
+  description = CXX $out
+  depfile = $out.d
+  deps = gcc
+
+rule alink_thin
+  command = rm -f $out && $ar rcsT $out $in
+  description = AR $out
+
+rule link
+  command = $ld $ldflags -o $out -Wl,--start-group $in $solibs -Wl,--end-group $libs
+  description = LINK $out