pre-commit/pre_commit/languages/node_env.py
2021-03-19 19:23:05 -04:00

108 lines
3.4 KiB
Python

import io
import os
import platform
import re
import sys
import sysconfig
from tarfile import TarFile
from tarfile import TarInfo
from typing import Generator
from urllib.parse import urljoin
from urllib.request import urlopen
from zipfile import ZipFile
import pre_commit.constants as C
ARCHITECTURES = {
'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
}
NIX_NODE_SUBDIRS = re.compile(r'node[^/]+/(bin|lib|include|share)')
WINDOWS_NODE_SUBDIRS = re.compile(r'node[^/]+(np|node)')
def install_node(envdir: str, language_version: str) -> str:
windows = sys.platform in ('cygwin', 'win32')
if sysconfig.get_config_var('HOST_GNU_TYPE') == 'x86_64-pc-linux-musl':
domain = 'https://unofficial-builds.nodejs.org'
suffix = 'linux-x64-musl.tar.gz'
else:
domain = 'https://nodejs.org'
arch = ARCHITECTURES[platform.machine()]
if windows:
suffix = f'win-{arch}.zip'
else:
suffix = f'{platform.system().lower()}-{arch}.tar.gz'
version = _node_version(domain, language_version)
archive_name = f'node-{version}-{suffix}'
with urlopen(
urljoin(domain, f'download/release/{version}/{archive_name}'),
) as release:
compressed = io.BytesIO(release.read())
# TODO should i just extractall + rm the extra bits instead of partially
# extracting & renaming?
if windows:
# TODO this doesn't quite work
def renamed_members(z: ZipFile) -> Generator[str, None, None]:
prefix_len = len(f'{archive_name}/')
for m in z.filelist:
if WINDOWS_NODE_SUBDIRS.match(m.filename):
m.filename = m.filename[prefix_len:]
yield m.filename
with ZipFile(compressed) as z:
z.extractall(envdir, renamed_members(z))
raise NotImplementedError('TODO')
else:
def rename_members_tf(tf: TarFile) -> Generator[TarInfo, None, None]:
prefix_len = len(f'{archive_name}/')
for m in tf.getmembers():
if NIX_NODE_SUBDIRS.match(m.name):
m.path = m.path[prefix_len:]
yield m
with TarFile.open(fileobj=compressed) as tf:
tf.extractall(envdir, rename_members_tf(tf)) # type: ignore
for file in ('npm', 'npx', 'node'):
path = os.path.join(envdir, 'bin', file)
os.chmod(path, os.stat(path).st_mode | 0o111)
os.symlink('node', os.path.join(envdir, 'bin', 'nodejs'))
return os.path.join(envdir, 'bin', 'npm')
def _node_version(domain: str, language_version: str) -> str:
"""Looks up the node.js version based on the configured
version (uses the latest by default)"""
if language_version == C.DEFAULT:
# grab latest
with urlopen(urljoin(domain, 'download/release/index.tab')) as index:
_ = next(index) # header
version = next(index).split()[0].decode()
else:
version = f'v{language_version.replace("v", "")}'
return version