#!/usr/bin/python3 -u # -*- coding: utf-8 -*- # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """ This script downloads the latest chromium build (or a manually defined version) for a given platform. It then uploads the build, with the revision of the build stored in a REVISION file. """ import argparse import errno import os import shutil import subprocess import tempfile import requests from redo import retriable LAST_CHANGE_URL = ( # formatted with platform "https://www.googleapis.com/download/storage/v1/b/" "chromium-browser-snapshots/o/{}%2FLAST_CHANGE?alt=media" ) CHROMIUM_BASE_URL = ( # formatted with (platform/revision/archive) "https://www.googleapis.com/download/storage/v1/b/" "chromium-browser-snapshots/o/{}%2F{}%2F{}?alt=media" ) CHROMIUM_INFO = { "linux": { "platform": "Linux_x64", "chromium": "chrome-linux.zip", "dir": "chrome-linux", "result": "chromium-linux.tar.bz2", "chromedriver": "chromedriver_linux64.zip", }, "win32": { "platform": "Win", "chromium": "chrome-win.zip", "dir": "chrome-win", "result": "chromium-win32.tar.bz2", "chromedriver": "chromedriver_win32.zip", }, "win64": { "platform": "Win", "chromium": "chrome-win.zip", "dir": "chrome-win", "result": "chromium-win64.tar.bz2", "chromedriver": "chromedriver_win32.zip", }, "mac": { "platform": "Mac", "chromium": "chrome-mac.zip", "dir": "chrome-mac", "result": "chromium-mac.tar.bz2", "chromedriver": "chromedriver_mac64.zip", }, "mac-arm": { "platform": "Mac_Arm", "chromium": "chrome-mac.zip", "dir": "chrome-mac", "result": "chromium-mac-arm.tar.bz2", "chromedriver": "chromedriver_mac64.zip", }, } def log(msg): print("build-chromium: %s" % msg) @retriable(attempts=7, sleeptime=5, sleepscale=2) def fetch_file(url, filepath): """Download a file from the given url to a given file.""" size = 4096 r = requests.get(url, stream=True) r.raise_for_status() with open(filepath, "wb") as fd: for chunk in r.iter_content(size): fd.write(chunk) def unzip(zippath, target): """Unzips an archive to the target location.""" log("Unpacking archive at: %s to: %s" % (zippath, target)) unzip_command = ["unzip", "-q", "-o", zippath, "-d", target] subprocess.check_call(unzip_command) @retriable(attempts=7, sleeptime=5, sleepscale=2) def fetch_chromium_revision(platform): """Get the revision of the latest chromium build.""" chromium_platform = CHROMIUM_INFO[platform]["platform"] revision_url = LAST_CHANGE_URL.format(chromium_platform) log("Getting revision number for latest %s chromium build..." % chromium_platform) # Expecting a file with a single number indicating the latest # chromium build with a chromedriver that we can download r = requests.get(revision_url, timeout=30) r.raise_for_status() chromium_revision = r.content.decode("utf-8") return chromium_revision.strip() def fetch_chromedriver(platform, revision, chromium_dir): """Get the chromedriver for the given revision and repackage it.""" if not revision: revision = fetch_chromium_revision(platform) download_url = CHROMIUM_BASE_URL.format( CHROMIUM_INFO[platform]["platform"], revision, CHROMIUM_INFO[platform]["chromedriver"], ) tmpzip = os.path.join(tempfile.mkdtemp(), "cd-tmp.zip") log("Downloading chromedriver from %s" % download_url) fetch_file(download_url, tmpzip) tmppath = tempfile.mkdtemp() unzip(tmpzip, tmppath) # Find the chromedriver then copy it to the chromium directory cd_path = None for dirpath, _, filenames in os.walk(tmppath): for filename in filenames: if filename == "chromedriver" or filename == "chromedriver.exe": cd_path = os.path.join(dirpath, filename) break if cd_path is not None: break if cd_path is None: raise Exception("Could not find chromedriver binary in %s" % tmppath) log("Copying chromedriver from: %s to: %s" % (cd_path, chromium_dir)) shutil.copy(cd_path, chromium_dir) return revision def build_chromium_archive(platform, revision=None): """ Download and store a chromium build for a given platform. Retrieves either the latest version, or uses a pre-defined version if the `--revision` option is given a revision. """ upload_dir = os.environ.get("UPLOAD_DIR") if upload_dir: # Create the upload directory if it doesn't exist. try: log("Creating upload directory in %s..." % os.path.abspath(upload_dir)) os.makedirs(upload_dir) except OSError as e: if e.errno != errno.EEXIST: raise # Make a temporary location for the file tmppath = tempfile.mkdtemp() # Create the directory format expected for browsertime setup in taskgraph transform artifact_dir = CHROMIUM_INFO[platform]["dir"] chromium_dir = os.path.join(tmppath, artifact_dir) os.mkdir(chromium_dir) # Store the revision number and chromedriver revision = fetch_chromedriver(platform, revision, chromium_dir) revision_file = os.path.join(chromium_dir, ".REVISION") with open(revision_file, "w+") as f: f.write(str(revision)) tar_file = CHROMIUM_INFO[platform]["result"] tar_command = ["tar", "cjf", tar_file, "-C", tmppath, artifact_dir] log("Added revision to %s file." % revision_file) log("Tarring with the command: %s" % str(tar_command)) subprocess.check_call(tar_command) upload_dir = os.environ.get("UPLOAD_DIR") if upload_dir: # Move the tarball to the output directory for upload. log("Moving %s to the upload directory..." % tar_file) shutil.copy(tar_file, os.path.join(upload_dir, tar_file)) shutil.rmtree(tmppath) def parse_args(): """Read command line arguments and return options.""" parser = argparse.ArgumentParser() parser.add_argument( "--platform", help="Platform version of chromium to build.", required=True ) parser.add_argument( "--revision", help="Revision of chromium to build to get. " "(Defaults to the newest chromium build).", default=None, ) return parser.parse_args() if __name__ == "__main__": args = vars(parse_args()) build_chromium_archive(**args)