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