@@ -10,20 +10,19 @@ SRC_URI = "npm://some.registry.url;OptionA=xxx;OptionB=xxx;..."
Supported SRC_URI options are:
-- package
+- dn
The npm package name. This is a mandatory parameter.
-- version
+- dv
The npm package version. This is a mandatory parameter.
- downloadfilename
Specifies the filename used when storing the downloaded file.
- destsuffix
- Specifies the directory to use to unpack the package (default: npm).
+ The name of the path in which to place the package (default: npm).
"""
-import base64
import json
import os
import re
@@ -40,6 +39,8 @@ from bb.fetch2 import check_network_access
from bb.fetch2 import runfetchcmd
from bb.utils import is_semver
+from bb.fetch2.wget import Wget
+
def npm_package(package):
"""Convert the npm package name to remove unsupported character"""
# For scoped package names ('@user/package') the '/' is replaced by a '-'.
@@ -64,14 +65,7 @@ def npm_localfile(package, version=None):
filename = package
return os.path.join("npm2", filename)
-def npm_integrity(integrity):
- """
- Get the checksum name and expected value from the subresource integrity
- https://www.w3.org/TR/SRI/
- """
- algo, value = integrity.split("-", maxsplit=1)
- return "%ssum" % algo, base64.b64decode(value).hex()
-
+# Deprecated
def npm_unpack(tarball, destdir, d):
"""Unpack a npm tarball"""
bb.utils.mkdirhier(destdir)
@@ -80,8 +74,8 @@ def npm_unpack(tarball, destdir, d):
cmd += " --delay-directory-restore"
cmd += " --strip-components=1"
runfetchcmd(cmd, d, workdir=destdir)
- runfetchcmd("chmod -R +X '%s'" % (destdir), d, quiet=True, workdir=destdir)
+# Deprecated
class NpmEnvironment(object):
"""
Using a npm config file seems more reliable than using cli arguments.
@@ -130,7 +124,15 @@ class NpmEnvironment(object):
return _run(cmd)
-class Npm(FetchMethod):
+
+def construct_url_path(name, version):
+ return f"/{name}/-/{name.split('/')[-1]}-{version}.tgz"
+
+def construct_url(registry, name, version):
+ path = construct_url_path(name, version)
+ return f"https://{registry}{path}"
+
+class Npm(Wget):
"""Class to fetch a package from a npm registry"""
def supports(self, ud, d):
@@ -139,178 +141,58 @@ class Npm(FetchMethod):
def urldata_init(self, ud, d):
"""Init npm specific variables within url data"""
- ud.package = None
- ud.version = None
- ud.registry = None
- # Get the 'package' parameter
if "package" in ud.parm:
- ud.package = ud.parm.get("package")
+ bb.warn(f"Parameter 'package' in '{ud.url}' is deprecated."
+ "Please use 'dn' parameter instead.")
+ ud.parm["dn"] = ud.parm["package"]
+ del ud.parm["package"]
+ if "version" in ud.parm:
+ bb.warn(f"Parameter 'version' in '{ud.url}' is deprecated."
+ "Please use 'dv' parameter instead.")
+ ud.parm["dv"] = ud.parm["version"]
+ del ud.parm["version"]
+
+ if any(x not in ud.parm for x in ["dn", "dv"]):
+ return
+
+ registry = ud.host
+ if ud.path != '/':
+ registry += ud.path
+ name = ud.parm["dn"]
+ version = ud.parm["dv"]
+
+ if not is_semver(version):
+ if version == "latest":
+ raise ParameterError("Value 'latest' for parameter 'version' is no longer supported", ud.url)
+ else:
+ raise ParameterError("Invalid 'version' parameter", ud.url)
- if not ud.package:
- raise MissingParameterError("Parameter 'package' required", ud.url)
+ ud.url = construct_url(registry, name, version)
+ ud.info_url = f"https://{registry}/{name}"
- # Get the 'version' parameter
- if "version" in ud.parm:
- ud.version = ud.parm.get("version")
+ if not "downloadfilename" in ud.parm:
+ ud.parm['downloadfilename'] = npm_localfile(name, version)
- if not ud.version:
- raise MissingParameterError("Parameter 'version' required", ud.url)
+ destsuffix = ud.parm.get("destsuffix", "npm")
+ subdir = ud.parm.get("subdir", "")
+ ud.parm["destsuffix"] = destsuffix
+ ud.parm["subdir"] = os.path.join(subdir, destsuffix)
- if not is_semver(ud.version) and not ud.version == "latest":
- raise ParameterError("Invalid 'version' parameter", ud.url)
+ if 'name' not in ud.parm:
+ ud.parm["name"] = f"{npm_package(name)}-{version}"
- # Extract the 'registry' part of the url
- ud.registry = re.sub(r"^npm://", "https://", ud.url.split(";")[0])
+ ud.parm["striplevel"] = 1
- # Using the 'downloadfilename' parameter as local filename
- # or the npm package name.
- if "downloadfilename" in ud.parm:
- ud.localfile = npm_localfile(d.expand(ud.parm["downloadfilename"]))
- else:
- ud.localfile = npm_localfile(ud.package, ud.version)
-
- # Get the base 'npm' command
- ud.basecmd = d.getVar("FETCHCMD_npm") or "npm"
-
- # This fetcher resolves a URI from a npm package name and version and
- # then forwards it to a proxy fetcher. A resolve file containing the
- # resolved URI is created to avoid unwanted network access (if the file
- # already exists). The management of the donestamp file, the lockfile
- # and the checksums are forwarded to the proxy fetcher.
- ud.proxy = None
- ud.needdonestamp = False
- ud.resolvefile = self.localpath(ud, d) + ".resolved"
-
- def _resolve_proxy_url(self, ud, d):
- def _npm_view():
- args = []
- args.append(("json", "true"))
- args.append(("registry", ud.registry))
- pkgver = shlex.quote(ud.package + "@" + ud.version)
- cmd = ud.basecmd + " view %s" % pkgver
- env = NpmEnvironment(d)
- check_network_access(d, cmd, ud.registry)
- view_string = env.run(cmd, args=args)
-
- if not view_string:
- raise FetchError("Unavailable package %s" % pkgver, ud.url)
-
- try:
- view = json.loads(view_string)
-
- error = view.get("error")
- if error is not None:
- raise FetchError(error.get("summary"), ud.url)
-
- if ud.version == "latest":
- bb.warn("The npm package %s is using the latest " \
- "version available. This could lead to " \
- "non-reproducible builds." % pkgver)
- elif ud.version != view.get("version"):
- raise ParameterError("Invalid 'version' parameter", ud.url)
-
- return view
-
- except Exception as e:
- raise FetchError("Invalid view from npm: %s" % str(e), ud.url)
-
- def _get_url(view):
- tarball_url = view.get("dist", {}).get("tarball")
-
- if tarball_url is None:
- raise FetchError("Invalid 'dist.tarball' in view", ud.url)
-
- uri = URI(tarball_url)
- uri.params["downloadfilename"] = ud.localfile
-
- integrity = view.get("dist", {}).get("integrity")
- shasum = view.get("dist", {}).get("shasum")
-
- if integrity is not None:
- checksum_name, checksum_expected = npm_integrity(integrity)
- uri.params[checksum_name] = checksum_expected
- elif shasum is not None:
- uri.params["sha1sum"] = shasum
- else:
- raise FetchError("Invalid 'dist.integrity' in view", ud.url)
-
- return str(uri)
-
- url = _get_url(_npm_view())
-
- bb.utils.mkdirhier(os.path.dirname(ud.resolvefile))
- with open(ud.resolvefile, "w") as f:
- f.write(url)
-
- def _setup_proxy(self, ud, d):
- if ud.proxy is None:
- if not os.path.exists(ud.resolvefile):
- self._resolve_proxy_url(ud, d)
-
- with open(ud.resolvefile, "r") as f:
- url = f.read()
-
- # Avoid conflicts between the environment data and:
- # - the proxy url checksum
- data = bb.data.createCopy(d)
- data.delVarFlags("SRC_URI")
- ud.proxy = Fetch([url], data)
-
- def _get_proxy_method(self, ud, d):
- self._setup_proxy(ud, d)
- proxy_url = ud.proxy.urls[0]
- proxy_ud = ud.proxy.ud[proxy_url]
- proxy_d = ud.proxy.d
- proxy_ud.setup_localpath(proxy_d)
- return proxy_ud.method, proxy_ud, proxy_d
-
- def verify_donestamp(self, ud, d):
- """Verify the donestamp file"""
- proxy_m, proxy_ud, proxy_d = self._get_proxy_method(ud, d)
- return proxy_m.verify_donestamp(proxy_ud, proxy_d)
-
- def update_donestamp(self, ud, d):
- """Update the donestamp file"""
- proxy_m, proxy_ud, proxy_d = self._get_proxy_method(ud, d)
- proxy_m.update_donestamp(proxy_ud, proxy_d)
-
- def need_update(self, ud, d):
- """Force a fetch, even if localpath exists ?"""
- if not os.path.exists(ud.resolvefile):
- return True
- if ud.version == "latest":
- return True
- proxy_m, proxy_ud, proxy_d = self._get_proxy_method(ud, d)
- return proxy_m.need_update(proxy_ud, proxy_d)
-
- def try_mirrors(self, fetch, ud, d, mirrors):
- """Try to use a mirror"""
- proxy_m, proxy_ud, proxy_d = self._get_proxy_method(ud, d)
- return proxy_m.try_mirrors(fetch, proxy_ud, proxy_d, mirrors)
-
- def download(self, ud, d):
- """Fetch url"""
- self._setup_proxy(ud, d)
- ud.proxy.download()
-
- def unpack(self, ud, rootdir, d):
- """Unpack the downloaded archive"""
- destsuffix = ud.parm.get("destsuffix", "npm")
- destdir = os.path.join(rootdir, destsuffix)
- npm_unpack(ud.localpath, destdir, d)
- ud.unpack_tracer.unpack("npm", destdir)
-
- def clean(self, ud, d):
- """Clean any existing full or partial download"""
- if os.path.exists(ud.resolvefile):
- self._setup_proxy(ud, d)
- ud.proxy.clean()
- bb.utils.remove(ud.resolvefile)
-
- def done(self, ud, d):
- """Is the download done ?"""
- if not os.path.exists(ud.resolvefile):
- return False
- proxy_m, proxy_ud, proxy_d = self._get_proxy_method(ud, d)
- return proxy_m.done(proxy_ud, proxy_d)
+ super().urldata_init(ud, d)
+
+ def latest_versionstring(self, ud, d):
+ from functools import cmp_to_key
+ info = json.loads(self._fetch_index(ud.info_url, ud, d))
+ versions = [(0, v, "") for v in info["versions"]]
+ versions = sorted(versions, key=cmp_to_key(bb.utils.vercmp))
+
+ return (versions[-1][1], "")
+
+ def require_download_metadata(self):
+ return True