blob: 88d4f712f35848a729b619a6443f093cf71305e8 [file] [log] [blame]
# Copyright 2021 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
from __future__ import annotations
import contextlib
from recipe_engine import recipe_api
class NodeJSApi(recipe_api.RecipeApi):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._installed = {} # {Path: installed nodejs version there}
@contextlib.contextmanager
def __call__(self, version, path=None, cache=None):
"""Installs a Node.js toolchain and activates it in the environment.
Installs it under the given `path`, defaulting to `[CACHE]/nodejs`. Various
cache directories used by npm are placed under `cache`, defaulting to
`[CACHE]/npmcache`.
`version` will be used to construct CIPD package version for packages under
https://chrome-infra-packages.appspot.com/p/infra/3pp/tools/nodejs/.
To reuse the Node.js toolchain deployment and npm caches across builds,
declare the corresponding named caches in Buildbucket configs. E.g. when
using defaults:
luci.builder(
...
caches = [
swarming.cache("nodejs"),
swarming.cache("npmcache"),
],
)
Args:
* version (str) - a Node.js version to install (e.g. `17.1.0`).
* path (Path) - a path to install Node.js into.
* cache (Path) - a path to put Node.js caches under.
"""
path = path or self.m.path.cache_dir / 'nodejs'
cache = cache or self.m.path.cache_dir / 'npmcache'
with self.m.context(infra_steps=True):
env, env_pfx = self._ensure_installed(version, path, cache)
with self.m.context(env=env, env_prefixes=env_pfx):
yield
def _ensure_installed(self, version, path, cache):
if self._installed.get(path) != version:
pkgs = self.m.cipd.EnsureFile()
pkgs.add_package(
'infra/3pp/tools/nodejs/${platform}', _3pp_version(version))
self.m.cipd.ensure(path, pkgs)
self._installed[path] = version
env = {
# npm's content-addressed cache.
'npm_config_cache': cache / 'npm',
# Where packages are installed when using 'npm -g ...'.
'npm_config_prefix': cache / 'pfx',
# Potential workaround for b/409370331.
'UV_USE_IO_URING': '0',
}
env_prefixes = {
'PATH': [
# Putting this in front of PATH (before `bin` from the CIPD package)
# allows doing stuff like `npm install -g npm@8.1.4` and picking up
# the updated `npm` binary from `<npm_config_prefix>/bin`.
env['npm_config_prefix']
if self.m.platform.is_win else env['npm_config_prefix'] / 'bin',
path if self.m.platform.is_win else path / 'bin',
],
}
return env, env_prefixes
def _3pp_version(version):
"""Returns 3pp CIPD package version given the nodejs version.
This is just a look up table. Everything <=v17 is "version:v2@". Everything
>=v23 is "version:v3@". In between there's a mess which we hardcode.
"""
major = int(version.split('.')[0])
if major <= 17:
return 'version:2@' + version
if major >=23:
return 'version:3@' + version
KNOWN_EPOCH2_VERSIONS = [
'18.0.0',
'18.1.0',
'18.2.0',
'18.3.0',
'18.4.0',
'18.5.0',
'18.6.0',
'18.7.0',
'18.8.0',
'18.9.0',
'18.9.1',
'18.10.0',
'18.11.0',
'18.16.0',
'18.16.1',
'18.17.0',
'18.17.1',
'18.18.0',
'18.18.1',
'18.18.2',
'18.19.0',
'18.19.1',
'18.20.0',
'18.20.1',
'18.20.2',
'19.0.0',
'19.0.1',
'19.1.0',
'19.2.0',
'19.3.0',
'19.4.0',
'19.5.0',
'19.6.0',
'19.6.1',
'19.7.0',
'19.8.0',
'19.8.1',
'19.9.0',
'20.0.0',
'20.1.0',
'20.2.0',
'20.3.0',
'20.3.1',
'20.4.0',
'20.5.0',
'20.5.1',
'20.6.0',
'20.6.1',
'20.7.0',
'20.8.0',
'20.8.1',
'20.10.0',
'20.11.0',
'20.11.1',
'20.12.0',
'20.12.1',
'20.12.2',
'20.13.0',
'20.13.1',
'20.14.0',
'21.0.0',
'21.1.0',
'21.2.0',
'21.3.0',
'21.4.0',
'21.5.0',
'21.6.0',
'21.6.1',
'21.6.2',
'21.7.0',
'21.7.1',
'21.7.2',
'21.7.3',
'22.0.0',
'22.1.0',
'22.2.0',
'22.3.0',
]
if version in KNOWN_EPOCH2_VERSIONS:
return 'version:2@' + version
return 'version:3@' + version