blob: feef14318b570f084beaca20356bac67a21c5435 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2015 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.
"""Download and run node.js."""
import os
import shutil
import sys
import subprocess
import tarfile
import tempfile
import urllib.request
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
DEFAULT_VERSION = '0.12.2'
BUCKET = 'chromium-infra-bins'
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-x86.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/node/%s/%s' % (
BUCKET, version, target)
print('Fetching %s' % url)
u = urllib.request.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.mkdir(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):
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,] + sys.argv[1:])
if __name__ == '__main__':
sys.exit(main())