From b0dcf8a7e6c4c13c7478be68d505bc50e97b2361 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 1 Jul 2026 17:39:34 -0700 Subject: [PATCH] Ask about installing git hooks as part of `./bootstrap.py` Just like all the other actions that bookstrap performs it will only run it if the stamp file is missing, so it effectively only asks once (unless you delete the stamp in the `out/` directory). --- bootstrap.py | 139 +++++++++++++++------- tools/maint/{ => git-hooks}/post-checkout | 2 +- tools/maint/{ => git-hooks}/pre-push | 2 +- 3 files changed, 95 insertions(+), 48 deletions(-) rename tools/maint/{ => git-hooks}/post-checkout (88%) rename tools/maint/{ => git-hooks}/pre-push (64%) diff --git a/bootstrap.py b/bootstrap.py index 00375cac33724..7ed6823571fd8 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -23,25 +23,6 @@ # created. Bootstrap.py needs to be run before an .emscripten config exists. from tools import utils -actions = [ - ('npm packages', [ - 'package.json', - 'package-lock.json', - ], ['npm', 'ci']), - ('create entry points', [ - 'tools/maint/create_entry_points.py', - 'tools/pylauncher/pylauncher.exe', - 'tools/maint/run_python.bat', - 'tools/maint/run_python.sh', - 'tools/maint/run_python.ps1', - ], [sys.executable, 'tools/maint/create_entry_points.py']), - ('git submodules', [ - 'test/third_party/posixtestsuite/', - 'test/third_party/googletest', - 'test/third_party/wasi-test-suite', - ], ['git', 'submodule', 'update', '--init']), -] - def get_stamp_file(action_name): return os.path.join(STAMP_DIR, action_name.replace(' ', '_') + '.stamp') @@ -64,44 +45,110 @@ def check(): utils.exit_with_error(f'emscripten setup is not complete ("{name}" is out-of-date). Run `bootstrap.py` to update') +def ask_yes_no(question): + while True: + try: + reply = input(f"{question} (y/n): ").strip().lower() + except EOFError: + return False + + if reply in {'y', 'yes'}: + return True + if reply in {'n', 'no'}: + return False + + print("Invalid input. Please enter 'y' or 'n'.") + + +def maybe_install_hooks(): + if os.path.exists('.git/hooks/pre-push'): + print('git hooks already installed; skipping') + return + if os.environ.get('CI') or not sys.stdin.isatty(): + # Do nothing when running in CI or non-interactive shell + return + if ask_yes_no('Install emscripten git hooks (see tools/maint/git-hooks)?'): + install_hooks() + + +def install_hooks(): + if not os.path.exists(utils.path_from_root('.git')): + print('--install-git-hooks requires a git checkout') + return 1 + + dst = utils.path_from_root('.git/hooks') + if not os.path.exists(dst): + os.mkdir(dst) + + for src in ('tools/maint/git-hooks/post-checkout', 'tools/maint/git-hooks/pre-push'): + shutil.copy(utils.path_from_root(src), dst) + return 0 + + +def run_cmd(cmd): + orig_exe = cmd[0] + if not os.path.isabs(orig_exe): + cmd[0] = shutil.which(orig_exe) + if not cmd[0]: + utils.exit_with_error(f'command not found: {orig_exe}') + print(' -> %s' % ' '.join(cmd)) + subprocess.run(cmd, check=True, text=True, encoding='utf-8', cwd=utils.path_from_root()) + + +actions = [ + ('npm packages', [ + 'package.json', + 'package-lock.json', + ], ['npm', 'ci']), + ('create entry points', [ + 'tools/maint/create_entry_points.py', + 'tools/pylauncher/pylauncher.exe', + 'tools/maint/run_python.bat', + 'tools/maint/run_python.sh', + 'tools/maint/run_python.ps1', + ], [sys.executable, 'tools/maint/create_entry_points.py']), + ('git submodules', [ + 'test/third_party/posixtestsuite/', + 'test/third_party/googletest', + 'test/third_party/wasi-test-suite', + ], ['git', 'submodule', 'update', '--init']), + ('install hooks', [ + 'tools/maint/git-hooks/post-checkout', + 'tools/maint/git-hooks/pre-push', + ], maybe_install_hooks), +] + + def main(args): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('-v', '--verbose', action='store_true', help='verbose', default=False) + parser.add_argument('-f', '--force', action='store_true', help='force all actions to run', default=False) parser.add_argument('-n', '--dry-run', action='store_true', help='dry run', default=False) parser.add_argument('-i', '--install-git-hooks', action='store_true', help='install emscripten git hooks', default=False) args = parser.parse_args() if args.install_git_hooks: - if not os.path.exists(utils.path_from_root('.git')): - print('--install-git-hooks requires git checkout') - return 1 - - dst = utils.path_from_root('.git/hooks') - if not os.path.exists(dst): - os.mkdir(dst) - - src = utils.path_from_root('tools/maint/post-checkout') - for src in ('tools/maint/post-checkout', 'tools/maint/pre-push'): - shutil.copy(utils.path_from_root(src), dst) - return 0 - - for name, deps, cmd in actions: - if check_deps(name, deps): - print('Up-to-date: %s' % name) - continue - print('Out-of-date: %s' % name) - stamp_file = get_stamp_file(name) + return install_hooks() + + for name, deps, action in actions: + if not args.force: + if check_deps(name, deps): + print('Up-to-date: %s' % name) + continue + print('Out-of-date: %s' % name) if args.dry_run: - print(' (skipping: dry run) -> %s' % ' '.join(cmd)) + if type(action) == list: + action_str = ' '.join(action) + else: + action_str = action.__name__ + print(f' (skipping: dry run) -> {action_str}') continue - orig_exe = cmd[0] - if not os.path.isabs(orig_exe): - cmd[0] = shutil.which(orig_exe) - if not cmd[0]: - utils.exit_with_error(f'command not found: {orig_exe}') - print(' -> %s' % ' '.join(cmd)) - subprocess.run(cmd, check=True, text=True, encoding='utf-8', cwd=utils.path_from_root()) + if type(action) == list: + run_cmd(action) + else: + action() utils.safe_ensure_dirs(STAMP_DIR) + stamp_file = get_stamp_file(name) utils.write_file(stamp_file, 'Timestamp file created by bootstrap.py') return 0 diff --git a/tools/maint/post-checkout b/tools/maint/git-hooks/post-checkout similarity index 88% rename from tools/maint/post-checkout rename to tools/maint/git-hooks/post-checkout index af7e11f5b2ea6..d64ca612a7ef6 100755 --- a/tools/maint/post-checkout +++ b/tools/maint/git-hooks/post-checkout @@ -6,7 +6,7 @@ # The bootstrap script itself is smart enough to basically do nothing unless # one of the relevant files was updated (e.g. package.json). # -# This script can be installed using `bootstrap.py -i`. +# This script is normally installed by `bootstrap.py`. # Test for the existence of the bootstrap script itself to handle branches # that predate its existence. diff --git a/tools/maint/pre-push b/tools/maint/git-hooks/pre-push similarity index 64% rename from tools/maint/pre-push rename to tools/maint/git-hooks/pre-push index 37f28bea7e7af..0408735329590 100755 --- a/tools/maint/pre-push +++ b/tools/maint/git-hooks/pre-push @@ -2,7 +2,7 @@ # # Git pre-push script that runs some quick/simple tests. # -# This script can be installed using `bootstrap.py -i`. +# This script is normally installed by `bootstrap.py`. set -o errexit