Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Download musl-compatible nodeenv from unofficial-builds #247

Merged
merged 2 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 19 additions & 86 deletions nodeenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import contextlib
import io
import json
import sys
import os
import re
Expand All @@ -25,18 +26,17 @@
import platform
import zipfile
import shutil
import sysconfig
import glob

try: # pragma: no cover (py2 only)
from ConfigParser import SafeConfigParser as ConfigParser
# noinspection PyCompatibility
from HTMLParser import HTMLParser
import urllib2
iteritems = operator.methodcaller('iteritems')
except ImportError: # pragma: no cover (py3 only)
from configparser import ConfigParser
# noinspection PyUnresolvedReferences
from html.parser import HTMLParser
import urllib.request as urllib2
iteritems = operator.methodcaller('items')

Expand All @@ -50,9 +50,6 @@
src_domain = "nodejs.org"

is_PY3 = sys.version_info[0] >= 3
if is_PY3:
from functools import cmp_to_key

is_WIN = platform.system() == 'Windows'
is_CYGWIN = platform.system().startswith('CYGWIN')

Expand Down Expand Up @@ -500,9 +497,13 @@ def callit(cmd, show_stdout=True, in_shell=False,

def get_root_url(version):
if parse_version(version) > parse_version("0.5.0"):
return 'https://%s/dist/v%s/' % (src_domain, version)
return 'https://%s/download/release/v%s/' % (src_domain, version)
else:
return 'https://%s/dist/' % src_domain
return 'https://%s/download/release/' % src_domain


def is_x86_64_musl():
return sysconfig.get_config_var('HOST_GNU_TYPE') == 'x86_64-pc-linux-musl'


def get_node_bin_url(version):
Expand All @@ -522,10 +523,11 @@ def get_node_bin_url(version):
}
if is_WIN or is_CYGWIN:
postfix = '-win-%(arch)s.zip' % sysinfo
filename = '%s-v%s%s' % (get_binary_prefix(), version, postfix)
elif is_x86_64_musl():
postfix = '-linux-x64-musl.tar.gz'
else:
postfix = '-%(system)s-%(arch)s.tar.gz' % sysinfo
filename = '%s-v%s%s' % (get_binary_prefix(), version, postfix)
filename = '%s-v%s%s' % (get_binary_prefix(), version, postfix)
return get_root_url(version) + filename


Expand Down Expand Up @@ -954,59 +956,13 @@ def create_environment(env_dir, opt):
shutil.rmtree(src_dir)


class GetsAHrefs(HTMLParser):
def __init__(self):
# Old style class in py2 :(
HTMLParser.__init__(self)
self.hrefs = []

def handle_starttag(self, tag, attrs):
if tag == 'a':
self.hrefs.append(dict(attrs).get('href', ''))


VERSION_RE = re.compile(r'\d+\.\d+\.\d+')


def _py2_cmp(a, b):
# -1 = a < b, 0 = eq, 1 = a > b
return (a > b) - (a < b)


def compare_versions(version, other_version):
version_tuple = version.split('.')
other_tuple = other_version.split('.')

version_length = len(version_tuple)
other_length = len(other_tuple)
version_dots = min(version_length, other_length)

for i in range(version_dots):
a = int(version_tuple[i])
b = int(other_tuple[i])
cmp_value = _py2_cmp(a, b)
if cmp_value != 0:
return cmp_value

return _py2_cmp(version_length, other_length)
def _get_versions_json():
response = urlopen('https://%s/download/release/index.json' % src_domain)
return json.loads(response.read().decode('UTF-8'))


def get_node_versions():
response = urlopen('https://{0}/dist'.format(src_domain))
href_parser = GetsAHrefs()
href_parser.feed(response.read().decode('UTF-8'))

versions = set(
VERSION_RE.search(href).group()
for href in href_parser.hrefs
if VERSION_RE.search(href)
)
if is_PY3:
key_compare = cmp_to_key(compare_versions)
versions = sorted(versions, key=key_compare)
else:
versions = sorted(versions, cmp=compare_versions)
return versions
return [dct['version'].lstrip('v') for dct in _get_versions_json()][::-1]


def print_node_versions():
Expand All @@ -1025,23 +981,7 @@ def get_last_stable_node_version():
"""
Return last stable node.js version
"""
response = urlopen('https://%s/dist/latest/' % src_domain)
href_parser = GetsAHrefs()
href_parser.feed(response.read().decode('UTF-8'))

