1#!/usr/bin/env python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Bootstraps gn.
7
8It is done by first building it manually in a temporary directory, then building
9it with its own BUILD.gn to the final destination.
10"""
11
12import contextlib
13import errno
14import logging
15import optparse
16import os
17import shutil
18import subprocess
19import sys
20import tempfile
21
22BOOTSTRAP_DIR = os.path.dirname(os.path.abspath(__file__))
23GN_ROOT = os.path.dirname(BOOTSTRAP_DIR)
24SRC_ROOT = os.path.dirname(os.path.dirname(GN_ROOT))
25
26is_linux = sys.platform.startswith('linux')
27is_mac = sys.platform.startswith('darwin')
28is_posix = is_linux or is_mac
29
30def check_call(cmd, **kwargs):
31  logging.debug('Running: %s', ' '.join(cmd))
32  subprocess.check_call(cmd, cwd=GN_ROOT, **kwargs)
33
34def mkdir_p(path):
35  try:
36    os.makedirs(path)
37  except OSError as e:
38    if e.errno == errno.EEXIST and os.path.isdir(path):
39      pass
40    else: raise
41
42@contextlib.contextmanager
43def scoped_tempdir():
44  path = tempfile.mkdtemp()
45  try:
46    yield path
47  finally:
48    shutil.rmtree(path)
49
50
51def main(argv):
52  parser = optparse.OptionParser(description=sys.modules[__name__].__doc__)
53  parser.add_option('-d', '--debug', action='store_true',
54                    help='Do a debug build. Defaults to release build.')
55  parser.add_option('-o', '--output',
56                    help='place output in PATH', metavar='PATH')
57  parser.add_option('-s', '--no-rebuild', action='store_true',
58                    help='Do not rebuild GN with GN.')
59  parser.add_option('-v', '--verbose', action='store_true',
60                    help='Log more details')
61  options, args = parser.parse_args(argv)
62
63  if args:
64    parser.error('Unrecognized command line arguments: %s.' % ', '.join(args))
65
66  logging.basicConfig(level=logging.DEBUG if options.verbose else logging.ERROR)
67
68  if options.debug:
69    build_rel = os.path.join('out', 'Debug')
70  else:
71    build_rel = os.path.join('out', 'Release')
72  build_root = os.path.join(SRC_ROOT, build_rel)
73
74  try:
75    with scoped_tempdir() as tempdir:
76      print 'Building gn manually in a temporary directory for bootstrapping...'
77      build_gn_with_ninja_manually(tempdir, options)
78      temp_gn = os.path.join(tempdir, 'gn')
79      out_gn = os.path.join(build_root, 'gn')
80
81      if options.no_rebuild:
82        mkdir_p(build_root)
83        shutil.copy2(temp_gn, out_gn)
84      else:
85        print 'Building gn using itself to %s...' % build_rel
86        build_gn_with_gn(temp_gn, build_rel, options)
87
88      if options.output:
89        # Preserve the executable permission bit.
90        shutil.copy2(out_gn, options.output)
91  except subprocess.CalledProcessError as e:
92    print >> sys.stderr, str(e)
93    return 1
94  return 0
95
96
97def build_gn_with_ninja_manually(tempdir, options):
98  write_ninja(os.path.join(tempdir, 'build.ninja'), options)
99  cmd = ['ninja', '-C', tempdir]
100  if options.verbose:
101    cmd.append('-v')
102  cmd.append('gn')
103  check_call(cmd)
104
105def write_ninja(path, options):
106  cc = os.environ.get('CC', '')
107  cxx = os.environ.get('CXX', '')
108  cflags = os.environ.get('CFLAGS', '').split()
109  cflags_cc = os.environ.get('CXXFLAGS', '').split()
110  ld = os.environ.get('LD', cxx)
111  ldflags = os.environ.get('LDFLAGS', '').split()
112  include_dirs = [SRC_ROOT]
113  libs = []
114
115  if is_posix:
116    if options.debug:
117      cflags.extend(['-O0', '-g'])
118    else:
119      cflags.extend(['-O2', '-g0'])
120
121    cflags.extend(['-D_FILE_OFFSET_BITS=64', '-pthread', '-pipe'])
122    cflags_cc.extend(['-std=gnu++11', '-Wno-c++11-narrowing'])
123
124  static_libraries = {
125      'base': {'sources': [], 'tool': 'cxx'},
126      'dynamic_annotations': {'sources': [], 'tool': 'cc'},
127      'gn': {'sources': [], 'tool': 'cxx'},
128  }
129
130  for name in os.listdir(GN_ROOT):
131    if not name.endswith('.cc'):
132      continue
133    if name.endswith('_unittest.cc'):
134      continue
135    if name in ['generate_test_gn_data.cc', 'run_all_unittests.cc']:
136      continue
137    full_path = os.path.join(GN_ROOT, name)
138    static_libraries['gn']['sources'].append(
139        os.path.relpath(full_path, SRC_ROOT))
140
141  static_libraries['dynamic_annotations']['sources'].extend([
142      'base/third_party/dynamic_annotations/dynamic_annotations.c',
143  ])
144  static_libraries['base']['sources'].extend([
145      'base/at_exit.cc',
146      'base/atomicops_internals_x86_gcc.cc',
147      'base/base_paths.cc',
148      'base/base_switches.cc',
149      'base/callback_internal.cc',
150      'base/command_line.cc',
151      'base/debug/alias.cc',
152      'base/debug/stack_trace.cc',
153      'base/debug/task_annotator.cc',
154      'base/debug/trace_event_impl.cc',
155      'base/debug/trace_event_impl_constants.cc',
156      'base/debug/trace_event_memory.cc',
157      'base/debug/trace_event_synthetic_delay.cc',
158      'base/environment.cc',
159      'base/files/file.cc',
160      'base/files/file_enumerator.cc',
161      'base/files/file_path.cc',
162      'base/files/file_path_constants.cc',
163      'base/files/file_util.cc',
164      'base/files/scoped_file.cc',
165      'base/json/json_parser.cc',
166      'base/json/json_reader.cc',
167      'base/json/json_string_value_serializer.cc',
168      'base/json/json_writer.cc',
169      'base/json/string_escape.cc',
170      'base/lazy_instance.cc',
171      'base/location.cc',
172      'base/logging.cc',
173      'base/memory/ref_counted.cc',
174      'base/memory/ref_counted_memory.cc',
175      'base/memory/singleton.cc',
176      'base/memory/weak_ptr.cc',
177      'base/message_loop/incoming_task_queue.cc',
178      'base/message_loop/message_loop.cc',
179      'base/message_loop/message_loop_proxy.cc',
180      'base/message_loop/message_loop_proxy_impl.cc',
181      'base/message_loop/message_pump.cc',
182      'base/message_loop/message_pump_default.cc',
183      'base/metrics/bucket_ranges.cc',
184      'base/metrics/histogram.cc',
185      'base/metrics/histogram_base.cc',
186      'base/metrics/histogram_samples.cc',
187      'base/metrics/sample_map.cc',
188      'base/metrics/sample_vector.cc',
189      'base/metrics/sparse_histogram.cc',
190      'base/metrics/statistics_recorder.cc',
191      'base/path_service.cc',
192      'base/pending_task.cc',
193      'base/pickle.cc',
194      'base/process/kill.cc',
195      'base/process/process_iterator.cc',
196      'base/process/process_metrics.cc',
197      'base/profiler/alternate_timer.cc',
198      'base/profiler/tracked_time.cc',
199      'base/run_loop.cc',
200      'base/sequence_checker_impl.cc',
201      'base/sequenced_task_runner.cc',
202      'base/strings/string16.cc',
203      'base/strings/string_number_conversions.cc',
204      'base/strings/string_piece.cc',
205      'base/strings/string_split.cc',
206      'base/strings/string_util.cc',
207      'base/strings/string_util_constants.cc',
208      'base/strings/stringprintf.cc',
209      'base/strings/utf_string_conversion_utils.cc',
210      'base/strings/utf_string_conversions.cc',
211      'base/synchronization/cancellation_flag.cc',
212      'base/synchronization/lock.cc',
213      'base/sys_info.cc',
214      'base/task_runner.cc',
215      'base/third_party/dmg_fp/dtoa_wrapper.cc',
216      'base/third_party/dmg_fp/g_fmt.cc',
217      'base/third_party/icu/icu_utf.cc',
218      'base/third_party/nspr/prtime.cc',
219      'base/thread_task_runner_handle.cc',
220      'base/threading/non_thread_safe_impl.cc',
221      'base/threading/post_task_and_reply_impl.cc',
222      'base/threading/sequenced_worker_pool.cc',
223      'base/threading/simple_thread.cc',
224      'base/threading/thread_checker_impl.cc',
225      'base/threading/thread_collision_warner.cc',
226      'base/threading/thread_id_name_manager.cc',
227      'base/threading/thread_local_storage.cc',
228      'base/threading/thread_restrictions.cc',
229      'base/time/time.cc',
230      'base/timer/elapsed_timer.cc',
231      'base/timer/timer.cc',
232      'base/tracked_objects.cc',
233      'base/tracking_info.cc',
234      'base/values.cc',
235      'base/vlog.cc',
236  ])
237
238  if is_posix:
239    static_libraries['base']['sources'].extend([
240        'base/base_paths_posix.cc',
241        'base/debug/debugger_posix.cc',
242        'base/debug/stack_trace_posix.cc',
243        'base/files/file_enumerator_posix.cc',
244        'base/files/file_posix.cc',
245        'base/files/file_util_posix.cc',
246        'base/message_loop/message_pump_libevent.cc',
247        'base/posix/file_descriptor_shuffle.cc',
248        'base/process/kill_posix.cc',
249        'base/process/process_handle_posix.cc',
250        'base/process/process_metrics_posix.cc',
251        'base/process/process_posix.cc',
252        'base/safe_strerror_posix.cc',
253        'base/synchronization/condition_variable_posix.cc',
254        'base/synchronization/lock_impl_posix.cc',
255        'base/synchronization/waitable_event_posix.cc',
256        'base/sys_info_posix.cc',
257        'base/threading/platform_thread_posix.cc',
258        'base/threading/thread_local_posix.cc',
259        'base/threading/thread_local_storage_posix.cc',
260        'base/time/time_posix.cc',
261    ])
262    static_libraries['libevent'] = {
263        'sources': [
264            'third_party/libevent/buffer.c',
265            'third_party/libevent/evbuffer.c',
266            'third_party/libevent/evdns.c',
267            'third_party/libevent/event.c',
268            'third_party/libevent/event_tagging.c',
269            'third_party/libevent/evrpc.c',
270            'third_party/libevent/evutil.c',
271            'third_party/libevent/http.c',
272            'third_party/libevent/log.c',
273            'third_party/libevent/poll.c',
274            'third_party/libevent/select.c',
275            'third_party/libevent/signal.c',
276            'third_party/libevent/strlcpy.c',
277        ],
278        'tool': 'cc',
279        'include_dirs': [],
280        'cflags': cflags + ['-DHAVE_CONFIG_H'],
281    }
282
283
284  if is_linux:
285    libs.extend(['-lrt'])
286    ldflags.extend(['-pthread'])
287
288    static_libraries['xdg_user_dirs'] = {
289        'sources': [
290            'base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc',
291        ],
292        'tool': 'cxx',
293    }
294    static_libraries['base']['sources'].extend([
295        'base/nix/xdg_util.cc',
296        'base/process/internal_linux.cc',
297        'base/process/process_handle_linux.cc',
298        'base/process/process_iterator_linux.cc',
299        'base/process/process_linux.cc',
300        'base/process/process_metrics_linux.cc',
301        'base/strings/sys_string_conversions_posix.cc',
302        'base/sys_info_linux.cc',
303        'base/threading/platform_thread_linux.cc',
304    ])
305    static_libraries['libevent']['include_dirs'].extend([
306        os.path.join(SRC_ROOT, 'third_party', 'libevent', 'linux')
307    ])
308    static_libraries['libevent']['sources'].extend([
309        'third_party/libevent/epoll.c',
310    ])
311
312
313  if is_mac:
314    static_libraries['base']['sources'].extend([
315        'base/base_paths_mac.mm',
316        'base/files/file_util_mac.mm',
317        'base/mac/bundle_locations.mm',
318        'base/mac/foundation_util.mm',
319        'base/mac/mach_logging.cc',
320        'base/mac/scoped_mach_port.cc',
321        'base/mac/scoped_nsautorelease_pool.mm',
322        'base/message_loop/message_pump_mac.mm',
323        'base/process/process_handle_mac.cc',
324        'base/process/process_iterator_mac.cc',
325        'base/strings/sys_string_conversions_mac.mm',
326        'base/time/time_mac.cc',
327        'base/threading/platform_thread_mac.mm',
328    ])
329    static_libraries['libevent']['include_dirs'].extend([
330        os.path.join(SRC_ROOT, 'third_party', 'libevent', 'mac')
331    ])
332    static_libraries['libevent']['sources'].extend([
333        'third_party/libevent/kqueue.c',
334    ])
335
336
337  if is_mac:
338    template_filename = 'build_mac.ninja.template'
339  else:
340    template_filename = 'build.ninja.template'
341
342  with open(os.path.join(GN_ROOT, 'bootstrap', template_filename)) as f:
343    ninja_template = f.read()
344
345  def src_to_obj(path):
346    return '%s' % os.path.splitext(path)[0] + '.o'
347
348  ninja_lines = []
349  for library, settings in static_libraries.iteritems():
350    for src_file in settings['sources']:
351      ninja_lines.extend([
352          'build %s: %s %s' % (src_to_obj(src_file),
353                               settings['tool'],
354                               os.path.join(SRC_ROOT, src_file)),
355          '  includes = %s' % ' '.join(
356              ['-I' + dirname for dirname in
357               include_dirs + settings.get('include_dirs', [])]),
358          '  cflags = %s' % ' '.join(cflags + settings.get('cflags', [])),
359          '  cflags_cc = %s' %
360              ' '.join(cflags_cc + settings.get('cflags_cc', [])),
361      ])
362      if cc:
363        ninja_lines.append('  cc = %s' % cc)
364      if cxx:
365        ninja_lines.append('  cxx = %s' % cxx)
366
367    ninja_lines.append('build %s.a: alink_thin %s' % (
368        library,
369        ' '.join([src_to_obj(src_file) for src_file in settings['sources']])))
370
371  if is_mac:
372    libs.extend([
373        '-framework', 'AppKit',
374        '-framework', 'CoreFoundation',
375        '-framework', 'Foundation',
376        '-framework', 'Security',
377    ]);
378
379  ninja_lines.extend([
380      'build gn: link %s' % (
381          ' '.join(['%s.a' % library for library in static_libraries])),
382      '  ldflags = %s' % ' '.join(ldflags),
383      '  libs = %s' % ' '.join(libs),
384  ])
385  if ld:
386    ninja_lines.append('  ld = %s' % ld)
387  else:
388    ninja_lines.append('  ld = $ldxx')
389
390  ninja_lines.append('')  # Make sure the file ends with a newline.
391
392  with open(path, 'w') as f:
393    f.write(ninja_template + '\n'.join(ninja_lines))
394
395
396def build_gn_with_gn(temp_gn, build_dir, options):
397  cmd = [temp_gn, 'gen', build_dir]
398  if not options.debug:
399    cmd.append('--args=is_debug=false')
400  check_call(cmd)
401
402  cmd = ['ninja', '-C', build_dir]
403  if options.verbose:
404    cmd.append('-v')
405  cmd.append('gn')
406  check_call(cmd)
407
408  if not debug:
409    check_call(['strip', os.path.join(build_dir, 'gn')])
410
411
412if __name__ == '__main__':
413  sys.exit(main(sys.argv[1:]))
414