#!/usr/bin/env python # Copyright 2019 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. """Ensure that CIPD fetched the right GN version. Due to crbug.com/944367, using cipd in gclient to fetch GN binaries may not always work right. This is a script that can be used as a backup method to force-install GN at the right revision, just in case. It should be used as a gclient hook alongside fetching GN via CIPD until we have a proper fix in place. TODO(crbug.com/944667): remove this script when it is no longer needed. """ from __future__ import print_function import argparse import errno import io import os import re import stat import subprocess import sys try: import urllib2 as urllib except ImportError: import urllib.request as urllib import zipfile BUILDTOOLS_DIR = os.path.abspath(os.path.dirname(__file__)) SRC_DIR = os.path.dirname(BUILDTOOLS_DIR) def ChmodGnFile(path_to_exe): """Makes the gn binary executable for all and writable for the user.""" os.chmod(path_to_exe, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | # This is 0o755. stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) def main(): parser = argparse.ArgumentParser() parser.add_argument('version', help='CIPD "git_revision:XYZ" label for GN to sync to') args = parser.parse_args() if not args.version.startswith('git_revision:'): print('Unknown version format: %s' % args.version) return 2 desired_revision = args.version[len('git_revision:'):] if sys.platform == 'darwin': platform, member, dest_dir = ('mac-amd64', 'gn', 'mac') elif sys.platform == 'win32': platform, member, dest_dir = ('windows-amd64', 'gn.exe', 'win') else: platform, member, dest_dir = ('linux-amd64', 'gn', 'linux64') path_to_exe = os.path.join(BUILDTOOLS_DIR, dest_dir, member) cmd = [path_to_exe, '--version'] cmd_str = ' '.join(cmd) try: out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=SRC_DIR).decode(errors='replace') except subprocess.CalledProcessError as e: print('`%s` returned %d:\n%s' % (cmd_str, e.returncode, e.output)) return 1 except OSError as e: if e.errno != errno.ENOENT: print('`%s` failed:\n%s' % (cmd_str, e.strerror)) return 1 # The tool doesn't exist, so redownload it. out = '' if out: current_revision_match = re.findall(r'\((.*)\)', out) if current_revision_match: current_revision = current_revision_match[0] if desired_revision.startswith(current_revision): # We're on the right version, so we're done. return 0 print("`%s` returned '%s', which wasn't what we were expecting." % (cmd_str, out.strip())) print("Force-installing %s to update it." % desired_revision) url = 'https://chrome-infra-packages.appspot.com/dl/gn/gn/%s/+/%s' % ( platform, args.version) try: zipdata = urllib.urlopen(url).read() except urllib.HTTPError as e: print('Failed to download the package from %s: %d %s' % ( url, e.code, e.reason)) return 1 try: # Make the existing file writable so that we can overwrite it. ChmodGnFile(path_to_exe) except OSError as e: if e.errno != errno.ENOENT: print('Failed to make %s writable:\n%s\n' % (path_to_exe, e.strerror)) return 1 try: zf = zipfile.ZipFile(io.BytesIO(zipdata)) zf.extract(member, os.path.join(BUILDTOOLS_DIR, dest_dir)) except OSError as e: print('Failed to extract the binary:\n%s\n' % e.strerror) return 1 except (zipfile.LargeZipFile, zipfile.BadZipfile) as e: print('Zip containing gn was corrupt:\n%s\n' % e) return 1 try: ChmodGnFile(path_to_exe) except OSError as e: print('Failed to make %s executable:\n%s\n' % (path_to_exe, e.strerror)) return 1 return 0 if __name__ == '__main__': sys.exit(main())