links = []
pattern = re.compile(r'''%s-v([0-9]+)\.([0-9]+)\.([0-9]+)\.tar\.gz''' % (
get_binary_prefix()))

for href in href_parser.hrefs:
match = pattern.match(href)
if match:
version = u'.'.join(match.groups())
major, minor, revision = map(int, match.groups())
links.append((version, major, minor, revision))
break

return links[-1][0]
return _get_versions_json()[0]['version'].lstrip('v')


def get_env_dir(opt, args):
Expand All @@ -1060,16 +1000,6 @@ def get_env_dir(opt, args):
return to_utf8(res)


def is_installed(name):
try:
devnull = open(os.devnull)
subprocess.Popen([name], stdout=devnull, stderr=devnull)
except OSError as e:
if e.errno == os.errno.ENOENT:
return False
return True


# noinspection PyProtectedMember
def main():
"""
Expand Down Expand Up @@ -1099,6 +1029,9 @@ def main():

if opt.mirror:
src_domain = opt.mirror
# use unofficial builds only if musl and no explicitly chosen mirror
elif is_x86_64_musl():
src_domain = 'unofficial-builds.nodejs.org'

if not opt.node or opt.node.lower() == "latest":
opt.node = get_last_stable_node_version()
Expand Down
24 changes: 0 additions & 24 deletions tests/iojs.htm

This file was deleted.

15 changes: 0 additions & 15 deletions tests/iojs_dist.htm

This file was deleted.

107 changes: 16 additions & 91 deletions tests/nodeenv_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import absolute_import
from __future__ import unicode_literals

import io
import os.path
import subprocess

Expand All @@ -14,60 +13,6 @@
HERE = os.path.abspath(os.path.dirname(__file__))


def test_compare_versions():
assert nodeenv.compare_versions('1', '2') == -1
assert nodeenv.compare_versions('1', '2') == -1
assert nodeenv.compare_versions('0.1', '0.2') == -1
assert nodeenv.compare_versions('0.9', '0.10') == -1
assert nodeenv.compare_versions('0.2', '0.2.1') == -1
assert nodeenv.compare_versions('0.2.1', '0.2.10') == -1
assert nodeenv.compare_versions('0.2.9', '0.2.10') == -1
assert nodeenv.compare_versions('0.2.1', '0.3') == -1


def test_gets_a_hrefs_trivial():
parser = nodeenv.GetsAHrefs()
parser.feed('')
assert parser.hrefs == []


def test_gets_a_hrefs_nodejs_org():
# Retrieved 2015-01-15
contents = io.open(os.path.join(HERE, 'nodejs.htm')).read()
parser = nodeenv.GetsAHrefs()
parser.feed(contents)
# Smoke test
assert parser.hrefs == [
'../', 'docs/', 'x64/', 'SHASUMS.txt', 'SHASUMS.txt.asc',
'SHASUMS.txt.gpg', 'SHASUMS256.txt', 'SHASUMS256.txt.asc',
'SHASUMS256.txt.gpg', 'node-v0.10.35-darwin-x64.tar.gz',
'node-v0.10.35-darwin-x86.tar.gz', 'node-v0.10.35-linux-x64.tar.gz',
'node-v0.10.35-linux-x86.tar.gz', 'node-v0.10.35-sunos-x64.tar.gz',
'node-v0.10.35-sunos-x86.tar.gz', 'node-v0.10.35-x86.msi',
'node-v0.10.35.pkg', 'node-v0.10.35.tar.gz', 'node.exe',
'node.exp', 'node.lib', 'node.pdb', 'openssl-cli.exe',
'openssl-cli.pdb',
]


def test_gets_a_hrefs_iojs_org():
# Retrieved 2015-01-15
contents = io.open(os.path.join(HERE, 'iojs.htm')).read()
parser = nodeenv.GetsAHrefs()
parser.feed(contents)
# Smoke test
assert parser.hrefs == [
'../', 'doc/', 'win-x64/', 'win-x86/', 'SHASUMS256.txt',
'SHASUMS256.txt.asc', 'SHASUMS256.txt.gpg',
'iojs-v1.0.1-darwin-x64.tar.gz', 'iojs-v1.0.1-linux-armv7l.tar.gz',
'iojs-v1.0.1-linux-armv7l.tar.xz', 'iojs-v1.0.1-linux-x64.tar.gz',
'iojs-v1.0.1-linux-x64.tar.xz', 'iojs-v1.0.1-linux-x86.tar.gz',
'iojs-v1.0.1-linux-x86.tar.xz', 'iojs-v1.0.1-x64.msi',
'iojs-v1.0.1-x86.msi', 'iojs-v1.0.1.pkg', 'iojs-v1.0.1.tar.gz',
'iojs-v1.0.1.tar.xz',
]


@pytest.mark.integration
def test_smoke(tmpdir):
nenv_path = tmpdir.join('nenv').strpath
Expand All @@ -83,16 +28,10 @@ def test_smoke(tmpdir):


@pytest.yield_fixture
def returns_iojs_dist():
with io.open(os.path.join(HERE, 'iojs_dist.htm'), 'rb') as iojs_dist:
with mock.patch.object(nodeenv, 'urlopen', return_value=iojs_dist):
yield


@pytest.yield_fixture
def returns_nodejs_dist():
with io.open(os.path.join(HERE, 'nodejs_dist.htm'), 'rb') as node_dist:
with mock.patch.object(nodeenv, 'urlopen', return_value=node_dist):
def mock_index_json():
# retrieved 2019-12-31
with open(os.path.join(HERE, 'nodejs_index.json'), 'rb') as f:
with mock.patch.object(nodeenv, 'urlopen', return_value=f):
yield


Expand All @@ -106,41 +45,27 @@ def mck_to_out(mck):
return '\n'.join(call[0][0] for call in mck.call_args_list)


@pytest.mark.usefixtures('returns_iojs_dist')
def test_get_node_versions_iojs():
@pytest.mark.usefixtures('mock_index_json')
def test_get_node_versions():
versions = nodeenv.get_node_versions()
assert versions == ['1.0.0', '1.0.1', '1.0.2', '1.0.3', '1.0.4', '1.1.0']


@pytest.mark.usefixtures('returns_nodejs_dist')
def test_get_node_versions_nodejs():
versions = nodeenv.get_node_versions()
# There's a lot of versions here, let's just do some sanity assertions
assert len(versions) == 227
assert versions[0:3] == ['0.0.1', '0.0.2', '0.0.3']
assert versions[-3:] == ['0.11.15', '0.11.16', '0.12.0']


@pytest.mark.usefixtures('returns_iojs_dist')
def test_print_node_versions_iojs(cap_logging_info):
nodeenv.print_node_versions()
printed = mck_to_out(cap_logging_info)
assert printed == '1.0.0\t1.0.1\t1.0.2\t1.0.3\t1.0.4\t1.1.0'
# there are a lot of versions, just some sanity checks here
assert len(versions) == 485
assert versions[:3] == ['0.1.14', '0.1.15', '0.1.16']
assert versions[-3:] == ['13.3.0', '13.4.0', '13.5.0']


@pytest.mark.usefixtures('returns_nodejs_dist')
def test_print_node_versions_node(cap_logging_info):
@pytest.mark.usefixtures('mock_index_json')
def test_print_node_versions(cap_logging_info):
nodeenv.print_node_versions()
printed = mck_to_out(cap_logging_info)
# There's a lot of output here, let's just assert a few things
assert printed.startswith(
'0.0.1\t0.0.2\t0.0.3\t0.0.4\t0.0.5\t0.0.6\t0.1.0\t0.1.1\n'
'0.1.14\t0.1.15\t0.1.16\t0.1.17\t0.1.18\t0.1.19\t0.1.20\t0.1.21\n'
)
assert printed.endswith('\n0.11.15\t0.11.16\t0.12.0')
assert printed.endswith('\n13.1.0\t13.2.0\t13.3.0\t13.4.0\t13.5.0')
tabs_per_line = [line.count('\t') for line in printed.splitlines()]
# 8 items per line = 7 tabs
# The last line contains the remaning 3 items
assert tabs_per_line == [7] * 28 + [2]
# The last line contains the remaning 5 items
assert tabs_per_line == [7] * 60 + [4]


def test_predeactivate_hook(tmpdir):
Expand Down
29 changes: 0 additions & 29 deletions tests/nodejs.htm

This file was deleted.

Loading