]>
git.proxmox.com Git - rustc.git/blob - src/bootstrap/bootstrap.py
1 # Copyright 2015-2016 The Rust Project Developers. See the COPYRIGHT
2 # file at the top-level directory of this distribution and at
3 # http://rust-lang.org/COPYRIGHT.
5 # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 # option. This file may not be copied, modified, or distributed
9 # except according to those terms.
11 from __future__
import absolute_import
, division
, print_function
27 def get(url
, path
, verbose
=False):
29 sha_url
= url
+ suffix
30 with tempfile
.NamedTemporaryFile(delete
=False) as temp_file
:
31 temp_path
= temp_file
.name
32 with tempfile
.NamedTemporaryFile(suffix
=suffix
, delete
=False) as sha_file
:
33 sha_path
= sha_file
.name
36 download(sha_path
, sha_url
, False, verbose
)
37 if os
.path
.exists(path
):
38 if verify(path
, sha_path
, False):
40 print("using already-download file", path
)
44 print("ignoring already-download file",
45 path
, "due to failed verification")
47 download(temp_path
, url
, True, verbose
)
48 if not verify(temp_path
, sha_path
, verbose
):
49 raise RuntimeError("failed verification")
51 print("moving {} to {}".format(temp_path
, path
))
52 shutil
.move(temp_path
, path
)
54 delete_if_present(sha_path
, verbose
)
55 delete_if_present(temp_path
, verbose
)
58 def delete_if_present(path
, verbose
):
59 """Remove the given file if present"""
60 if os
.path
.isfile(path
):
62 print("removing", path
)
66 def download(path
, url
, probably_big
, verbose
):
69 _download(path
, url
, probably_big
, verbose
, True)
72 print("\nspurious failure, trying again")
73 _download(path
, url
, probably_big
, verbose
, False)
76 def _download(path
, url
, probably_big
, verbose
, exception
):
77 if probably_big
or verbose
:
78 print("downloading {}".format(url
))
79 # see http://serverfault.com/questions/301128/how-to-download
80 if sys
.platform
== 'win32':
81 run(["PowerShell.exe", "/nologo", "-Command",
82 "(New-Object System.Net.WebClient)"
83 ".DownloadFile('{}', '{}')".format(url
, path
)],
87 if probably_big
or verbose
:
91 run(["curl", option
, "--retry", "3", "-Sf", "-o", path
, url
],
96 def verify(path
, sha_path
, verbose
):
97 """Check if the sha256 sum of the given path is valid"""
99 print("verifying", path
)
100 with
open(path
, "rb") as source
:
101 found
= hashlib
.sha256(source
.read()).hexdigest()
102 with
open(sha_path
, "r") as sha256sum
:
103 expected
= sha256sum
.readline().split()[0]
104 verified
= found
== expected
106 print("invalid checksum:\n"
108 " expected: {}".format(found
, expected
))
112 def unpack(tarball
, dst
, verbose
=False, match
=None):
113 """Unpack the given tarball file"""
114 print("extracting", tarball
)
115 fname
= os
.path
.basename(tarball
).replace(".tar.gz", "")
116 with contextlib
.closing(tarfile
.open(tarball
)) as tar
:
117 for member
in tar
.getnames():
118 if "/" not in member
:
120 name
= member
.replace(fname
+ "/", "", 1)
121 if match
is not None and not name
.startswith(match
):
123 name
= name
[len(match
) + 1:]
125 dst_path
= os
.path
.join(dst
, name
)
127 print(" extracting", member
)
128 tar
.extract(member
, dst
)
129 src_path
= os
.path
.join(dst
, member
)
130 if os
.path
.isdir(src_path
) and os
.path
.exists(dst_path
):
132 shutil
.move(src_path
, dst_path
)
133 shutil
.rmtree(os
.path
.join(dst
, fname
))
136 def run(args
, verbose
=False, exception
=False, **kwargs
):
137 """Run a child program in a new process"""
139 print("running: " + ' '.join(args
))
141 # Use Popen here instead of call() as it apparently allows powershell on
142 # Windows to not lock up waiting for input presumably.
143 ret
= subprocess
.Popen(args
, **kwargs
)
146 err
= "failed to run: " + ' '.join(args
)
147 if verbose
or exception
:
148 raise RuntimeError(err
)
152 def stage0_data(rust_root
):
153 """Build a dictionary from stage0.txt"""
154 nightlies
= os
.path
.join(rust_root
, "src/stage0.txt")
155 with
open(nightlies
, 'r') as nightlies
:
156 lines
= [line
.rstrip() for line
in nightlies
157 if not line
.startswith("#")]
158 return dict([line
.split(": ", 1) for line
in lines
if line
])
161 def format_build_time(duration
):
162 """Return a nicer format for build time
164 >>> format_build_time('300')
167 return str(datetime
.timedelta(seconds
=int(duration
)))
170 def default_build_triple():
171 """Build triple as in LLVM"""
172 default_encoding
= sys
.getdefaultencoding()
174 ostype
= subprocess
.check_output(
175 ['uname', '-s']).strip().decode(default_encoding
)
176 cputype
= subprocess
.check_output(
177 ['uname', '-m']).strip().decode(default_encoding
)
178 except (subprocess
.CalledProcessError
, OSError):
179 if sys
.platform
== 'win32':
180 return 'x86_64-pc-windows-msvc'
181 err
= "uname not found"
184 # The goal here is to come up with the same triple as LLVM would,
185 # at least for the subset of platforms we're willing to target.
187 'Bitrig': 'unknown-bitrig',
188 'Darwin': 'apple-darwin',
189 'DragonFly': 'unknown-dragonfly',
190 'FreeBSD': 'unknown-freebsd',
191 'Haiku': 'unknown-haiku',
192 'NetBSD': 'unknown-netbsd',
193 'OpenBSD': 'unknown-openbsd'
196 # Consider the direct transformation first and then the special cases
197 if ostype
in ostype_mapper
:
198 ostype
= ostype_mapper
[ostype
]
199 elif ostype
== 'Linux':
200 os_from_sp
= subprocess
.check_output(
201 ['uname', '-o']).strip().decode(default_encoding
)
202 if os_from_sp
== 'Android':
203 ostype
= 'linux-android'
205 ostype
= 'unknown-linux-gnu'
206 elif ostype
== 'SunOS':
207 ostype
= 'sun-solaris'
208 # On Solaris, uname -m will return a machine classification instead
209 # of a cpu type, so uname -p is recommended instead. However, the
210 # output from that option is too generic for our purposes (it will
211 # always emit 'i386' on x86/amd64 systems). As such, isainfo -k
212 # must be used instead.
214 cputype
= subprocess
.check_output(
215 ['isainfo', '-k']).strip().decode(default_encoding
)
216 except (subprocess
.CalledProcessError
, OSError):
217 err
= "isainfo not found"
219 elif ostype
.startswith('MINGW'):
220 # msys' `uname` does not print gcc configuration, but prints msys
221 # configuration. so we cannot believe `uname -m`:
222 # msys1 is always i686 and msys2 is always x86_64.
223 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
225 ostype
= 'pc-windows-gnu'
227 if os
.environ
.get('MSYSTEM') == 'MINGW64':
229 elif ostype
.startswith('MSYS'):
230 ostype
= 'pc-windows-gnu'
231 elif ostype
.startswith('CYGWIN_NT'):
233 if ostype
.endswith('WOW64'):
235 ostype
= 'pc-windows-gnu'
237 err
= "unknown OS type: {}".format(ostype
)
242 'aarch64': 'aarch64',
249 'powerpc': 'powerpc',
250 'powerpc64': 'powerpc64',
251 'powerpc64le': 'powerpc64le',
253 'ppc64': 'powerpc64',
254 'ppc64le': 'powerpc64le',
262 # Consider the direct transformation first and then the special cases
263 if cputype
in cputype_mapper
:
264 cputype
= cputype_mapper
[cputype
]
265 elif cputype
in {'xscale', 'arm'}:
267 if ostype
== 'linux-android':
268 ostype
= 'linux-androideabi'
269 elif cputype
== 'armv6l':
271 if ostype
== 'linux-android':
272 ostype
= 'linux-androideabi'
275 elif cputype
in {'armv7l', 'armv8l'}:
277 if ostype
== 'linux-android':
278 ostype
= 'linux-androideabi'
281 elif cputype
== 'mips':
282 if sys
.byteorder
== 'big':
284 elif sys
.byteorder
== 'little':
287 raise ValueError("unknown byteorder: {}".format(sys
.byteorder
))
288 elif cputype
== 'mips64':
289 if sys
.byteorder
== 'big':
291 elif sys
.byteorder
== 'little':
294 raise ValueError('unknown byteorder: {}'.format(sys
.byteorder
))
295 # only the n64 ABI is supported, indicate it
297 elif cputype
== 'sparcv9' or cputype
== 'sparc64':
300 err
= "unknown cpu type: {}".format(cputype
)
303 return "{}-{}".format(cputype
, ostype
)
306 class RustBuild(object):
307 """Provide all the methods required to build Rust"""
309 self
.cargo_channel
= ''
311 self
._download
_url
= 'https://static.rust-lang.org'
312 self
.rustc_channel
= ''
314 self
.build_dir
= os
.path
.join(os
.getcwd(), "build")
316 self
.config_toml
= ''
318 self
.rust_root
= os
.path
.abspath(os
.path
.join(__file__
, '../../..'))
319 self
.use_locked_deps
= ''
320 self
.use_vendored_sources
= ''
323 def download_stage0(self
):
324 """Fetch the build system for Rust, written in Rust
326 This method will build a cache directory, then it will fetch the
327 tarball which has the stage0 compiler used to then bootstrap the Rust
330 Each downloaded tarball is extracted, after that, the script
331 will move all the content to the right place.
333 rustc_channel
= self
.rustc_channel
334 cargo_channel
= self
.cargo_channel
336 if self
.rustc().startswith(self
.bin_root()) and \
337 (not os
.path
.exists(self
.rustc()) or
338 self
.program_out_of_date(self
.rustc_stamp())):
339 self
.print_what_bootstrap_means()
340 if os
.path
.exists(self
.bin_root()):
341 shutil
.rmtree(self
.bin_root())
342 filename
= "rust-std-{}-{}.tar.gz".format(
343 rustc_channel
, self
.build
)
344 pattern
= "rust-std-{}".format(self
.build
)
345 self
._download
_stage
0_helper
(filename
, pattern
)
347 filename
= "rustc-{}-{}.tar.gz".format(rustc_channel
, self
.build
)
348 self
._download
_stage
0_helper
(filename
, "rustc")
349 self
.fix_executable("{}/bin/rustc".format(self
.bin_root()))
350 self
.fix_executable("{}/bin/rustdoc".format(self
.bin_root()))
351 with
open(self
.rustc_stamp(), 'w') as rust_stamp
:
352 rust_stamp
.write(self
.date
)
354 if "pc-windows-gnu" in self
.build
:
355 filename
= "rust-mingw-{}-{}.tar.gz".format(
356 rustc_channel
, self
.build
)
357 self
._download
_stage
0_helper
(filename
, "rust-mingw")
359 if self
.cargo().startswith(self
.bin_root()) and \
360 (not os
.path
.exists(self
.cargo()) or
361 self
.program_out_of_date(self
.cargo_stamp())):
362 self
.print_what_bootstrap_means()
363 filename
= "cargo-{}-{}.tar.gz".format(cargo_channel
, self
.build
)
364 self
._download
_stage
0_helper
(filename
, "cargo")
365 self
.fix_executable("{}/bin/cargo".format(self
.bin_root()))
366 with
open(self
.cargo_stamp(), 'w') as cargo_stamp
:
367 cargo_stamp
.write(self
.date
)
369 def _download_stage0_helper(self
, filename
, pattern
):
370 cache_dst
= os
.path
.join(self
.build_dir
, "cache")
371 rustc_cache
= os
.path
.join(cache_dst
, self
.date
)
372 if not os
.path
.exists(rustc_cache
):
373 os
.makedirs(rustc_cache
)
375 url
= "{}/dist/{}".format(self
._download
_url
, self
.date
)
376 tarball
= os
.path
.join(rustc_cache
, filename
)
377 if not os
.path
.exists(tarball
):
378 get("{}/{}".format(url
, filename
), tarball
, verbose
=self
.verbose
)
379 unpack(tarball
, self
.bin_root(), match
=pattern
, verbose
=self
.verbose
)
382 def fix_executable(fname
):
383 """Modifies the interpreter section of 'fname' to fix the dynamic linker
385 This method is only required on NixOS and uses the PatchELF utility to
386 change the dynamic linker of ELF executables.
388 Please see https://nixos.org/patchelf.html for more information
390 default_encoding
= sys
.getdefaultencoding()
392 ostype
= subprocess
.check_output(
393 ['uname', '-s']).strip().decode(default_encoding
)
394 except subprocess
.CalledProcessError
:
396 except OSError as reason
:
397 if getattr(reason
, 'winerror', None) is not None:
401 if ostype
!= "Linux":
404 if not os
.path
.exists("/etc/NIXOS"):
406 if os
.path
.exists("/lib"):
409 # At this point we're pretty sure the user is running NixOS
410 nix_os_msg
= "info: you seem to be running NixOS. Attempting to patch"
411 print(nix_os_msg
, fname
)
414 interpreter
= subprocess
.check_output(
415 ["patchelf", "--print-interpreter", fname
])
416 interpreter
= interpreter
.strip().decode(default_encoding
)
417 except subprocess
.CalledProcessError
as reason
:
418 print("warning: failed to call patchelf:", reason
)
421 loader
= interpreter
.split("/")[-1]
424 ldd_output
= subprocess
.check_output(
425 ['ldd', '/run/current-system/sw/bin/sh'])
426 ldd_output
= ldd_output
.strip().decode(default_encoding
)
427 except subprocess
.CalledProcessError
as reason
:
428 print("warning: unable to call ldd:", reason
)
431 for line
in ldd_output
.splitlines():
432 libname
= line
.split()[0]
433 if libname
.endswith(loader
):
434 loader_path
= libname
[:len(libname
) - len(loader
)]
437 print("warning: unable to find the path to the dynamic linker")
440 correct_interpreter
= loader_path
+ loader
443 subprocess
.check_output(
444 ["patchelf", "--set-interpreter", correct_interpreter
, fname
])
445 except subprocess
.CalledProcessError
as reason
:
446 print("warning: failed to call patchelf:", reason
)
449 def rustc_stamp(self
):
450 """Return the path for .rustc-stamp
453 >>> rb.build_dir = "build"
454 >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
457 return os
.path
.join(self
.bin_root(), '.rustc-stamp')
459 def cargo_stamp(self
):
460 """Return the path for .cargo-stamp
463 >>> rb.build_dir = "build"
464 >>> rb.cargo_stamp() == os.path.join("build", "stage0", ".cargo-stamp")
467 return os
.path
.join(self
.bin_root(), '.cargo-stamp')
469 def program_out_of_date(self
, stamp_path
):
470 """Check if the given program stamp is out of date"""
471 if not os
.path
.exists(stamp_path
) or self
.clean
:
473 with
open(stamp_path
, 'r') as stamp
:
474 return self
.date
!= stamp
.read()
477 """Return the binary root directory
480 >>> rb.build_dir = "build"
481 >>> rb.bin_root() == os.path.join("build", "stage0")
484 When the 'build' property is given should be a nested directory:
486 >>> rb.build = "devel"
487 >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
490 return os
.path
.join(self
.build_dir
, self
.build
, "stage0")
492 def get_toml(self
, key
):
493 """Returns the value of the given key in config.toml, otherwise returns None
496 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
497 >>> rb.get_toml("key2")
500 If the key does not exists, the result is None:
502 >>> rb.get_toml("key3") is None
505 for line
in self
.config_toml
.splitlines():
506 match
= re
.match(r
'^{}\s*=(.*)$'.format(key
), line
)
507 if match
is not None:
508 value
= match
.group(1)
509 return self
.get_string(value
) or value
.strip()
513 """Return config path for cargo"""
514 return self
.program_config('cargo')
517 """Return config path for rustc"""
518 return self
.program_config('rustc')
520 def program_config(self
, program
):
521 """Return config path for the given program
524 >>> rb.config_toml = 'rustc = "rustc"\\n'
525 >>> rb.program_config('rustc')
527 >>> rb.config_toml = ''
528 >>> cargo_path = rb.program_config('cargo')
529 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
533 config
= self
.get_toml(program
)
535 return os
.path
.expanduser(config
)
536 return os
.path
.join(self
.bin_root(), "bin", "{}{}".format(
537 program
, self
.exe_suffix()))
540 def get_string(line
):
541 """Return the value between double quotes
543 >>> RustBuild.get_string(' "devel" ')
546 start
= line
.find('"')
548 end
= start
+ 1 + line
[start
+ 1:].find('"')
549 return line
[start
+ 1:end
]
550 start
= line
.find('\'')
552 end
= start
+ 1 + line
[start
+ 1:].find('\'')
553 return line
[start
+ 1:end
]
558 """Return a suffix for executables"""
559 if sys
.platform
== 'win32':
563 def print_what_bootstrap_means(self
):
564 """Prints more information about the build system"""
565 if hasattr(self
, 'printed'):
568 if os
.path
.exists(self
.bootstrap_binary()):
570 if '--help' not in sys
.argv
or len(sys
.argv
) == 1:
573 print('info: the build system for Rust is written in Rust, so this')
574 print(' script is now going to download a stage0 rust compiler')
575 print(' and then compile the build system itself')
577 print('info: in the meantime you can read more about rustbuild at')
578 print(' src/bootstrap/README.md before the download finishes')
580 def bootstrap_binary(self
):
581 """Return the path of the boostrap binary
584 >>> rb.build_dir = "build"
585 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
586 ... "debug", "bootstrap")
589 return os
.path
.join(self
.build_dir
, "bootstrap", "debug", "bootstrap")
591 def build_bootstrap(self
):
592 """Build bootstrap"""
593 self
.print_what_bootstrap_means()
594 build_dir
= os
.path
.join(self
.build_dir
, "bootstrap")
595 if self
.clean
and os
.path
.exists(build_dir
):
596 shutil
.rmtree(build_dir
)
597 env
= os
.environ
.copy()
598 env
["RUSTC_BOOTSTRAP"] = '1'
599 env
["CARGO_TARGET_DIR"] = build_dir
600 env
["RUSTC"] = self
.rustc()
601 env
["LD_LIBRARY_PATH"] = os
.path
.join(self
.bin_root(), "lib") + \
602 (os
.pathsep
+ env
["LD_LIBRARY_PATH"]) \
603 if "LD_LIBRARY_PATH" in env
else ""
604 env
["DYLD_LIBRARY_PATH"] = os
.path
.join(self
.bin_root(), "lib") + \
605 (os
.pathsep
+ env
["DYLD_LIBRARY_PATH"]) \
606 if "DYLD_LIBRARY_PATH" in env
else ""
607 env
["LIBRARY_PATH"] = os
.path
.join(self
.bin_root(), "lib") + \
608 (os
.pathsep
+ env
["LIBRARY_PATH"]) \
609 if "LIBRARY_PATH" in env
else ""
610 env
["PATH"] = os
.path
.join(self
.bin_root(), "bin") + \
611 os
.pathsep
+ env
["PATH"]
612 if not os
.path
.isfile(self
.cargo()):
613 raise Exception("no cargo executable found at `{}`".format(
615 args
= [self
.cargo(), "build", "--manifest-path",
616 os
.path
.join(self
.rust_root
, "src/bootstrap/Cargo.toml")]
618 args
.append("--verbose")
620 args
.append("--verbose")
621 if self
.use_locked_deps
:
622 args
.append("--locked")
623 if self
.use_vendored_sources
:
624 args
.append("--frozen")
625 run(args
, env
=env
, verbose
=self
.verbose
)
627 def build_triple(self
):
628 """Build triple as in LLVM"""
629 config
= self
.get_toml('build')
632 return default_build_triple()
634 def update_submodules(self
):
635 """Update submodules"""
636 if (not os
.path
.exists(os
.path
.join(self
.rust_root
, ".git"))) or \
637 self
.get_toml('submodules') == "false":
639 print('Updating submodules')
640 default_encoding
= sys
.getdefaultencoding()
641 run(["git", "submodule", "-q", "sync"], cwd
=self
.rust_root
, verbose
=self
.verbose
)
642 submodules
= [s
.split(' ', 1)[1] for s
in subprocess
.check_output(
643 ["git", "config", "--file",
644 os
.path
.join(self
.rust_root
, ".gitmodules"),
645 "--get-regexp", "path"]
646 ).decode(default_encoding
).splitlines()]
647 submodules
= [module
for module
in submodules
648 if not ((module
.endswith("llvm") and
649 self
.get_toml('llvm-config')) or
650 (module
.endswith("jemalloc") and
651 (self
.get_toml('use-jemalloc') == "false" or
652 self
.get_toml('jemalloc'))))]
653 run(["git", "submodule", "update",
654 "--init", "--recursive"] + submodules
,
655 cwd
=self
.rust_root
, verbose
=self
.verbose
)
656 run(["git", "submodule", "-q", "foreach", "git",
657 "reset", "-q", "--hard"],
658 cwd
=self
.rust_root
, verbose
=self
.verbose
)
659 run(["git", "submodule", "-q", "foreach", "git",
661 cwd
=self
.rust_root
, verbose
=self
.verbose
)
663 def set_dev_environment(self
):
664 """Set download URL for development environment"""
665 self
._download
_url
= 'https://dev-static.rust-lang.org'
669 """Configure, fetch, build and run the initial bootstrap"""
670 parser
= argparse
.ArgumentParser(description
='Build rust')
671 parser
.add_argument('--config')
672 parser
.add_argument('--build')
673 parser
.add_argument('--clean', action
='store_true')
674 parser
.add_argument('-v', '--verbose', action
='store_true')
676 args
= [a
for a
in sys
.argv
if a
!= '-h' and a
!= '--help']
677 args
, _
= parser
.parse_known_args(args
)
679 # Configure initial bootstrap
681 build
.verbose
= args
.verbose
682 build
.clean
= args
.clean
685 with
open(args
.config
or 'config.toml') as config
:
686 build
.config_toml
= config
.read()
687 except (OSError, IOError):
690 if '\nverbose = 2' in build
.config_toml
:
692 elif '\nverbose = 1' in build
.config_toml
:
695 build
.use_vendored_sources
= '\nvendor = true' in build
.config_toml
697 build
.use_locked_deps
= '\nlocked-deps = true' in build
.config_toml
699 if 'SUDO_USER' in os
.environ
and not build
.use_vendored_sources
:
700 if os
.environ
.get('USER') != os
.environ
['SUDO_USER']:
701 build
.use_vendored_sources
= True
702 print('info: looks like you are running this command under `sudo`')
703 print(' and so in order to preserve your $HOME this will now')
704 print(' use vendored sources by default. Note that if this')
705 print(' does not work you should run a normal build first')
706 print(' before running a command like `sudo make install`')
708 if build
.use_vendored_sources
:
709 if not os
.path
.exists('.cargo'):
710 os
.makedirs('.cargo')
711 with
open('.cargo/config', 'w') as cargo_config
:
712 cargo_config
.write("""
714 replace-with = 'vendored-sources'
715 registry = 'https://example.com'
717 [source.vendored-sources]
718 directory = '{}/src/vendor'
719 """.format(build
.rust_root
))
721 if os
.path
.exists('.cargo'):
722 shutil
.rmtree('.cargo')
724 data
= stage0_data(build
.rust_root
)
725 build
.date
= data
['date']
726 build
.rustc_channel
= data
['rustc']
727 build
.cargo_channel
= data
['cargo']
730 build
.set_dev_environment()
732 build
.update_submodules()
734 # Fetch/build the bootstrap
735 build
.build
= args
.build
or build
.build_triple()
736 build
.download_stage0()
738 build
.build_bootstrap()
742 args
= [build
.bootstrap_binary()]
743 args
.extend(sys
.argv
[1:])
744 env
= os
.environ
.copy()
745 env
["BUILD"] = build
.build
746 env
["SRC"] = build
.rust_root
747 env
["BOOTSTRAP_PARENT_ID"] = str(os
.getpid())
748 env
["BOOTSTRAP_PYTHON"] = sys
.executable
749 run(args
, env
=env
, verbose
=build
.verbose
)
753 """Entry point for the bootstrap process"""
756 '-h' in sys
.argv
) or ('--help' in sys
.argv
) or (len(sys
.argv
) == 1)
759 if not help_triggered
:
760 print("Build completed successfully in {}".format(
761 format_build_time(time() - start_time
)))
762 except (SystemExit, KeyboardInterrupt) as error
:
763 if hasattr(error
, 'code') and isinstance(error
.code
, int):
764 exit_code
= error
.code
768 if not help_triggered
:
769 print("Build completed unsuccessfully in {}".format(
770 format_build_time(time() - start_time
)))
774 if __name__
== '__main__':