diff --git a/bootstrap/win/git_bootstrap.py b/bootstrap/win/git_bootstrap.py new file mode 100644 index 000000000..0d4eabc9d --- /dev/null +++ b/bootstrap/win/git_bootstrap.py @@ -0,0 +1,199 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import fnmatch +import logging +import os +import shutil +import subprocess +import sys +import tempfile + + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +ROOT_DIR = os.path.abspath(os.path.join(THIS_DIR, '..', '..')) + +DEVNULL = open(os.devnull, 'w') + + +def _check_call(argv, **kwargs): + """Wrapper for subprocess.check_call that adds logging.""" + logging.info('running %r', argv) + subprocess.check_call(argv, **kwargs) + + +def get_target_git_version(): + """Returns git version that should be installed.""" + if os.path.exists(os.path.join(ROOT_DIR, '.git_bleeding_edge')): + git_version_file = 'git_version_bleeding_edge.txt' + else: + git_version_file = 'git_version.txt' + with open(os.path.join(THIS_DIR, git_version_file)) as f: + return f.read().strip() + + +def clean_up_old_git_installations(git_directory): + """Removes git installations other than |git_directory|.""" + for entry in fnmatch.filter(os.listdir(ROOT_DIR), 'git-*_bin'): + full_entry = os.path.join(ROOT_DIR, entry) + if full_entry != git_directory: + logging.info('Cleaning up old git installation %r', entry) + shutil.rmtree(full_entry, ignore_errors=True) + + +def bootstrap_cipd(cipd_directory): + """Bootstraps CIPD client into |cipd_directory|.""" + _check_call([ + sys.executable, + os.path.join(ROOT_DIR, 'recipe_modules', 'cipd', 'resources', + 'bootstrap.py'), + '--platform', 'windows-amd64', + '--dest-directory', cipd_directory + ]) + + +def cipd_install(args, dest_directory, package, version): + """Installs CIPD |package| at |version| into |dest_directory|.""" + manifest_file = tempfile.mktemp() + try: + with open(manifest_file, 'w') as f: + f.write('%s %s\n' % (package, version)) + + cipd_args = [ + args.cipd_client, + 'ensure', + '-list', manifest_file, + '-root', dest_directory, + ] + if args.cipd_cache_directory: + cipd_args.extend(['-cache-dir', args.cipd_cache_directory]) + if args.verbose: + cipd_args.append('-verbose') + _check_call(cipd_args) + finally: + os.remove(manifest_file) + + +def need_to_install_git(args, git_directory): + """Returns True if git needs to be installed.""" + if args.force: + return True + git_exe_path = os.path.join(git_directory, 'bin', 'git.exe') + if not os.path.exists(git_exe_path): + return True + if subprocess.call( + [git_exe_path, '--version'], + stdout=DEVNULL, stderr=DEVNULL) != 0: + return True + for script in ('git.bat', 'gitk.bat', 'ssh.bat', 'ssh-keygen.bat', + 'git-bash'): + full_path = os.path.join(ROOT_DIR, script) + if not os.path.exists(full_path): + return True + with open(full_path) as f: + if os.path.relpath(git_directory, ROOT_DIR) not in f.read(): + return True + if not os.path.exists(os.path.join( + git_directory, 'etc', 'profile.d', 'python.sh')): + return True + return False + + +def install_git(args, git_version, git_directory): + """Installs |git_version| into |git_directory|.""" + if not args.cipd_client: + bootstrap_cipd(ROOT_DIR) + args.cipd_client = os.path.join(ROOT_DIR, 'cipd') + temp_dir = tempfile.mkdtemp() + try: + cipd_install(args, + temp_dir, + 'infra/depot_tools/git_installer/windows-amd64', + 'v' + git_version.replace('.', '_')) + + if not os.path.exists(git_directory): + os.makedirs(git_directory) + + # 7-zip has weird expectations for command-line syntax. Pass it as a string + # to avoid subprocess module quoting breaking it. Also double-escape + # backslashes in paths. + _check_call(' '.join([ + os.path.join(temp_dir, 'git-installer.exe'), + '-y', + '-InstallPath="%s"' % git_directory.replace('\\', '\\\\'), + '-Directory="%s"' % git_directory.replace('\\', '\\\\'), + ])) + finally: + shutil.rmtree(temp_dir, ignore_errors=True) + + with open(os.path.join(THIS_DIR, 'git.template.bat')) as f: + git_template = f.read() + git_template = git_template.replace( + 'GIT_BIN_DIR', os.path.relpath(git_directory, ROOT_DIR)) + scripts = ( + ('git.bat', 'cmd\\git.exe'), + ('gitk.bat', 'cmd\\gitk.exe'), + ('ssh.bat', 'usr\\bin\\ssh.exe'), + ('ssh-keygen.bat', 'usr\\bin\\ssh-keygen.exe'), + ) + for script in scripts: + with open(os.path.join(ROOT_DIR, script[0]), 'w') as f: + f.write(git_template.replace('GIT_PROGRAM', script[1])) + with open(os.path.join(THIS_DIR, 'git-bash.template.sh')) as f: + git_bash_template = f.read() + with open(os.path.join(ROOT_DIR, 'git-bash'), 'w') as f: + f.write(git_bash_template.replace( + 'GIT_BIN_DIR', os.path.relpath(git_directory, ROOT_DIR))) + shutil.copyfile( + os.path.join(THIS_DIR, 'profile.d.python.sh'), + os.path.join(git_directory, 'etc', 'profile.d', 'python.sh')) + + git_bat_path = os.path.join(ROOT_DIR, 'git.bat') + _check_call([git_bat_path, 'config', '--system', 'core.autocrlf', 'false']) + _check_call([git_bat_path, 'config', '--system', 'core.filemode', 'false']) + _check_call([git_bat_path, 'config', '--system', 'core.preloadindex', 'true']) + _check_call([git_bat_path, 'config', '--system', 'core.fscache', 'true']) + + +def sync_git_help_files(git_directory): + """Updates depot_tools help files inside |git_directory|.""" + # TODO(phajdan.jr): consider replacing the call with python code. + _check_call([ + 'xcopy', '/i', '/q', '/d', '/y', + os.path.join(ROOT_DIR, 'man', 'html', '*'), + os.path.join(git_directory, 'mingw64', 'share', 'doc', 'git-doc')], + stdout=DEVNULL) + + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument('--cipd-client', help='Path to CIPD client binary.') + parser.add_argument('--cipd-cache-directory', + help='Path to CIPD cache directory.') + parser.add_argument('--force', action='store_true', + help='Always re-install git.') + parser.add_argument('--verbose', action='store_true') + args = parser.parse_args(argv) + + if os.environ.get('WIN_TOOLS_FORCE') == '1': + args.force = True + + logging.basicConfig(level=logging.INFO if args.verbose else logging.WARN) + + git_version = get_target_git_version() + git_directory = os.path.join(ROOT_DIR, 'git-%s-64_bin' % git_version) + + clean_up_old_git_installations(git_directory) + + if need_to_install_git(args, git_directory): + install_git(args, git_version, git_directory) + + sync_git_help_files(git_directory) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) \ No newline at end of file