Your IP : 3.145.70.108
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
nodeenv
~~~~~~~
Node.js virtual environment
:copyright: (c) 2014 by Eugene Kalinin
:license: BSD, see LICENSE for more details.
"""
import contextlib
import io
import json
import sys
import os
import re
import ssl
import stat
import logging
import operator
import argparse
import subprocess
import tarfile
import pipes
import platform
import zipfile
import shutil
import sysconfig
import glob
try: # pragma: no cover (py2 only)
from ConfigParser import SafeConfigParser as ConfigParser
# noinspection PyCompatibility
import urllib2
iteritems = operator.methodcaller('iteritems')
import httplib
IncompleteRead = httplib.IncompleteRead
except ImportError: # pragma: no cover (py3 only)
from configparser import ConfigParser
# noinspection PyUnresolvedReferences
import urllib.request as urllib2
iteritems = operator.methodcaller('items')
import http
IncompleteRead = http.client.IncompleteRead
from pkg_resources import parse_version
nodeenv_version = '1.8.0'
join = os.path.join
abspath = os.path.abspath
src_base_url = None
is_PY3 = sys.version_info[0] >= 3
is_WIN = platform.system() == 'Windows'
is_CYGWIN = platform.system().startswith(('CYGWIN', 'MSYS'))
ignore_ssl_certs = False
# ---------------------------------------------------------
# Utils
# https://github.com/jhermann/waif/blob/master/python/to_uft8.py
def to_utf8(text):
"""Convert given text to UTF-8 encoding (as far as possible)."""
if not text or is_PY3:
return text
try: # unicode or pure ascii
return text.encode("utf8")
except UnicodeDecodeError:
try: # successful UTF-8 decode means it's pretty sure UTF-8
text.decode("utf8")
return text
except UnicodeDecodeError:
try: # get desperate; and yes, this has a western hemisphere bias
return text.decode("cp1252").encode("utf8")
except UnicodeDecodeError:
pass
return text # return unchanged, hope for the best
class Config(object):
"""
Configuration namespace.
"""
# Defaults
node = 'latest'
npm = 'latest'
with_npm = False
jobs = '2'
without_ssl = False
debug = False
profile = False
make = 'make'
prebuilt = True
ignore_ssl_certs = False
mirror = None
@classmethod
def _load(cls, configfiles, verbose=False):
"""
Load configuration from the given files in reverse order,
if they exist and have a [nodeenv] section.
Additionally, load version from .node-version if file exists.
"""
for configfile in reversed(configfiles):
configfile = os.path.expanduser(configfile)
if not os.path.exists(configfile):
continue
ini_file = ConfigParser()
ini_file.read(configfile)
section = "nodeenv"
if not ini_file.has_section(section):
continue
for attr, val in iteritems(vars(cls)):
if attr.startswith('_') or not \
ini_file.has_option(section, attr):
continue
if isinstance(val, bool):
val = ini_file.getboolean(section, attr)
else:
val = ini_file.get(section, attr)
if verbose:
print('CONFIG {0}: {1} = {2}'.format(
os.path.basename(configfile), attr, val))
setattr(cls, attr, val)
if os.path.exists(".node-version"):
with open(".node-version", "r") as v_file:
setattr(cls, "node", v_file.readlines(1)[0].strip())
@classmethod
def _dump(cls):
"""
Print defaults for the README.
"""
print(" [nodeenv]")
print(" " + "\n ".join(
"%s = %s" % (k, v) for k, v in sorted(iteritems(vars(cls)))
if not k.startswith('_')))
Config._default = dict(
(attr, val) for attr, val in iteritems(vars(Config))
if not attr.startswith('_')
)
def clear_output(out):
"""
Remove new-lines and
"""
return out.decode('utf-8').replace('\n', '')
def remove_env_bin_from_path(env, env_bin_dir):
"""
Remove bin directory of the current environment from PATH
"""
return env.replace(env_bin_dir + ':', '')
def node_version_from_args(args):
"""
Parse the node version from the argparse args
"""
if args.node == 'system':
out, err = subprocess.Popen(
["node", "--version"], stdout=subprocess.PIPE).communicate()
return parse_version(clear_output(out).replace('v', ''))
return parse_version(args.node)
def create_logger():
"""
Create logger for diagnostic
"""
# create logger
loggr = logging.getLogger("nodeenv")
loggr.setLevel(logging.INFO)
# monkey patch
def emit(self, record):
msg = self.format(record)
fs = "%s" if getattr(record, "continued", False) else "%s\n"
self.stream.write(fs % to_utf8(msg))
self.flush()
logging.StreamHandler.emit = emit
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter(fmt="%(message)s")
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
loggr.addHandler(ch)
return loggr
logger = create_logger()
def make_parser():
"""
Make a command line argument parser.
"""
parser = argparse.ArgumentParser(
usage="%(prog)s [OPTIONS] DEST_DIR")
parser.add_argument(
'--version', action='version', version=nodeenv_version)
parser.add_argument(
'-n', '--node', dest='node', metavar='NODE_VER', default=Config.node,
help='The node.js version to use, e.g., '
'--node=0.4.3 will use the node-v0.4.3 '
'to create the new environment. '
'The default is last stable version (`latest`). '
'Use `lts` to use the latest LTS release. '
'Use `system` to use system-wide node.')
parser.add_argument(
'--mirror',
action="store", dest='mirror', default=Config.mirror,
help='Set mirror server of nodejs.org to download from.')
if not is_WIN:
parser.add_argument(
'-j', '--jobs', dest='jobs', default=Config.jobs,
help='Sets number of parallel commands at node.js compilation. '
'The default is 2 jobs.')
parser.add_argument(
'--load-average', dest='load_average',
help='Sets maximum load average for executing parallel commands '
'at node.js compilation.')
parser.add_argument(
'--without-ssl', dest='without_ssl',
action='store_true', default=Config.without_ssl,
help='Build node.js without SSL support')
parser.add_argument(
'--debug', dest='debug',
action='store_true', default=Config.debug,
help='Build debug variant of the node.js')
parser.add_argument(
'--profile', dest='profile',
action='store_true', default=Config.profile,
help='Enable profiling for node.js')
parser.add_argument(
'--make', '-m', dest='make_path',
metavar='MAKE_PATH',
help='Path to make command',
default=Config.make)
parser.add_argument(
'--source', dest='prebuilt',
action='store_false', default=Config.prebuilt,
help='Install node.js from the source')
parser.add_argument(
'-v', '--verbose',
action='store_true', dest='verbose', default=False,
help="Verbose mode")
parser.add_argument(
'-q', '--quiet',
action='store_true', dest='quiet', default=False,
help="Quiet mode")
parser.add_argument(
'-C', '--config-file', dest='config_file', default=None,
help="Load a different file than '~/.nodeenvrc'. "
"Pass an empty string for no config (use built-in defaults).")
parser.add_argument(
'-r', '--requirements',
dest='requirements', default='', metavar='FILENAME',
help='Install all the packages listed in the given requirements file.')
parser.add_argument(
'--prompt', dest='prompt',
help='Provides an alternative prompt prefix for this environment')
parser.add_argument(
'-l', '--list', dest='list',
action='store_true', default=False,
help='Lists available node.js versions')
parser.add_argument(
'--update', dest='update',
action='store_true', default=False,
help='Install npm packages from file without node')
parser.add_argument(
'--with-npm', dest='with_npm',
action='store_true', default=Config.with_npm,
help='Build without installing npm into the new virtual environment. '
'Required for node.js < 0.6.3. By default, the npm included with '
'node.js is used. Under Windows, this defaults to true.')
parser.add_argument(
'--npm', dest='npm',
metavar='NPM_VER', default=Config.npm,
help='The npm version to use, e.g., '
'--npm=0.3.18 will use the npm-0.3.18.tgz '
'tarball to install. '
'The default is last available version (`latest`).')
parser.add_argument(
'--no-npm-clean', dest='no_npm_clean',
action='store_true', default=False,
help='Skip the npm 0.x cleanup. Cleanup is enabled by default.')
parser.add_argument(
'--python-virtualenv', '-p', dest='python_virtualenv',
action='store_true', default=False,
help='Use current python virtualenv')
parser.add_argument(
'--clean-src', '-c', dest='clean_src',
action='store_true', default=False,
help='Remove "src" directory after installation')
parser.add_argument(
'--force', dest='force',
action='store_true', default=False,
help='Force installation in a pre-existing directory')
parser.add_argument(
'--prebuilt', dest='prebuilt',
action='store_true', default=Config.prebuilt,
help='Install node.js from prebuilt package (default)')
parser.add_argument(
'--ignore_ssl_certs', dest='ignore_ssl_certs',
action='store_true', default=Config.ignore_ssl_certs,
help='Ignore certificates for package downloads. - UNSAFE -')
parser.add_argument(
metavar='DEST_DIR', dest='env_dir', nargs='?',
help='Destination directory')
return parser
def parse_args(check=True):
"""
Parses command line arguments.
Set `check` to False to skip validation checks.
"""
parser = make_parser()
args = parser.parse_args()
if args.config_file is None:
args.config_file = ["./tox.ini", "./setup.cfg", "~/.nodeenvrc"]
elif not args.config_file:
args.config_file = []
else:
# Make sure that explicitly provided files exist
if not os.path.exists(args.config_file):
parser.error("Config file '{0}' doesn't exist!".format(
args.config_file))
args.config_file = [args.config_file]
if not check:
return args
if not args.list:
if not args.python_virtualenv and not args.env_dir:
parser.error('You must provide a DEST_DIR or '
'use current python virtualenv')
return args
def mkdir(path):
"""
Create directory
"""
if not os.path.exists(path):
logger.debug(' * Creating: %s ... ', path, extra=dict(continued=True))
os.makedirs(path)
logger.debug('done.')
else:
logger.debug(' * Directory %s already exists', path)
def make_executable(filename):
mode_0755 = (stat.S_IRWXU | stat.S_IXGRP |
stat.S_IRGRP | stat.S_IROTH | stat.S_IXOTH)
os.chmod(filename, mode_0755)
# noinspection PyArgumentList
def writefile(dest, content, overwrite=True, append=False):
"""
Create file and write content in it
"""
content = to_utf8(content)
if is_PY3 and type(content) != bytes:
content = bytes(content, 'utf-8')
if not os.path.exists(dest):
logger.debug(' * Writing %s ... ', dest, extra=dict(continued=True))
with open(dest, 'wb') as f:
f.write(content)
make_executable(dest)
logger.debug('done.')
return
else:
with open(dest, 'rb') as f:
c = f.read()
if content in c:
logger.debug(' * Content %s already in place', dest)
return
if not overwrite:
logger.info(' * File %s exists with different content; '
' not overwriting', dest)
return
if append:
logger.info(' * Appending data to %s', dest)
with open(dest, 'ab') as f:
f.write(content)
return
logger.info(' * Overwriting %s with new content', dest)
with open(dest, 'wb') as f:
f.write(content)
def callit(cmd, show_stdout=True, in_shell=False,
cwd=None, extra_env=None):
"""
Execute cmd line in sub-shell
"""
all_output = []
cmd_parts = []
for part in cmd:
if len(part) > 45:
part = part[:20] + "..." + part[-20:]
if ' ' in part or '\n' in part or '"' in part or "'" in part:
part = '"%s"' % part.replace('"', '\\"')
cmd_parts.append(part)
cmd_desc = ' '.join(cmd_parts)
logger.debug(" ** Running command %s" % cmd_desc)
if in_shell:
cmd = ' '.join(cmd)
# output
stdout = subprocess.PIPE
# env
if extra_env:
env = os.environ.copy()
if extra_env:
env.update(extra_env)
else:
env = None
# execute
try:
proc = subprocess.Popen(
cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout,
cwd=cwd, env=env, shell=in_shell)
except Exception:
e = sys.exc_info()[1]
logger.error("Error %s while executing command %s" % (e, cmd_desc))
raise
stdout = proc.stdout
while stdout:
line = stdout.readline()
if not line:
break
try:
if is_WIN:
line = line.decode('mbcs').rstrip()
else:
line = line.decode('utf8').rstrip()
except UnicodeDecodeError:
line = line.decode('cp866').rstrip()
all_output.append(line)
if show_stdout:
logger.info(line)
proc.wait()
# error handler
if proc.returncode:
if show_stdout:
for s in all_output:
logger.critical(s)
raise OSError("Command %s failed with error code %s"
% (cmd_desc, proc.returncode))
return proc.returncode, all_output
def get_root_url(version):
if parse_version(version) > parse_version("0.5.0"):
return '%s/v%s/' % (src_base_url, version)
else:
return src_base_url
def is_x86_64_musl():
return sysconfig.get_config_var('HOST_GNU_TYPE') == 'x86_64-pc-linux-musl'
def is_riscv64():
return platform.machine() == 'riscv64'
def get_node_bin_url(version):
archmap = {
'x86': 'x86', # Windows Vista 32
'i686': 'x86',
'x86_64': 'x64', # Linux Ubuntu 64
'amd64': 'x64', # FreeBSD 64bits
'AMD64': 'x64', # Windows Server 2012 R2 (x64)
'armv6l': 'armv6l', # arm
'armv7l': 'armv7l',
'armv8l': 'armv7l',
'aarch64': 'arm64',
'arm64': 'arm64',
'arm64/v8': 'arm64',
'armv8': 'arm64',
'armv8.4': 'arm64',
'ppc64le': 'ppc64le', # Power PC
's390x': 's390x', # IBM S390x
'riscv64': 'riscv64', # RISCV 64
}
sysinfo = {
'system': platform.system().lower(),
'arch': archmap[platform.machine()],
}
if is_WIN or is_CYGWIN:
postfix = '-win-%(arch)s.zip' % sysinfo
elif is_x86_64_musl():
postfix = '-linux-x64-musl.tar.gz'
else:
postfix = '-%(system)s-%(arch)s.tar.gz' % sysinfo
filename = 'node-v%s%s' % (version, postfix)
return get_root_url(version) + filename
def get_node_src_url(version):
tar_name = 'node-v%s.tar.gz' % version
return get_root_url(version) + tar_name
@contextlib.contextmanager
def tarfile_open(*args, **kwargs):
"""Compatibility layer because py26."""
tf = tarfile.open(*args, **kwargs)
try:
yield tf
finally:
tf.close()
def _download_node_file(node_url, n_attempt=3):
"""Do multiple attempts to avoid incomplete data in case
of unstable network"""
while n_attempt > 0:
try:
return io.BytesIO(urlopen(node_url).read())
except IncompleteRead as e:
logger.warning(
'Incomplete read while reading'
'from {} - {}'.format(node_url, e)
)
n_attempt -= 1
if n_attempt == 0:
raise e
def download_node_src(node_url, src_dir, args):
"""
Download source code
"""
logger.info('.', extra=dict(continued=True))
dl_contents = _download_node_file(node_url)
logger.info('.', extra=dict(continued=True))
if is_WIN or is_CYGWIN:
ctx = zipfile.ZipFile(dl_contents)
members = operator.methodcaller('namelist')
member_name = lambda s: s # noqa: E731
else:
ctx = tarfile_open(fileobj=dl_contents)
members = operator.methodcaller('getmembers')
member_name = operator.attrgetter('name')
with ctx as archive:
node_ver = re.escape(args.node)
rexp_string = r"node-v%s[^/]*/(README\.md|CHANGELOG\.md|LICENSE)"\
% node_ver
extract_list = [
member
for member in members(archive)
if re.match(rexp_string, member_name(member)) is None
]
archive.extractall(src_dir, extract_list)
def urlopen(url):
home_url = "https://github.com/ekalinin/nodeenv/"
headers = {'User-Agent': 'nodeenv/%s (%s)' % (nodeenv_version, home_url)}
req = urllib2.Request(url, None, headers)
if ignore_ssl_certs:
# py27: protocol required, py3: optional
# https://github.com/ekalinin/nodeenv/issues/296
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.verify_mode = ssl.CERT_NONE
return urllib2.urlopen(req, context=context)
return urllib2.urlopen(req)
# ---------------------------------------------------------
# Virtual environment functions
def copytree(src, dst, symlinks=False, ignore=None):
for item in os.listdir(src):
s = os.path.join(src, item)
d = os.path.join(dst, item)
if os.path.isdir(s):
try:
shutil.copytree(s, d, symlinks, ignore)
except OSError:
copytree(s, d, symlinks, ignore)
else:
if os.path.islink(s):
# copy link only if it not exists. #189
if not os.path.islink(d):
os.symlink(os.readlink(s), d)
else:
shutil.copy2(s, d)
def copy_node_from_prebuilt(env_dir, src_dir, node_version):
"""
Copy prebuilt binaries into environment
"""
logger.info('.', extra=dict(continued=True))
if is_WIN:
dest = join(env_dir, 'Scripts')
mkdir(dest)
elif is_CYGWIN:
dest = join(env_dir, 'bin')
mkdir(dest)
# write here to avoid https://bugs.python.org/issue35650
writefile(join(env_dir, 'bin', 'node'), CYGWIN_NODE)
else:
dest = env_dir
src_folder_tpl = src_dir + to_utf8('/node-v%s*' % node_version)
src_folder, = glob.glob(src_folder_tpl)
copytree(src_folder, dest, True)
if is_CYGWIN:
for filename in ('npm', 'npx', 'node.exe'):
filename = join(env_dir, 'bin', filename)
if os.path.exists(filename):
make_executable(filename)
logger.info('.', extra=dict(continued=True))
def build_node_from_src(env_dir, src_dir, node_src_dir, args):
env = {}
make_param_names = ['load-average', 'jobs']
make_param_values = map(
lambda x: getattr(args, x.replace('-', '_')),
make_param_names)
make_opts = [
'--{0}={1}'.format(name, value)
if len(value) > 0 else '--{0}'.format(name)
for name, value in zip(make_param_names, make_param_values)
if value is not None
]
if getattr(sys.version_info, 'major', sys.version_info[0]) > 2:
# Currently, the node.js build scripts are using python2.*,
# therefore we need to temporarily point python exec to the
# python 2.* version in this case.
try:
_, which_python2_output = callit(
['which', 'python2'], args.verbose, True, node_src_dir, env
)
python2_path = which_python2_output[0]
except (OSError, IndexError):
raise OSError(
'Python >=3.0 virtualenv detected, but no python2 '
'command (required for building node.js) was found'
)
logger.debug(' * Temporarily pointing python to %s', python2_path)
node_tmpbin_dir = join(src_dir, 'tmpbin')
node_tmpbin_link = join(node_tmpbin_dir, 'python')
mkdir(node_tmpbin_dir)
if not os.path.exists(node_tmpbin_link):
callit(['ln', '-s', python2_path, node_tmpbin_link])
env['PATH'] = '{}:{}'.format(node_tmpbin_dir,
os.environ.get('PATH', ''))
conf_cmd = [
'./configure',
'--prefix=%s' % pipes.quote(env_dir)
]
if args.without_ssl:
conf_cmd.append('--without-ssl')
if args.debug:
conf_cmd.append('--debug')
if args.profile:
conf_cmd.append('--profile')
make_cmd = args.make_path
callit(conf_cmd, args.verbose, True, node_src_dir, env)
logger.info('.', extra=dict(continued=True))
callit([make_cmd] + make_opts, args.verbose, True, node_src_dir, env)
logger.info('.', extra=dict(continued=True))
callit([make_cmd + ' install'], args.verbose, True, node_src_dir, env)
def install_node(env_dir, src_dir, args):
"""
Download source code for node.js, unpack it
and install it in virtual environment.
"""
try:
install_node_wrapped(env_dir, src_dir, args)
except BaseException:
# this restores the newline suppressed by continued=True
logger.info('')
raise
def install_node_wrapped(env_dir, src_dir, args):
env_dir = abspath(env_dir)
node_src_dir = join(src_dir, to_utf8('node-v%s' % args.node))
src_type = "prebuilt" if args.prebuilt else "source"
logger.info(' * Install %s node (%s) ' % (src_type, args.node),
extra=dict(continued=True))
if args.prebuilt:
node_url = get_node_bin_url(args.node)
else:
node_url = get_node_src_url(args.node)
# get src if not downloaded yet
if not os.path.exists(node_src_dir):
try:
download_node_src(node_url, src_dir, args)
except urllib2.HTTPError:
if "arm64" in node_url:
# if arm64 not found, try x64
download_node_src(node_url.replace('arm64', 'x64'),
src_dir, args)
else:
logger.warning('Failed to download from %s' % node_url)
logger.info('.', extra=dict(continued=True))
if args.prebuilt:
copy_node_from_prebuilt(env_dir, src_dir, args.node)
else:
build_node_from_src(env_dir, src_dir, node_src_dir, args)
logger.info(' done.')
def install_npm(env_dir, _src_dir, args):
"""
Download source code for npm, unpack it
and install it in virtual environment.
"""
logger.info(' * Install npm.js (%s) ... ' % args.npm,
extra=dict(continued=True))
env = dict(
os.environ,
clean='no' if args.no_npm_clean else 'yes',
npm_install=args.npm,
)
proc = subprocess.Popen(
(
'bash', '-c',
'. {0} && npm install -g npm@{1}'.format(
pipes.quote(join(env_dir, 'bin', 'activate')),
args.npm,
)
),
env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
out, _ = proc.communicate()
if args.verbose:
logger.info(out)
logger.info('done.')
def install_npm_win(env_dir, src_dir, args):
"""
Download source code for npm, unpack it
and install it in virtual environment.
"""
logger.info(' * Install npm.js (%s) ... ' % args.npm,
extra=dict(continued=True))
npm_url = 'https://github.com/npm/cli/archive/v%s.zip' % args.npm
npm_contents = io.BytesIO(urlopen(npm_url).read())
bin_path = join(env_dir, 'Scripts')
node_modules_path = join(bin_path, 'node_modules', 'npm')
if os.path.exists(node_modules_path):
shutil.rmtree(node_modules_path)
if os.path.exists(join(bin_path, 'npm.cmd')):
os.remove(join(bin_path, 'npm.cmd'))
if os.path.exists(join(bin_path, 'npm-cli.js')):
os.remove(join(bin_path, 'npm-cli.js'))
with zipfile.ZipFile(npm_contents, 'r') as zipf:
zipf.extractall(src_dir)
npm_ver = 'cli-%s' % args.npm
shutil.copytree(join(src_dir, npm_ver), node_modules_path)
shutil.copy(join(src_dir, npm_ver, 'bin', 'npm.cmd'),
join(bin_path, 'npm.cmd'))
shutil.copy(join(src_dir, npm_ver, 'bin', 'npm-cli.js'),
join(bin_path, 'npm-cli.js'))
if is_CYGWIN:
shutil.copy(join(bin_path, 'npm-cli.js'),
join(env_dir, 'bin', 'npm-cli.js'))
shutil.copytree(join(bin_path, 'node_modules'),
join(env_dir, 'bin', 'node_modules'))
npm_gh_url = 'https://raw.githubusercontent.com/npm/cli'
npm_bin_url = '{}/{}/bin/npm'.format(npm_gh_url, args.npm)
writefile(join(env_dir, 'bin', 'npm'), urlopen(npm_bin_url).read())
def install_packages(env_dir, args):
"""
Install node.js packages via npm
"""
logger.info(' * Install node.js packages ... ',
extra=dict(continued=True))
packages = [package.strip() for package in
open(args.requirements).readlines()]
activate_path = join(env_dir, 'bin', 'activate')
real_npm_ver = args.npm if args.npm.count(".") == 2 else args.npm + ".0"
if args.npm == "latest" or real_npm_ver >= "1.0.0":
cmd = '. ' + pipes.quote(activate_path) + \
' && npm install -g %(pack)s'
else:
cmd = '. ' + pipes.quote(activate_path) + \
' && npm install %(pack)s' + \
' && npm activate %(pack)s'
for package in packages:
if not package:
continue
callit(cmd=[
cmd % {"pack": package}], show_stdout=args.verbose, in_shell=True)
logger.info('done.')
def install_activate(env_dir, args):
"""
Install virtual environment activation script
"""
if is_WIN:
files = {
'activate.bat': ACTIVATE_BAT,
"deactivate.bat": DEACTIVATE_BAT,
"Activate.ps1": ACTIVATE_PS1
}
bin_dir = join(env_dir, 'Scripts')
shim_node = join(bin_dir, "node.exe")
shim_nodejs = join(bin_dir, "nodejs.exe")
else:
files = {
'activate': ACTIVATE_SH,
'activate.fish': ACTIVATE_FISH,
'shim': SHIM
}
bin_dir = join(env_dir, 'bin')
shim_node = join(bin_dir, "node")
shim_nodejs = join(bin_dir, "nodejs")
if is_CYGWIN:
mkdir(bin_dir)
if args.node == "system":
files["node"] = SHIM
mod_dir = join('lib', 'node_modules')
prompt = args.prompt or '(%s)' % os.path.basename(os.path.abspath(env_dir))
if args.node == "system":
env = os.environ.copy()
env.update({'PATH': remove_env_bin_from_path(env['PATH'], bin_dir)})
for candidate in ("nodejs", "node"):
which_node_output, _ = subprocess.Popen(
["which", candidate],
stdout=subprocess.PIPE, env=env).communicate()
shim_node = clear_output(which_node_output)
if shim_node:
break
assert shim_node, "Did not find nodejs or node system executable"
for name, content in files.items():
file_path = join(bin_dir, name)
content = content.replace('__NODE_VIRTUAL_PROMPT__', prompt)
content = content.replace('__NODE_VIRTUAL_ENV__',
os.path.abspath(env_dir))
content = content.replace('__SHIM_NODE__', shim_node)
content = content.replace('__BIN_NAME__', os.path.basename(bin_dir))
content = content.replace('__MOD_NAME__', mod_dir)
if is_CYGWIN:
_, cyg_bin_dir = callit(
['cygpath', '-w', os.path.abspath(bin_dir)],
show_stdout=False, in_shell=False)
content = content.replace('__NPM_CONFIG_PREFIX__', cyg_bin_dir[0])
else:
content = content.replace('__NPM_CONFIG_PREFIX__',
'$NODE_VIRTUAL_ENV')
# if we call in the same environment:
# $ nodeenv -p --prebuilt
# $ nodeenv -p --node=system
# we should get `bin/node` not as binary+string.
# `bin/activate` should be appended if we're inside
# existing python's virtual environment
need_append = False
if args.python_virtualenv:
disable_prompt = DISABLE_PROMPT.get(name, '')
enable_prompt = ENABLE_PROMPT.get(name, '')
content = disable_prompt + content + enable_prompt
need_append = bool(disable_prompt)
writefile(file_path, content, append=need_append)
if not os.path.exists(shim_nodejs):
if is_WIN:
try:
callit(['mklink', shim_nodejs, 'node.exe'], True, True)
except OSError:
logger.error('Error: Failed to create nodejs.exe link')
else:
os.symlink("node", shim_nodejs)
def set_predeactivate_hook(env_dir):
if not is_WIN:
with open(join(env_dir, 'bin', 'predeactivate'), 'a') as hook:
hook.write(PREDEACTIVATE_SH)
def create_environment(env_dir, args):
"""
Creates a new environment in ``env_dir``.
"""
if os.path.exists(env_dir) and not args.python_virtualenv:
logger.info(' * Environment already exists: %s', env_dir)
if not args.force:
sys.exit(2)
src_dir = to_utf8(abspath(join(env_dir, 'src')))
mkdir(src_dir)
if args.node != "system":
install_node(env_dir, src_dir, args)
else:
mkdir(join(env_dir, 'bin'))
mkdir(join(env_dir, 'lib'))
mkdir(join(env_dir, 'lib', 'node_modules'))
# activate script install must be
# before npm install, npm use activate
# for install
install_activate(env_dir, args)
if node_version_from_args(args) < parse_version("0.6.3") or args.with_npm:
instfunc = install_npm_win if is_WIN or is_CYGWIN else install_npm
instfunc(env_dir, src_dir, args)
if args.requirements:
install_packages(env_dir, args)
if args.python_virtualenv:
set_predeactivate_hook(env_dir)
# Cleanup
if args.clean_src:
shutil.rmtree(src_dir)
def _get_versions_json():
response = urlopen('%s/index.json' % src_base_url)
return json.loads(response.read().decode('UTF-8'))
def get_node_versions():
return [dct['version'].lstrip('v') for dct in _get_versions_json()][::-1]
def print_node_versions():
"""
Prints into stdout all available node.js versions
"""
versions = get_node_versions()
chunks_of_8 = [
versions[pos:pos + 8] for pos in range(0, len(versions), 8)
]
for chunk in chunks_of_8:
logger.info('\t'.join(chunk))
def get_last_stable_node_version():
"""
Return last stable node.js version
"""
return _get_versions_json()[0]['version'].lstrip('v')
def get_last_lts_node_version():
"""
Return the last node.js version marked as LTS
"""
return next((v['version'].lstrip('v')
for v in _get_versions_json() if v['lts']), None)
def get_env_dir(args):
if args.python_virtualenv:
if hasattr(sys, 'real_prefix'):
res = sys.prefix
elif hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix:
res = sys.prefix
elif 'CONDA_PREFIX' in os.environ:
res = sys.prefix
else:
logger.error('No python virtualenv is available')
sys.exit(2)
else:
res = args.env_dir
return to_utf8(res)
# noinspection PyProtectedMember
def main():
"""
Entry point
"""
# quick&dirty way to help update the README
if "--dump-config-defaults" in sys.argv:
Config._dump()
return
args = parse_args(check=False)
# noinspection PyProtectedMember
Config._load(args.config_file, args.verbose)
args = parse_args()
if args.node.lower() == 'system' and is_WIN:
logger.error('Installing system node.js on win32 is not supported!')
exit(1)
global src_base_url
global ignore_ssl_certs
ignore_ssl_certs = args.ignore_ssl_certs
src_domain = None
if args.mirror:
if '://' in args.mirror:
src_base_url = args.mirror
else:
src_domain = args.mirror
# use unofficial builds only if musl and no explicitly chosen mirror
elif is_x86_64_musl() or is_riscv64():
src_domain = 'unofficial-builds.nodejs.org'
else:
src_domain = 'nodejs.org'
if src_base_url is None:
src_base_url = 'https://%s/download/release' % src_domain
if not args.node or args.node.lower() == 'latest':
args.node = get_last_stable_node_version()
elif args.node.lower() == 'lts':
args.node = get_last_lts_node_version()
if args.list:
print_node_versions()
elif args.update:
env_dir = get_env_dir(args)
install_packages(env_dir, args)
else:
env_dir = get_env_dir(args)
create_environment(env_dir, args)
# ---------------------------------------------------------
# Shell scripts content
DISABLE_PROMPT = {
'activate': """
# disable nodeenv's prompt
# (prompt already changed by original virtualenv's script)
# https://github.com/ekalinin/nodeenv/issues/26
NODE_VIRTUAL_ENV_DISABLE_PROMPT=1
""",
'activate.fish': """
# disable nodeenv's prompt
# (prompt already changed by original virtualenv's script)
# https://github.com/ekalinin/nodeenv/issues/26
set NODE_VIRTUAL_ENV_DISABLE_PROMPT 1
""",
}
ENABLE_PROMPT = {
'activate': """
unset NODE_VIRTUAL_ENV_DISABLE_PROMPT
""",
'activate.fish': """
set -e NODE_VIRTUAL_ENV_DISABLE_PROMPT
""",
}
SHIM = """#!/usr/bin/env bash
export NODE_PATH='__NODE_VIRTUAL_ENV__/lib/node_modules'
export NPM_CONFIG_PREFIX='__NODE_VIRTUAL_ENV__'
export npm_config_prefix='__NODE_VIRTUAL_ENV__'
exec '__SHIM_NODE__' "$@"
"""
ACTIVATE_BAT = r"""
@echo off
set "NODE_VIRTUAL_ENV=__NODE_VIRTUAL_ENV__"
if not defined PROMPT (
set "PROMPT=$P$G"
)
if defined _OLD_VIRTUAL_PROMPT (
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
)
if defined _OLD_VIRTUAL_NODE_PATH (
set "NODE_PATH=%_OLD_VIRTUAL_NODE_PATH%"
)
set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
set "PROMPT=__NODE_VIRTUAL_PROMPT__ %PROMPT%"
if defined NODE_PATH (
set "_OLD_VIRTUAL_NODE_PATH=%NODE_PATH%"
set NODE_PATH=
)
if defined _OLD_VIRTUAL_PATH (
set "PATH=%_OLD_VIRTUAL_PATH%"
) else (
set "_OLD_VIRTUAL_PATH=%PATH%"
)
set "PATH=%NODE_VIRTUAL_ENV%\Scripts;%PATH%"
:END
"""
DEACTIVATE_BAT = """\
@echo off
if defined _OLD_VIRTUAL_PROMPT (
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
)
set _OLD_VIRTUAL_PROMPT=
if defined _OLD_VIRTUAL_NODE_PATH (
set "NODE_PATH=%_OLD_VIRTUAL_NODE_PATH%"
set _OLD_VIRTUAL_NODE_PATH=
)
if defined _OLD_VIRTUAL_PATH (
set "PATH=%_OLD_VIRTUAL_PATH%"
)
set _OLD_VIRTUAL_PATH=
set NODE_VIRTUAL_ENV=
:END
"""
ACTIVATE_PS1 = r"""
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
copy-item function:_OLD_VIRTUAL_PROMPT function:prompt
remove-item function:_OLD_VIRTUAL_PROMPT
}
if (Test-Path env:_OLD_VIRTUAL_NODE_PATH) {
copy-item env:_OLD_VIRTUAL_NODE_PATH env:NODE_PATH
remove-item env:_OLD_VIRTUAL_NODE_PATH
}
if (Test-Path env:_OLD_VIRTUAL_PATH) {
copy-item env:_OLD_VIRTUAL_PATH env:PATH
remove-item env:_OLD_VIRTUAL_PATH
}
if (Test-Path env:NODE_VIRTUAL_ENV) {
remove-item env:NODE_VIRTUAL_ENV
}
if (!$NonDestructive) {
# Self destruct!
remove-item function:deactivate
}
}
deactivate -nondestructive
$env:NODE_VIRTUAL_ENV="__NODE_VIRTUAL_ENV__"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT {""}
copy-item function:prompt function:_OLD_VIRTUAL_PROMPT
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green '__NODE_VIRTUAL_PROMPT__ '
_OLD_VIRTUAL_PROMPT
}
# Clear NODE_PATH
if (Test-Path env:NODE_PATH) {
copy-item env:NODE_PATH env:_OLD_VIRTUAL_NODE_PATH
remove-item env:NODE_PATH
}
# Add the venv to the PATH
copy-item env:PATH env:_OLD_VIRTUAL_PATH
$env:PATH = "$env:NODE_VIRTUAL_ENV\Scripts;$env:PATH"
"""
ACTIVATE_SH = r"""
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate_node () {
# reset old environment variables
if [ -n "$_OLD_NODE_VIRTUAL_PATH" ] ; then
PATH="$_OLD_NODE_VIRTUAL_PATH"
export PATH
unset _OLD_NODE_VIRTUAL_PATH
NODE_PATH="$_OLD_NODE_PATH"
export NODE_PATH
unset _OLD_NODE_PATH
NPM_CONFIG_PREFIX="$_OLD_NPM_CONFIG_PREFIX"
npm_config_prefix="$_OLD_npm_config_prefix"
export NPM_CONFIG_PREFIX
export npm_config_prefix
unset _OLD_NPM_CONFIG_PREFIX
unset _OLD_npm_config_prefix
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
hash -r
fi
if [ -n "$_OLD_NODE_VIRTUAL_PS1" ] ; then
PS1="$_OLD_NODE_VIRTUAL_PS1"
export PS1
unset _OLD_NODE_VIRTUAL_PS1
fi
unset NODE_VIRTUAL_ENV
if [ ! "$1" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate_node
fi
}
freeze () {
local NPM_VER=`npm -v | cut -d '.' -f 1`
local re="[a-zA-Z0-9\.\-]+@[0-9]+\.[0-9]+\.[0-9]+([\+\-][a-zA-Z0-9\.\-]+)*"
if [ "$NPM_VER" = '0' ]; then
NPM_LIST=`npm list installed active 2>/dev/null | \
cut -d ' ' -f 1 | grep -v npm`
else
local npmls="npm ls -g"
if [ "$1" = "-l" ]; then
npmls="npm ls"
shift
fi
NPM_LIST=$(eval ${npmls} | grep -E '^.{4}\w{1}'| \
grep -o -E "$re"| grep -v npm)
fi
if [ -z "$@" ]; then
echo "$NPM_LIST"
else
echo "$NPM_LIST" > $@
fi
}
# unset irrelevant variables
deactivate_node nondestructive
# find the directory of this script
# http://stackoverflow.com/a/246128
if [ "${BASH_SOURCE}" ] ; then
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
DIR="$( command cd -P "$( dirname "$SOURCE" )" > /dev/null && pwd )"
NODE_VIRTUAL_ENV="$(dirname "$DIR")"
else
# dash not movable. fix use case:
# dash -c " . node-env/bin/activate && node -v"
NODE_VIRTUAL_ENV="__NODE_VIRTUAL_ENV__"
fi
# NODE_VIRTUAL_ENV is the parent of the directory where this script is
export NODE_VIRTUAL_ENV
_OLD_NODE_VIRTUAL_PATH="$PATH"
PATH="$NODE_VIRTUAL_ENV/lib/node_modules/.bin:$NODE_VIRTUAL_ENV/__BIN_NAME__:$PATH"
export PATH
_OLD_NODE_PATH="$NODE_PATH"
NODE_PATH="$NODE_VIRTUAL_ENV/__MOD_NAME__"
export NODE_PATH
_OLD_NPM_CONFIG_PREFIX="$NPM_CONFIG_PREFIX"
_OLD_npm_config_prefix="$npm_config_prefix"
NPM_CONFIG_PREFIX="__NPM_CONFIG_PREFIX__"
npm_config_prefix="__NPM_CONFIG_PREFIX__"
export NPM_CONFIG_PREFIX
export npm_config_prefix
if [ -z "$NODE_VIRTUAL_ENV_DISABLE_PROMPT" ] ; then
_OLD_NODE_VIRTUAL_PS1="$PS1"
if [ "x__NODE_VIRTUAL_PROMPT__" != x ] ; then
PS1="__NODE_VIRTUAL_PROMPT__ $PS1"
else
if [ "`basename \"$NODE_VIRTUAL_ENV\"`" = "__" ] ; then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
PS1="[`basename \`dirname \"$NODE_VIRTUAL_ENV\"\``] $PS1"
else
PS1="(`basename \"$NODE_VIRTUAL_ENV\"`) $PS1"
fi
fi
export PS1
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
hash -r
fi
"""
ACTIVATE_FISH = """
# This file must be used with "source bin/activate.fish" *from fish*
# you cannot run it directly
function deactivate_node -d 'Exit nodeenv and return to normal environment.'
# reset old environment variables
if test -n "$_OLD_NODE_VIRTUAL_PATH"
set -gx PATH $_OLD_NODE_VIRTUAL_PATH
set -e _OLD_NODE_VIRTUAL_PATH
end
if test -n "$_OLD_NODE_PATH"
set -gx NODE_PATH $_OLD_NODE_PATH
set -e _OLD_NODE_PATH
else
set -e NODE_PATH
end
if test -n "$_OLD_NPM_CONFIG_PREFIX"
set -gx NPM_CONFIG_PREFIX $_OLD_NPM_CONFIG_PREFIX
set -e _OLD_NPM_CONFIG_PREFIX
else
set -e NPM_CONFIG_PREFIX
end
if test -n "$_OLD_npm_config_prefix"
set -gx npm_config_prefix $_OLD_npm_config_prefix
set -e _OLD_npm_config_prefix
else
set -e npm_config_prefix
end
if test -n "$_OLD_NODE_FISH_PROMPT_OVERRIDE"
# Set an empty local `$fish_function_path` to allow the removal of
# `fish_prompt` using `functions -e`.
set -l fish_function_path
# Prevents error when using nested fish instances
if functions -q _node_old_fish_prompt
# Erase virtualenv's `fish_prompt` and restore the original.
functions -e fish_prompt
functions -c _node_old_fish_prompt fish_prompt
functions -e _node_old_fish_prompt
end
set -e _OLD_NODE_FISH_PROMPT_OVERRIDE
end
set -e NODE_VIRTUAL_ENV
if test (count $argv) = 0 -o "$argv[1]" != "nondestructive"
# Self destruct!
functions -e deactivate_node
end
end
function freeze -d 'Show a list of installed packages - like `pip freeze`'
set -l NPM_VER (npm -v | cut -d '.' -f 1)
set -l RE "[a-zA-Z0-9\\.\\-]+@[0-9]+\\.[0-9]+\\.[0-9]+([\\+\\-][a-zA-Z0-9\\.\\-]+)*"
if test "$NPM_VER" = "0"
set -g NPM_LIST (npm list installed active >/dev/null ^/dev/null | \
cut -d ' ' -f 1 | grep -v npm)
else
set -l NPM_LS "npm ls -g"
if test (count $argv) -gt 0 -a "$argv[1]" = "-l"
set NPM_LS "npm ls"
set -e argv[1]
end
set -l NPM_LIST (eval $NPM_LS | grep -E '^.{4}\\w{1}' | \
grep -o -E "$re" | \
grep -v npm)
end
if test (count $argv) = 0
echo $NPM_LIST
else
echo $NPM_LIST > $argv[1]
end
end
# unset irrelevant variables
deactivate_node nondestructive
# find the directory of this script
begin
set -l SOURCE (status filename)
while test -L "$SOURCE"
set SOURCE (readlink "$SOURCE")
end
set -l DIR (dirname (realpath "$SOURCE"))
# NODE_VIRTUAL_ENV is the parent of the directory where this script is
set -gx NODE_VIRTUAL_ENV (dirname "$DIR")
end
set -gx _OLD_NODE_VIRTUAL_PATH $PATH
# The node_modules/.bin path doesn't exists and it will print a warning, and
# that's why we redirect stderr to /dev/null :)
set -gx PATH "$NODE_VIRTUAL_ENV/lib/node_modules/.bin" "$NODE_VIRTUAL_ENV/__BIN_NAME__" $PATH ^/dev/null
if set -q NODE_PATH
set -gx _OLD_NODE_PATH $NODE_PATH
set -gx NODE_PATH "$NODE_VIRTUAL_ENV/__MOD_NAME__" $NODE_PATH
else
set -gx NODE_PATH "$NODE_VIRTUAL_ENV/__MOD_NAME__"
end
if set -q NPM_CONFIG_PREFIX
set -gx _OLD_NPM_CONFIG_PREFIX $NPM_CONFIG_PREFIX
end
set -gx NPM_CONFIG_PREFIX "__NPM_CONFIG_PREFIX__"
if set -q npm_config_prefix
set -gx _OLD_npm_config_prefix $npm_config_prefix
end
set -gx npm_config_prefix "__NPM_CONFIG_PREFIX__"
if test -z "$NODE_VIRTUAL_ENV_DISABLE_PROMPT"
# Copy the current `fish_prompt` function as `_node_old_fish_prompt`.
functions -c fish_prompt _node_old_fish_prompt
function fish_prompt
# Save the current $status, for fish_prompts that display it.
set -l old_status $status
# Prompt override provided?
# If not, just prepend the environment name.
if test -n "__NODE_VIRTUAL_PROMPT__"
printf '%s%s ' "__NODE_VIRTUAL_PROMPT__" (set_color normal)
else
printf '%s(%s) ' (set_color normal) (basename "$NODE_VIRTUAL_ENV")
end
# Restore the original $status
echo "exit $old_status" | source
_node_old_fish_prompt
end
set -gx _OLD_NODE_FISH_PROMPT_OVERRIDE "$NODE_VIRTUAL_ENV"
end
""" # noqa: E501
PREDEACTIVATE_SH = """
if type -p deactivate_node > /dev/null; then deactivate_node;fi
"""
CYGWIN_NODE = """#!/bin/sh
if [ -r "$1" ]; then
SCRIPT_PATH=$(cygpath -w "$1")
shift
set - $SCRIPT_PATH $@
unset SCRIPT_PATH
fi
exec $(dirname "$0")/node.exe "$@"
"""
if __name__ == '__main__':
main()