| #!/usr/bin/env python | |
| # Copyright (c) 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 os | |
| import shutil | |
| import sys | |
| import subprocess | |
| import tarfile | |
| import tempfile | |
| import urllib2 | |
| THIS_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| DEFAULT_VERSION = '4.5.0' | |
| BUCKET = 'chromium-nodejs' | |
| def install_latest_node_js(version, tmp_dir): | |
| target_dir = os.path.join(THIS_DIR, 'runtimes', version) | |
| version_file = os.path.join(target_dir, 'VERSION') | |
| if sys.platform == 'win32': | |
| bin_location = os.path.join(target_dir, 'node.exe') | |
| else: | |
| bin_location = os.path.join(target_dir, 'bin', 'node') | |
| # We assume that, if the VERSION file exists, then the installation is good. | |
| if os.path.exists(version_file): | |
| with open(version_file, 'r') as f: | |
| if f.read() == version: | |
| return bin_location | |
| # TODO(hinoka): This probably doesn't work that well on Windows... | |
| shutil.rmtree(target_dir, ignore_errors=True) | |
| # Get the target name correct. | |
| if sys.platform == 'win32': | |
| target = 'node.exe' | |
| elif sys.platform == 'darwin': | |
| target = 'node-v%s-darwin-x64.tar.gz' % version | |
| elif sys.platform == 'linux2': | |
| target = 'node-v%s-linux-x86.tar.gz' % version | |
| else: | |
| raise Exception('Unrecognized platform %s' % sys.platform) | |
| dest = os.path.join(tmp_dir, 'node_download') | |
| url = 'https://storage.googleapis.com/%s/%s/%s' % ( | |
| BUCKET, version, target) | |
| print('Fetching %s' % url) | |
| u = urllib2.urlopen(url) | |
| with open(dest, 'wb') as f: | |
| while True: | |
| chunk = u.read(2 ** 20) | |
| if not chunk: | |
| break | |
| f.write(chunk) | |
| # When multiple node.py instances run at the same time for the first time, | |
| # the check to see whether or not the installation occured already. But then | |
| # they all race to see who's the first to run shutil.move(), which obviously | |
| # fails for everyone other than the first instance. This CL makes | |
| # os.rename() not fail, since its assumed that if it fails that means | |
| # someone else already created an installation. | |
| # | |
| # Another approach is to use an flock, but then it starts to get messy when | |
| # you have to keep polling filesystem state to see if another instance | |
| # finished, or add timeouts to remove an flock if it was left on the system by | |
| # a failed attempt, etc, etc. This just seemed like a less flaky solution, | |
| # despite the fact that it means multiple network requests are spawned. | |
| write_version = True | |
| if sys.platform != 'win32': | |
| # The Windows version comes as a self contained executable, the other | |
| # versions come as a tar.gz that needs to be extracted. | |
| with tarfile.open(dest, 'r:gz') as f: | |
| f.extractall(path=tmp_dir) | |
| try: | |
| os.mkdir(os.path.join(THIS_DIR, 'runtimes')) | |
| os.rename(os.path.join(tmp_dir, target[:-len('.tar.gz')]), | |
| target_dir) | |
| except OSError: | |
| write_version = False | |
| os.remove(dest) | |
| else: | |
| try: | |
| # Still potentiall racy, from python docs: | |
| # "On Windows...there may be no way to implement an atomic rename when dst | |
| # names an existing file." | |
| os.makedirs(target_dir) | |
| os.rename(dest, bin_location) | |
| except OSError: | |
| write_version = False | |
| if write_version: | |
| with open(version_file, 'w') as f: | |
| f.write(version) | |
| return bin_location | |
| def main(mode=None): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--running-as-hook', action='store_true') | |
| args, rest_args = parser.parse_known_args() | |
| # Exit early if this is being invoked from `gclient runhooks` | |
| # and not on a bot (CHROME_HEADLESS is set on all build bots). | |
| if (args.running_as_hook and | |
| os.environ.get('CHROME_HEADLESS', '0') != '1'): | |
| return 0 | |
| version = os.environ.get('NODE_VERSION', DEFAULT_VERSION) | |
| try: | |
| tmp_dir = tempfile.mkdtemp(dir=THIS_DIR) | |
| bin_location = install_latest_node_js(version, tmp_dir) | |
| finally: | |
| if os.path.exists(tmp_dir): | |
| shutil.rmtree(tmp_dir) | |
| if mode == 'npm': | |
| # TODO(hinoka): How about Windows...? | |
| bin_location = os.path.join(os.path.dirname(bin_location), 'npm') | |
| return subprocess.call([bin_location, ] + rest_args) | |
| if __name__ == '__main__': | |
| sys.exit(main()) |