from optparse import OptionParser
import os
+import pipes
+import string
+import subprocess
import sys
-sys.path.insert(0, 'misc')
+sys.path.insert(0, 'misc')
import ninja_syntax
+
+class Platform(object):
+ """Represents a host/target platform and its specific build attributes."""
+ def __init__(self, platform):
+ self._platform = platform
+ if self._platform is not None:
+ return
+ self._platform = sys.platform
+ if self._platform.startswith('linux'):
+ self._platform = 'linux'
+ elif self._platform.startswith('freebsd'):
+ self._platform = 'freebsd'
+ elif self._platform.startswith('gnukfreebsd'):
+ self._platform = 'freebsd'
+ elif self._platform.startswith('openbsd'):
+ self._platform = 'openbsd'
+ elif self._platform.startswith('solaris') or self._platform == 'sunos5':
+ self._platform = 'solaris'
+ elif self._platform.startswith('mingw'):
+ self._platform = 'mingw'
+ elif self._platform.startswith('win'):
+ self._platform = 'msvc'
+ elif self._platform.startswith('bitrig'):
+ self._platform = 'bitrig'
+ elif self._platform.startswith('netbsd'):
+ self._platform = 'netbsd'
+
+ @staticmethod
+ def known_platforms():
+ return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5',
+ 'mingw', 'msvc', 'gnukfreebsd', 'bitrig', 'netbsd']
+
+ def platform(self):
+ return self._platform
+
+ def is_linux(self):
+ return self._platform == 'linux'
+
+ def is_mingw(self):
+ return self._platform == 'mingw'
+
+ def is_msvc(self):
+ return self._platform == 'msvc'
+
+ def msvc_needs_fs(self):
+ popen = subprocess.Popen(['cl', '/nologo', '/?'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = popen.communicate()
+ return '/FS ' in str(out)
+
+ def is_windows(self):
+ return self.is_mingw() or self.is_msvc()
+
+ def is_solaris(self):
+ return self._platform == 'solaris'
+
+ def uses_usr_local(self):
+ return self._platform in ('freebsd', 'openbsd', 'bitrig')
+
+ def supports_ppoll(self):
+ return self._platform in ('linux', 'openbsd', 'bitrig')
+
+ def supports_ninja_browse(self):
+ return not self.is_windows() and not self.is_solaris()
+
+
+class Bootstrap:
+ """API shim for ninja_syntax.Writer that instead runs the commands.
+
+ Used to bootstrap Ninja from scratch. In --bootstrap mode this
+ class is used to execute all the commands to build an executable.
+ It also proxies all calls to an underlying ninja_syntax.Writer, to
+ behave like non-bootstrap mode.
+ """
+ def __init__(self, writer, verbose=False):
+ self.writer = writer
+ self.verbose = verbose
+ # Map of variable name => expanded variable value.
+ self.vars = {}
+ # Map of rule name => dict of rule attributes.
+ self.rules = {
+ 'phony': {}
+ }
+
+ def comment(self, text):
+ return self.writer.comment(text)
+
+ def newline(self):
+ return self.writer.newline()
+
+ def variable(self, key, val):
+ self.vars[key] = self._expand(val)
+ return self.writer.variable(key, val)
+
+ def rule(self, name, **kwargs):
+ self.rules[name] = kwargs
+ return self.writer.rule(name, **kwargs)
+
+ def build(self, outputs, rule, inputs=None, **kwargs):
+ ruleattr = self.rules[rule]
+ cmd = ruleattr.get('command')
+ if cmd is None: # A phony rule, for example.
+ return
+
+ # Implement just enough of Ninja variable expansion etc. to
+ # make the bootstrap build work.
+ local_vars = {
+ 'in': self._expand_paths(inputs),
+ 'out': self._expand_paths(outputs)
+ }
+ for key, val in kwargs.get('variables', []):
+ local_vars[key] = ' '.join(ninja_syntax.as_list(val))
+
+ self._run_command(self._expand(cmd, local_vars))
+
+ return self.writer.build(outputs, rule, inputs, **kwargs)
+
+ def default(self, paths):
+ return self.writer.default(paths)
+
+ def _expand_paths(self, paths):
+ """Expand $vars in an array of paths, e.g. from a 'build' block."""
+ paths = ninja_syntax.as_list(paths)
+ return ' '.join(map(self._expand, paths))
+
+ def _expand(self, str, local_vars={}):
+ """Expand $vars in a string."""
+ return ninja_syntax.expand(str, self.vars, local_vars)
+
+ def _run_command(self, cmdline):
+ """Run a subcommand, quietly. Prints the full command on error."""
+ try:
+ if self.verbose:
+ print(cmdline)
+ subprocess.check_call(cmdline, shell=True)
+ except subprocess.CalledProcessError:
+ print('when running: ', cmdline)
+ raise
+
+
parser = OptionParser()
-platforms = ['linux', 'freebsd', 'solaris', 'mingw', 'windows']
profilers = ['gmon', 'pprof']
+parser.add_option('--bootstrap', action='store_true',
+ help='bootstrap a ninja binary from nothing')
+parser.add_option('--verbose', action='store_true',
+ help='enable verbose build')
parser.add_option('--platform',
- help='target platform (' + '/'.join(platforms) + ')',
- choices=platforms)
+ help='target platform (' +
+ '/'.join(Platform.known_platforms()) + ')',
+ choices=Platform.known_platforms())
parser.add_option('--host',
- help='host platform (' + '/'.join(platforms) + ')',
- choices=platforms)
+ help='host platform (' +
+ '/'.join(Platform.known_platforms()) + ')',
+ choices=Platform.known_platforms())
parser.add_option('--debug', action='store_true',
help='enable debugging extras',)
parser.add_option('--profile', metavar='TYPE',
choices=profilers,
help='enable profiling (' + '/'.join(profilers) + ')',)
-parser.add_option('--with-gtest', metavar='PATH',
- help='use gtest unpacked in directory PATH')
+parser.add_option('--with-gtest', metavar='PATH', help='ignored')
parser.add_option('--with-python', metavar='EXE',
help='use EXE as the Python interpreter',
default=os.path.basename(sys.executable))
-parser.add_option('--with-ninja', metavar='NAME',
- help="name for ninja binary for -t msvc (MSVC only)",
- default="ninja")
+parser.add_option('--force-pselect', action='store_true',
+ help='ppoll() is used by default where available, '
+ 'but some platforms may need to use pselect instead',)
(options, args) = parser.parse_args()
if args:
print('ERROR: extra unparsed command-line arguments:', args)
sys.exit(1)
-platform = options.platform
-if platform is None:
- platform = sys.platform
- if platform.startswith('linux'):
- platform = 'linux'
- elif platform.startswith('freebsd'):
- platform = 'freebsd'
- elif platform.startswith('solaris'):
- platform = 'solaris'
- elif platform.startswith('mingw'):
- platform = 'mingw'
- elif platform.startswith('win'):
- platform = 'windows'
-host = options.host or platform
+platform = Platform(options.platform)
+if options.host:
+ host = Platform(options.host)
+else:
+ host = platform
BUILD_FILENAME = 'build.ninja'
-buildfile = open(BUILD_FILENAME, 'w')
-n = ninja_syntax.Writer(buildfile)
+ninja_writer = ninja_syntax.Writer(open(BUILD_FILENAME, 'w'))
+n = ninja_writer
+
+if options.bootstrap:
+ # Make the build directory.
+ try:
+ os.mkdir('build')
+ except OSError:
+ pass
+ # Wrap ninja_writer with the Bootstrapper, which also executes the
+ # commands.
+ print('bootstrapping ninja...')
+ n = Bootstrap(n, verbose=options.verbose)
+
n.comment('This file is used to build ninja itself.')
n.comment('It is generated by ' + os.path.basename(__file__) + '.')
n.newline()
+n.variable('ninja_required_version', '1.3')
+n.newline()
+
n.comment('The arguments passed to configure.py, for rerunning it.')
-n.variable('configure_args', ' '.join(sys.argv[1:]))
+configure_args = sys.argv[1:]
+if '--bootstrap' in configure_args:
+ configure_args.remove('--bootstrap')
+n.variable('configure_args', ' '.join(configure_args))
env_keys = set(['CXX', 'AR', 'CFLAGS', 'LDFLAGS'])
configure_env = dict((k, os.environ[k]) for k in os.environ if k in env_keys)
if configure_env:
- config_str = ' '.join([k + '=' + configure_env[k] for k in configure_env])
+ config_str = ' '.join([k + '=' + pipes.quote(configure_env[k])
+ for k in configure_env])
n.variable('configure_env', config_str + '$ ')
n.newline()
CXX = configure_env.get('CXX', 'g++')
objext = '.o'
-if platform == 'windows':
+if platform.is_msvc():
CXX = 'cl'
objext = '.obj'
def cxx(name, **kwargs):
return n.build(built(name + objext), 'cxx', src(name + '.cc'), **kwargs)
def binary(name):
- if platform in ('mingw', 'windows'):
+ if platform.is_windows():
exe = name + '.exe'
n.build(name, 'phony', exe)
return exe
n.variable('builddir', 'build')
n.variable('cxx', CXX)
-if platform == 'windows':
+if platform.is_msvc():
n.variable('ar', 'link')
else:
n.variable('ar', configure_env.get('AR', 'ar'))
-if platform == 'windows':
- cflags = ['/nologo', # Don't print startup banner.
+if platform.is_msvc():
+ cflags = ['/showIncludes',
+ '/nologo', # Don't print startup banner.
'/Zi', # Create pdb with debug info.
'/W4', # Highest warning level.
'/WX', # Warnings as errors.
'/wd4512', '/wd4800', '/wd4702', '/wd4819',
# Disable warnings about passing "this" during initialization.
'/wd4355',
+ # Disable warnings about ignored typedef in DbgHelp.h
+ '/wd4091',
'/GR-', # Disable RTTI.
# Disable size_t -> int truncation warning.
# We never have strings or arrays larger than 2**31.
'/wd4267',
'/DNOMINMAX', '/D_CRT_SECURE_NO_WARNINGS',
+ '/D_VARIADIC_MAX=10',
'/DNINJA_PYTHON="%s"' % options.with_python]
+ if options.bootstrap:
+ # In bootstrap mode, we have no ninja process to catch /showIncludes
+ # output.
+ cflags.remove('/showIncludes')
+ if platform.msvc_needs_fs():
+ cflags.append('/FS')
ldflags = ['/DEBUG', '/libpath:$builddir']
if not options.debug:
cflags += ['/Ox', '/DNDEBUG', '/GL']
cflags.remove('-fno-rtti') # Needed for above pedanticness.
else:
cflags += ['-O2', '-DNDEBUG']
- if 'clang' in os.path.basename(CXX):
- cflags += ['-fcolor-diagnostics']
- if platform == 'mingw':
+ try:
+ proc = subprocess.Popen(
+ [CXX, '-fdiagnostics-color', '-c', '-x', 'c++', '/dev/null'],
+ stdout=open(os.devnull, 'wb'), stderr=subprocess.STDOUT)
+ proc.wait()
+ if proc.returncode == 0:
+ cflags += ['-fdiagnostics-color']
+ except:
+ pass
+ if platform.is_mingw():
cflags += ['-D_WIN32_WINNT=0x0501']
ldflags = ['-L$builddir']
+ if platform.uses_usr_local():
+ cflags.append('-I/usr/local/include')
+ ldflags.append('-L/usr/local/lib')
+
libs = []
-if platform == 'mingw':
+if platform.is_mingw():
cflags.remove('-fvisibility=hidden');
ldflags.append('-static')
-elif platform == 'sunos5':
+elif platform.is_solaris():
cflags.remove('-fvisibility=hidden')
-elif platform == 'windows':
+elif platform.is_msvc():
pass
else:
if options.profile == 'gmon':
cflags.append('-pg')
ldflags.append('-pg')
elif options.profile == 'pprof':
- libs.append('-lprofiler')
+ cflags.append('-fno-omit-frame-pointer')
+ libs.extend(['-Wl,--no-as-needed', '-lprofiler'])
+
+if platform.supports_ppoll() and not options.force_pselect:
+ cflags.append('-DUSE_PPOLL')
+if platform.supports_ninja_browse():
+ cflags.append('-DNINJA_HAVE_BROWSE')
def shell_escape(str):
"""Escape str such that it's interpreted as a single argument by
the shell."""
# This isn't complete, but it's just enough to make NINJA_PYTHON work.
- if platform in ('windows', 'mingw'):
+ if platform.is_windows():
return str
if '"' in str:
return "'%s'" % str.replace("'", "\\'")
n.variable('ldflags', ' '.join(shell_escape(flag) for flag in ldflags))
n.newline()
-if platform == 'windows':
- compiler = '$cxx'
- if options.with_ninja:
- compiler = ('%s -t msvc -o $out -- $cxx /showIncludes' %
- options.with_ninja)
+if platform.is_msvc():
n.rule('cxx',
- command='%s $cflags -c $in /Fo$out' % compiler,
- depfile='$out.d',
- description='CXX $out')
+ command='$cxx $cflags -c $in /Fo$out',
+ description='CXX $out',
+ deps='msvc' # /showIncludes is included in $cflags.
+ )
else:
n.rule('cxx',
command='$cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out',
depfile='$out.d',
+ deps='gcc',
description='CXX $out')
n.newline()
-if host == 'windows':
+if host.is_msvc():
n.rule('ar',
command='lib /nologo /ltcg /out:$out $in',
description='LIB $out')
-elif host == 'mingw':
+elif host.is_mingw():
n.rule('ar',
command='cmd /c $ar cqs $out.tmp $in && move /Y $out.tmp $out',
description='AR $out')
description='AR $out')
n.newline()
-if platform == 'windows':
+if platform.is_msvc():
n.rule('link',
command='$cxx $in $libs /nologo /link $ldflags /out:$out',
description='LINK $out')
objs = []
-if platform not in ('solaris', 'mingw', 'windows'):
+if platform.supports_ninja_browse():
n.comment('browse_py.h is used to inline browse.py.')
n.rule('inline',
command='src/inline.sh $varname < $in > $out',
n.comment('the depfile parser and ninja lexers are generated using re2c.')
def has_re2c():
- import subprocess
try:
proc = subprocess.Popen(['re2c', '-V'], stdout=subprocess.PIPE)
return int(proc.communicate()[0], 10) >= 1103
for name in ['build',
'build_log',
'clean',
+ 'debug_flags',
'depfile_parser',
+ 'deps_log',
'disk_interface',
'edit_distance',
'eval_env',
- 'explain',
'graph',
'graphviz',
'lexer',
+ 'line_printer',
'manifest_parser',
'metrics',
'state',
- 'util']:
+ 'util',
+ 'version']:
objs += cxx(name)
-if platform in ('mingw', 'windows'):
+if platform.is_windows():
for name in ['subprocess-win32',
'includes_normalize-win32',
'msvc_helper-win32',
'msvc_helper_main-win32']:
objs += cxx(name)
- if platform == 'windows':
+ if platform.is_msvc():
objs += cxx('minidump-win32')
objs += cc('getopt')
else:
objs += cxx('subprocess-posix')
-if platform == 'windows':
+if platform.is_msvc():
ninja_lib = n.build(built('ninja.lib'), 'ar', objs)
else:
ninja_lib = n.build(built('libninja.a'), 'ar', objs)
n.newline()
-if platform == 'windows':
+if platform.is_msvc():
libs.append('ninja.lib')
else:
libs.append('-lninja')
n.newline()
all_targets += ninja
+if options.bootstrap:
+ # We've built the ninja binary. Don't run any more commands
+ # through the bootstrap executor, but continue writing the
+ # build.ninja file.
+ n = ninja_writer
+
n.comment('Tests all build into ninja_test executable.')
-variables = []
-test_cflags = cflags[:]
-test_ldflags = None
-test_libs = libs
objs = []
-if options.with_gtest:
- path = options.with_gtest
-
- gtest_all_incs = '-I%s -I%s' % (path, os.path.join(path, 'include'))
- if platform == 'windows':
- gtest_cflags = '/nologo /EHsc /Zi ' + gtest_all_incs
- else:
- gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs
- objs += n.build(built('gtest-all' + objext), 'cxx',
- os.path.join(path, 'src', 'gtest-all.cc'),
- variables=[('cflags', gtest_cflags)])
- objs += n.build(built('gtest_main' + objext), 'cxx',
- os.path.join(path, 'src', 'gtest_main.cc'),
- variables=[('cflags', gtest_cflags)])
-
- test_cflags.append('-I%s' % os.path.join(path, 'include'))
-elif platform == 'windows':
- test_libs.extend(['gtest_main.lib', 'gtest.lib'])
-else:
- test_cflags.append('-DGTEST_HAS_RTTI=0')
- test_libs.extend(['-lgtest_main', '-lgtest'])
-
-if test_cflags == cflags:
- test_cflags = None
-n.variable('test_cflags', test_cflags)
for name in ['build_log_test',
'build_test',
'clean_test',
'depfile_parser_test',
+ 'deps_log_test',
'disk_interface_test',
'edit_distance_test',
'graph_test',
'lexer_test',
'manifest_parser_test',
+ 'ninja_test',
'state_test',
'subprocess_test',
'test',
'util_test']:
- objs += cxx(name, variables=[('cflags', '$test_cflags')])
-if platform in ('windows', 'mingw'):
+ objs += cxx(name)
+if platform.is_windows():
for name in ['includes_normalize_test', 'msvc_helper_test']:
- objs += cxx(name, variables=[('cflags', test_cflags)])
+ objs += cxx(name)
-if platform != 'mingw' and platform != 'windows':
- test_libs.append('-lpthread')
ninja_test = n.build(binary('ninja_test'), 'link', objs, implicit=ninja_lib,
- variables=[('ldflags', test_ldflags),
- ('libs', test_libs)])
+ variables=[('libs', libs)])
n.newline()
all_targets += ninja_test
n.comment('Ancillary executables.')
-objs = cxx('parser_perftest')
-all_targets += n.build(binary('parser_perftest'), 'link', objs,
- implicit=ninja_lib, variables=[('libs', libs)])
objs = cxx('build_log_perftest')
all_targets += n.build(binary('build_log_perftest'), 'link', objs,
implicit=ninja_lib, variables=[('libs', libs)])
objs = cxx('canon_perftest')
all_targets += n.build(binary('canon_perftest'), 'link', objs,
implicit=ninja_lib, variables=[('libs', libs)])
+objs = cxx('depfile_parser_perftest')
+all_targets += n.build(binary('depfile_parser_perftest'), 'link', objs,
+ implicit=ninja_lib, variables=[('libs', libs)])
objs = cxx('hash_collision_bench')
all_targets += n.build(binary('hash_collision_bench'), 'link', objs,
implicit=ninja_lib, variables=[('libs', libs)])
+objs = cxx('manifest_parser_perftest')
+all_targets += n.build(binary('manifest_parser_perftest'), 'link', objs,
+ implicit=ninja_lib, variables=[('libs', libs)])
n.newline()
n.comment('Generate a graph using the "graph" tool.')
n.comment('Generate the manual using asciidoc.')
n.rule('asciidoc',
- command='asciidoc -a toc -a max-width=45em -o $out $in',
- description='ASCIIDOC $in')
-manual = n.build(doc('manual.html'), 'asciidoc', doc('manual.asciidoc'))
+ command='asciidoc -b docbook -d book -o $out $in',
+ description='ASCIIDOC $out')
+n.rule('xsltproc',
+ command='xsltproc --nonet doc/docbook.xsl $in > $out',
+ description='XSLTPROC $out')
+xml = n.build(built('manual.xml'), 'asciidoc', doc('manual.asciidoc'))
+manual = n.build(doc('manual.html'), 'xsltproc', xml,
+ implicit=doc('style.css'))
n.build('manual', 'phony',
order_only=manual)
n.newline()
implicit=mainpage)
n.newline()
-if host != 'mingw':
+if not host.is_mingw():
n.comment('Regenerate build files if build script changes.')
n.rule('configure',
command='${configure_env}%s configure.py $configure_args' %
n.default(ninja)
n.newline()
-if host == 'linux':
+if host.is_linux():
n.comment('Packaging')
n.rule('rpmbuild',
command="misc/packaging/rpmbuild.sh",
n.build('all', 'phony', all_targets)
+n.close()
print('wrote %s.' % BUILD_FILENAME)
+
+verbose = ''
+if options.verbose:
+ verbose = ' -v'
+
+if options.bootstrap:
+ print('bootstrap complete. rebuilding...')
+ if platform.is_windows():
+ bootstrap_exe = 'ninja.bootstrap.exe'
+ if os.path.exists(bootstrap_exe):
+ os.unlink(bootstrap_exe)
+ os.rename('ninja.exe', bootstrap_exe)
+ subprocess.check_call('ninja.bootstrap.exe%s' % verbose, shell=True)
+ else:
+ subprocess.check_call('./ninja%s' % verbose, shell=True)