1#!/usr/bin/env python 2# 3# Copyright 2001 Google Inc. All Rights Reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Script that generates the build.ninja for ninja itself. 18 19Projects that use ninja themselves should either write a similar script 20or use a meta-build system that supports Ninja output.""" 21 22from __future__ import print_function 23 24from optparse import OptionParser 25import os 26import pipes 27import string 28import subprocess 29import sys 30 31sourcedir = os.path.dirname(os.path.realpath(__file__)) 32sys.path.insert(0, os.path.join(sourcedir, 'misc')) 33import ninja_syntax 34 35 36class Platform(object): 37 """Represents a host/target platform and its specific build attributes.""" 38 def __init__(self, platform): 39 self._platform = platform 40 if self._platform is not None: 41 return 42 self._platform = sys.platform 43 if self._platform.startswith('linux'): 44 self._platform = 'linux' 45 elif self._platform.startswith('freebsd'): 46 self._platform = 'freebsd' 47 elif self._platform.startswith('gnukfreebsd'): 48 self._platform = 'freebsd' 49 elif self._platform.startswith('openbsd'): 50 self._platform = 'openbsd' 51 elif self._platform.startswith('solaris') or self._platform == 'sunos5': 52 self._platform = 'solaris' 53 elif self._platform.startswith('mingw'): 54 self._platform = 'mingw' 55 elif self._platform.startswith('win'): 56 self._platform = 'msvc' 57 elif self._platform.startswith('bitrig'): 58 self._platform = 'bitrig' 59 elif self._platform.startswith('netbsd'): 60 self._platform = 'netbsd' 61 elif self._platform.startswith('aix'): 62 self._platform = 'aix' 63 elif self._platform.startswith('os400'): 64 self._platform = 'os400' 65 elif self._platform.startswith('dragonfly'): 66 self._platform = 'dragonfly' 67 68 @staticmethod 69 def known_platforms(): 70 return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5', 71 'mingw', 'msvc', 'gnukfreebsd', 'bitrig', 'netbsd', 'aix', 72 'dragonfly'] 73 74 def platform(self): 75 return self._platform 76 77 def is_linux(self): 78 return self._platform == 'linux' 79 80 def is_mingw(self): 81 return self._platform == 'mingw' 82 83 def is_msvc(self): 84 return self._platform == 'msvc' 85 86 def msvc_needs_fs(self): 87 popen = subprocess.Popen(['cl', '/nologo', '/help'], 88 stdout=subprocess.PIPE, 89 stderr=subprocess.PIPE) 90 out, err = popen.communicate() 91 return b'/FS' in out 92 93 def is_windows(self): 94 return self.is_mingw() or self.is_msvc() 95 96 def is_solaris(self): 97 return self._platform == 'solaris' 98 99 def is_aix(self): 100 return self._platform == 'aix' 101 102 def is_os400_pase(self): 103 return self._platform == 'os400' or os.uname().sysname.startswith('OS400') 104 105 def uses_usr_local(self): 106 return self._platform in ('freebsd', 'openbsd', 'bitrig', 'dragonfly', 'netbsd') 107 108 def supports_ppoll(self): 109 return self._platform in ('freebsd', 'linux', 'openbsd', 'bitrig', 110 'dragonfly') 111 112 def supports_ninja_browse(self): 113 return (not self.is_windows() 114 and not self.is_solaris() 115 and not self.is_aix()) 116 117 def can_rebuild_in_place(self): 118 return not (self.is_windows() or self.is_aix()) 119 120class Bootstrap: 121 """API shim for ninja_syntax.Writer that instead runs the commands. 122 123 Used to bootstrap Ninja from scratch. In --bootstrap mode this 124 class is used to execute all the commands to build an executable. 125 It also proxies all calls to an underlying ninja_syntax.Writer, to 126 behave like non-bootstrap mode. 127 """ 128 def __init__(self, writer, verbose=False): 129 self.writer = writer 130 self.verbose = verbose 131 # Map of variable name => expanded variable value. 132 self.vars = {} 133 # Map of rule name => dict of rule attributes. 134 self.rules = { 135 'phony': {} 136 } 137 138 def comment(self, text): 139 return self.writer.comment(text) 140 141 def newline(self): 142 return self.writer.newline() 143 144 def variable(self, key, val): 145 # In bootstrap mode, we have no ninja process to catch /showIncludes 146 # output. 147 self.vars[key] = self._expand(val).replace('/showIncludes', '') 148 return self.writer.variable(key, val) 149 150 def rule(self, name, **kwargs): 151 self.rules[name] = kwargs 152 return self.writer.rule(name, **kwargs) 153 154 def build(self, outputs, rule, inputs=None, **kwargs): 155 ruleattr = self.rules[rule] 156 cmd = ruleattr.get('command') 157 if cmd is None: # A phony rule, for example. 158 return 159 160 # Implement just enough of Ninja variable expansion etc. to 161 # make the bootstrap build work. 162 local_vars = { 163 'in': self._expand_paths(inputs), 164 'out': self._expand_paths(outputs) 165 } 166 for key, val in kwargs.get('variables', []): 167 local_vars[key] = ' '.join(ninja_syntax.as_list(val)) 168 169 self._run_command(self._expand(cmd, local_vars)) 170 171 return self.writer.build(outputs, rule, inputs, **kwargs) 172 173 def default(self, paths): 174 return self.writer.default(paths) 175 176 def _expand_paths(self, paths): 177 """Expand $vars in an array of paths, e.g. from a 'build' block.""" 178 paths = ninja_syntax.as_list(paths) 179 return ' '.join(map(self._shell_escape, (map(self._expand, paths)))) 180 181 def _expand(self, str, local_vars={}): 182 """Expand $vars in a string.""" 183 return ninja_syntax.expand(str, self.vars, local_vars) 184 185 def _shell_escape(self, path): 186 """Quote paths containing spaces.""" 187 return '"%s"' % path if ' ' in path else path 188 189 def _run_command(self, cmdline): 190 """Run a subcommand, quietly. Prints the full command on error.""" 191 try: 192 if self.verbose: 193 print(cmdline) 194 subprocess.check_call(cmdline, shell=True) 195 except subprocess.CalledProcessError: 196 print('when running: ', cmdline) 197 raise 198 199 200parser = OptionParser() 201profilers = ['gmon', 'pprof'] 202parser.add_option('--bootstrap', action='store_true', 203 help='bootstrap a ninja binary from nothing') 204parser.add_option('--verbose', action='store_true', 205 help='enable verbose build') 206parser.add_option('--platform', 207 help='target platform (' + 208 '/'.join(Platform.known_platforms()) + ')', 209 choices=Platform.known_platforms()) 210parser.add_option('--host', 211 help='host platform (' + 212 '/'.join(Platform.known_platforms()) + ')', 213 choices=Platform.known_platforms()) 214parser.add_option('--debug', action='store_true', 215 help='enable debugging extras',) 216parser.add_option('--profile', metavar='TYPE', 217 choices=profilers, 218 help='enable profiling (' + '/'.join(profilers) + ')',) 219parser.add_option('--with-gtest', metavar='PATH', help='ignored') 220parser.add_option('--with-python', metavar='EXE', 221 help='use EXE as the Python interpreter', 222 default=os.path.basename(sys.executable)) 223parser.add_option('--force-pselect', action='store_true', 224 help='ppoll() is used by default where available, ' 225 'but some platforms may need to use pselect instead',) 226(options, args) = parser.parse_args() 227if args: 228 print('ERROR: extra unparsed command-line arguments:', args) 229 sys.exit(1) 230 231platform = Platform(options.platform) 232if options.host: 233 host = Platform(options.host) 234else: 235 host = platform 236 237BUILD_FILENAME = 'build.ninja' 238ninja_writer = ninja_syntax.Writer(open(BUILD_FILENAME, 'w')) 239n = ninja_writer 240 241if options.bootstrap: 242 # Make the build directory. 243 try: 244 os.mkdir('build') 245 except OSError: 246 pass 247 # Wrap ninja_writer with the Bootstrapper, which also executes the 248 # commands. 249 print('bootstrapping ninja...') 250 n = Bootstrap(n, verbose=options.verbose) 251 252n.comment('This file is used to build ninja itself.') 253n.comment('It is generated by ' + os.path.basename(__file__) + '.') 254n.newline() 255 256n.variable('ninja_required_version', '1.3') 257n.newline() 258 259n.comment('The arguments passed to configure.py, for rerunning it.') 260configure_args = sys.argv[1:] 261if '--bootstrap' in configure_args: 262 configure_args.remove('--bootstrap') 263n.variable('configure_args', ' '.join(configure_args)) 264env_keys = set(['CXX', 'AR', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS']) 265configure_env = dict((k, os.environ[k]) for k in os.environ if k in env_keys) 266if configure_env: 267 config_str = ' '.join([k + '=' + pipes.quote(configure_env[k]) 268 for k in configure_env]) 269 n.variable('configure_env', config_str + '$ ') 270n.newline() 271 272CXX = configure_env.get('CXX', 'c++') 273objext = '.o' 274if platform.is_msvc(): 275 CXX = 'cl' 276 objext = '.obj' 277 278def src(filename): 279 return os.path.join('$root', 'src', filename) 280def built(filename): 281 return os.path.join('$builddir', filename) 282def doc(filename): 283 return os.path.join('$root', 'doc', filename) 284def cc(name, **kwargs): 285 return n.build(built(name + objext), 'cxx', src(name + '.c'), **kwargs) 286def cxx(name, **kwargs): 287 return n.build(built(name + objext), 'cxx', src(name + '.cc'), **kwargs) 288def binary(name): 289 if platform.is_windows(): 290 exe = name + '.exe' 291 n.build(name, 'phony', exe) 292 return exe 293 return name 294 295root = sourcedir 296if root == os.getcwd(): 297 # In the common case where we're building directly in the source 298 # tree, simplify all the paths to just be cwd-relative. 299 root = '.' 300n.variable('root', root) 301n.variable('builddir', 'build') 302n.variable('cxx', CXX) 303if platform.is_msvc(): 304 n.variable('ar', 'link') 305else: 306 n.variable('ar', configure_env.get('AR', 'ar')) 307 308if platform.is_msvc(): 309 cflags = ['/showIncludes', 310 '/nologo', # Don't print startup banner. 311 '/Zi', # Create pdb with debug info. 312 '/W4', # Highest warning level. 313 '/WX', # Warnings as errors. 314 '/wd4530', '/wd4100', '/wd4706', '/wd4244', 315 '/wd4512', '/wd4800', '/wd4702', '/wd4819', 316 # Disable warnings about constant conditional expressions. 317 '/wd4127', 318 # Disable warnings about passing "this" during initialization. 319 '/wd4355', 320 # Disable warnings about ignored typedef in DbgHelp.h 321 '/wd4091', 322 '/GR-', # Disable RTTI. 323 # Disable size_t -> int truncation warning. 324 # We never have strings or arrays larger than 2**31. 325 '/wd4267', 326 '/DNOMINMAX', '/D_CRT_SECURE_NO_WARNINGS', 327 '/D_HAS_EXCEPTIONS=0', 328 '/DNINJA_PYTHON="%s"' % options.with_python] 329 if platform.msvc_needs_fs(): 330 cflags.append('/FS') 331 ldflags = ['/DEBUG', '/libpath:$builddir'] 332 if not options.debug: 333 cflags += ['/Ox', '/DNDEBUG', '/GL'] 334 ldflags += ['/LTCG', '/OPT:REF', '/OPT:ICF'] 335else: 336 cflags = ['-g', '-Wall', '-Wextra', 337 '-Wno-deprecated', 338 '-Wno-missing-field-initializers', 339 '-Wno-unused-parameter', 340 '-fno-rtti', 341 '-fno-exceptions', 342 '-fvisibility=hidden', '-pipe', 343 '-DNINJA_PYTHON="%s"' % options.with_python] 344 if options.debug: 345 cflags += ['-D_GLIBCXX_DEBUG', '-D_GLIBCXX_DEBUG_PEDANTIC'] 346 cflags.remove('-fno-rtti') # Needed for above pedanticness. 347 else: 348 cflags += ['-O2', '-DNDEBUG'] 349 try: 350 proc = subprocess.Popen( 351 [CXX, '-fdiagnostics-color', '-c', '-x', 'c++', '/dev/null', 352 '-o', '/dev/null'], 353 stdout=open(os.devnull, 'wb'), stderr=subprocess.STDOUT) 354 if proc.wait() == 0: 355 cflags += ['-fdiagnostics-color'] 356 except: 357 pass 358 if platform.is_mingw(): 359 cflags += ['-D_WIN32_WINNT=0x0601', '-D__USE_MINGW_ANSI_STDIO=1'] 360 ldflags = ['-L$builddir'] 361 if platform.uses_usr_local(): 362 cflags.append('-I/usr/local/include') 363 ldflags.append('-L/usr/local/lib') 364 if platform.is_aix(): 365 # printf formats for int64_t, uint64_t; large file support 366 cflags.append('-D__STDC_FORMAT_MACROS') 367 cflags.append('-D_LARGE_FILES') 368 369 370libs = [] 371 372if platform.is_mingw(): 373 cflags.remove('-fvisibility=hidden'); 374 ldflags.append('-static') 375elif platform.is_solaris(): 376 cflags.remove('-fvisibility=hidden') 377elif platform.is_aix(): 378 cflags.remove('-fvisibility=hidden') 379elif platform.is_msvc(): 380 pass 381else: 382 if options.profile == 'gmon': 383 cflags.append('-pg') 384 ldflags.append('-pg') 385 elif options.profile == 'pprof': 386 cflags.append('-fno-omit-frame-pointer') 387 libs.extend(['-Wl,--no-as-needed', '-lprofiler']) 388 389if platform.supports_ppoll() and not options.force_pselect: 390 cflags.append('-DUSE_PPOLL') 391if platform.supports_ninja_browse(): 392 cflags.append('-DNINJA_HAVE_BROWSE') 393 394# Search for generated headers relative to build dir. 395cflags.append('-I.') 396 397def shell_escape(str): 398 """Escape str such that it's interpreted as a single argument by 399 the shell.""" 400 401 # This isn't complete, but it's just enough to make NINJA_PYTHON work. 402 if platform.is_windows(): 403 return str 404 if '"' in str: 405 return "'%s'" % str.replace("'", "\\'") 406 return str 407 408if 'CFLAGS' in configure_env: 409 cflags.append(configure_env['CFLAGS']) 410 ldflags.append(configure_env['CFLAGS']) 411if 'CXXFLAGS' in configure_env: 412 cflags.append(configure_env['CXXFLAGS']) 413 ldflags.append(configure_env['CXXFLAGS']) 414n.variable('cflags', ' '.join(shell_escape(flag) for flag in cflags)) 415if 'LDFLAGS' in configure_env: 416 ldflags.append(configure_env['LDFLAGS']) 417n.variable('ldflags', ' '.join(shell_escape(flag) for flag in ldflags)) 418n.newline() 419 420if platform.is_msvc(): 421 n.rule('cxx', 422 command='$cxx $cflags -c $in /Fo$out /Fd' + built('$pdb'), 423 description='CXX $out', 424 deps='msvc' # /showIncludes is included in $cflags. 425 ) 426else: 427 n.rule('cxx', 428 command='$cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out', 429 depfile='$out.d', 430 deps='gcc', 431 description='CXX $out') 432n.newline() 433 434if host.is_msvc(): 435 n.rule('ar', 436 command='lib /nologo /ltcg /out:$out $in', 437 description='LIB $out') 438elif host.is_mingw(): 439 n.rule('ar', 440 command='$ar crs $out $in', 441 description='AR $out') 442else: 443 n.rule('ar', 444 command='rm -f $out && $ar crs $out $in', 445 description='AR $out') 446n.newline() 447 448if platform.is_msvc(): 449 n.rule('link', 450 command='$cxx $in $libs /nologo /link $ldflags /out:$out', 451 description='LINK $out') 452else: 453 n.rule('link', 454 command='$cxx $ldflags -o $out $in $libs', 455 description='LINK $out') 456n.newline() 457 458objs = [] 459 460if platform.supports_ninja_browse(): 461 n.comment('browse_py.h is used to inline browse.py.') 462 n.rule('inline', 463 command='"%s"' % src('inline.sh') + ' $varname < $in > $out', 464 description='INLINE $out') 465 n.build(built('browse_py.h'), 'inline', src('browse.py'), 466 implicit=src('inline.sh'), 467 variables=[('varname', 'kBrowsePy')]) 468 n.newline() 469 470 objs += cxx('browse', order_only=built('browse_py.h')) 471 n.newline() 472 473n.comment('the depfile parser and ninja lexers are generated using re2c.') 474def has_re2c(): 475 try: 476 proc = subprocess.Popen(['re2c', '-V'], stdout=subprocess.PIPE) 477 return int(proc.communicate()[0], 10) >= 1103 478 except OSError: 479 return False 480if has_re2c(): 481 n.rule('re2c', 482 command='re2c -b -i --no-generation-date --no-version -o $out $in', 483 description='RE2C $out') 484 # Generate the .cc files in the source directory so we can check them in. 485 n.build(src('depfile_parser.cc'), 're2c', src('depfile_parser.in.cc')) 486 n.build(src('lexer.cc'), 're2c', src('lexer.in.cc')) 487else: 488 print("warning: A compatible version of re2c (>= 0.11.3) was not found; " 489 "changes to src/*.in.cc will not affect your build.") 490n.newline() 491 492n.comment('Core source files all build into ninja library.') 493cxxvariables = [] 494if platform.is_msvc(): 495 cxxvariables = [('pdb', 'ninja.pdb')] 496for name in ['build', 497 'build_log', 498 'clean', 499 'clparser', 500 'debug_flags', 501 'depfile_parser', 502 'deps_log', 503 'disk_interface', 504 'dyndep', 505 'dyndep_parser', 506 'edit_distance', 507 'eval_env', 508 'graph', 509 'graphviz', 510 'json', 511 'lexer', 512 'line_printer', 513 'manifest_parser', 514 'metrics', 515 'missing_deps', 516 'parser', 517 'state', 518 'status', 519 'string_piece_util', 520 'util', 521 'version']: 522 objs += cxx(name, variables=cxxvariables) 523if platform.is_windows(): 524 for name in ['subprocess-win32', 525 'includes_normalize-win32', 526 'msvc_helper-win32', 527 'msvc_helper_main-win32']: 528 objs += cxx(name, variables=cxxvariables) 529 if platform.is_msvc(): 530 objs += cxx('minidump-win32', variables=cxxvariables) 531 objs += cc('getopt') 532else: 533 objs += cxx('subprocess-posix') 534if platform.is_aix(): 535 objs += cc('getopt') 536if platform.is_msvc(): 537 ninja_lib = n.build(built('ninja.lib'), 'ar', objs) 538else: 539 ninja_lib = n.build(built('libninja.a'), 'ar', objs) 540n.newline() 541 542if platform.is_msvc(): 543 libs.append('ninja.lib') 544else: 545 libs.append('-lninja') 546 547if platform.is_aix() and not platform.is_os400_pase(): 548 libs.append('-lperfstat') 549 550all_targets = [] 551 552n.comment('Main executable is library plus main() function.') 553objs = cxx('ninja', variables=cxxvariables) 554ninja = n.build(binary('ninja'), 'link', objs, implicit=ninja_lib, 555 variables=[('libs', libs)]) 556n.newline() 557all_targets += ninja 558 559if options.bootstrap: 560 # We've built the ninja binary. Don't run any more commands 561 # through the bootstrap executor, but continue writing the 562 # build.ninja file. 563 n = ninja_writer 564 565n.comment('Tests all build into ninja_test executable.') 566 567objs = [] 568if platform.is_msvc(): 569 cxxvariables = [('pdb', 'ninja_test.pdb')] 570 571for name in ['build_log_test', 572 'build_test', 573 'clean_test', 574 'clparser_test', 575 'depfile_parser_test', 576 'deps_log_test', 577 'dyndep_parser_test', 578 'disk_interface_test', 579 'edit_distance_test', 580 'graph_test', 581 'json_test', 582 'lexer_test', 583 'manifest_parser_test', 584 'missing_deps_test', 585 'ninja_test', 586 'state_test', 587 'status_test', 588 'string_piece_util_test', 589 'subprocess_test', 590 'test', 591 'util_test']: 592 objs += cxx(name, variables=cxxvariables) 593if platform.is_windows(): 594 for name in ['includes_normalize_test', 'msvc_helper_test']: 595 objs += cxx(name, variables=cxxvariables) 596 597ninja_test = n.build(binary('ninja_test'), 'link', objs, implicit=ninja_lib, 598 variables=[('libs', libs)]) 599n.newline() 600all_targets += ninja_test 601 602 603n.comment('Ancillary executables.') 604 605if platform.is_aix() and '-maix64' not in ldflags: 606 # Both hash_collision_bench and manifest_parser_perftest require more 607 # memory than will fit in the standard 32-bit AIX shared stack/heap (256M) 608 libs.append('-Wl,-bmaxdata:0x80000000') 609 610for name in ['build_log_perftest', 611 'canon_perftest', 612 'depfile_parser_perftest', 613 'hash_collision_bench', 614 'manifest_parser_perftest', 615 'clparser_perftest']: 616 if platform.is_msvc(): 617 cxxvariables = [('pdb', name + '.pdb')] 618 objs = cxx(name, variables=cxxvariables) 619 all_targets += n.build(binary(name), 'link', objs, 620 implicit=ninja_lib, variables=[('libs', libs)]) 621 622n.newline() 623 624n.comment('Generate a graph using the "graph" tool.') 625n.rule('gendot', 626 command='./ninja -t graph all > $out') 627n.rule('gengraph', 628 command='dot -Tpng $in > $out') 629dot = n.build(built('graph.dot'), 'gendot', ['ninja', 'build.ninja']) 630n.build('graph.png', 'gengraph', dot) 631n.newline() 632 633n.comment('Generate the manual using asciidoc.') 634n.rule('asciidoc', 635 command='asciidoc -b docbook -d book -o $out $in', 636 description='ASCIIDOC $out') 637n.rule('xsltproc', 638 command='xsltproc --nonet doc/docbook.xsl $in > $out', 639 description='XSLTPROC $out') 640docbookxml = n.build(built('manual.xml'), 'asciidoc', doc('manual.asciidoc')) 641manual = n.build(doc('manual.html'), 'xsltproc', docbookxml, 642 implicit=[doc('style.css'), doc('docbook.xsl')]) 643n.build('manual', 'phony', 644 order_only=manual) 645n.newline() 646 647n.rule('dblatex', 648 command='dblatex -q -o $out -p doc/dblatex.xsl $in', 649 description='DBLATEX $out') 650n.build(doc('manual.pdf'), 'dblatex', docbookxml, 651 implicit=[doc('dblatex.xsl')]) 652 653n.comment('Generate Doxygen.') 654n.rule('doxygen', 655 command='doxygen $in', 656 description='DOXYGEN $in') 657n.variable('doxygen_mainpage_generator', 658 src('gen_doxygen_mainpage.sh')) 659n.rule('doxygen_mainpage', 660 command='$doxygen_mainpage_generator $in > $out', 661 description='DOXYGEN_MAINPAGE $out') 662mainpage = n.build(built('doxygen_mainpage'), 'doxygen_mainpage', 663 ['README.md', 'COPYING'], 664 implicit=['$doxygen_mainpage_generator']) 665n.build('doxygen', 'doxygen', doc('doxygen.config'), 666 implicit=mainpage) 667n.newline() 668 669if not host.is_mingw(): 670 n.comment('Regenerate build files if build script changes.') 671 n.rule('configure', 672 command='${configure_env}%s $root/configure.py $configure_args' % 673 options.with_python, 674 generator=True) 675 n.build('build.ninja', 'configure', 676 implicit=['$root/configure.py', 677 os.path.normpath('$root/misc/ninja_syntax.py')]) 678 n.newline() 679 680n.default(ninja) 681n.newline() 682 683if host.is_linux(): 684 n.comment('Packaging') 685 n.rule('rpmbuild', 686 command="misc/packaging/rpmbuild.sh", 687 description='Building rpms..') 688 n.build('rpm', 'rpmbuild') 689 n.newline() 690 691n.build('all', 'phony', all_targets) 692 693n.close() 694print('wrote %s.' % BUILD_FILENAME) 695 696if options.bootstrap: 697 print('bootstrap complete. rebuilding...') 698 699 rebuild_args = [] 700 701 if platform.can_rebuild_in_place(): 702 rebuild_args.append('./ninja') 703 else: 704 if platform.is_windows(): 705 bootstrap_exe = 'ninja.bootstrap.exe' 706 final_exe = 'ninja.exe' 707 else: 708 bootstrap_exe = './ninja.bootstrap' 709 final_exe = './ninja' 710 711 if os.path.exists(bootstrap_exe): 712 os.unlink(bootstrap_exe) 713 os.rename(final_exe, bootstrap_exe) 714 715 rebuild_args.append(bootstrap_exe) 716 717 if options.verbose: 718 rebuild_args.append('-v') 719 720 subprocess.check_call(rebuild_args) 721