| Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 1 | # | 
|  | 2 | # SPDX-License-Identifier: GPL-2.0-only | 
|  | 3 | # | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 4 | """ | 
|  | 5 | BitBake 'Fetch' NPM implementation | 
|  | 6 |  | 
|  | 7 | The NPM fetcher is used to retrieve files from the npmjs repository | 
|  | 8 |  | 
|  | 9 | Usage in the recipe: | 
|  | 10 |  | 
|  | 11 | SRC_URI = "npm://registry.npmjs.org/;name=${PN};version=${PV}" | 
|  | 12 | Suported SRC_URI options are: | 
|  | 13 |  | 
|  | 14 | - name | 
|  | 15 | - version | 
|  | 16 |  | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 17 | npm://registry.npmjs.org/${PN}/-/${PN}-${PV}.tgz  would become npm://registry.npmjs.org;name=${PN};version=${PV} | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 18 | The fetcher all triggers off the existence of ud.localpath. If that exists and has the ".done" stamp, its assumed the fetch is good/done | 
|  | 19 |  | 
|  | 20 | """ | 
|  | 21 |  | 
|  | 22 | import os | 
|  | 23 | import sys | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 24 | import urllib.request, urllib.parse, urllib.error | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 25 | import json | 
|  | 26 | import subprocess | 
|  | 27 | import signal | 
|  | 28 | import bb | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 29 | from   bb.fetch2 import FetchMethod | 
|  | 30 | from   bb.fetch2 import FetchError | 
|  | 31 | from   bb.fetch2 import ChecksumError | 
|  | 32 | from   bb.fetch2 import runfetchcmd | 
|  | 33 | from   bb.fetch2 import logger | 
|  | 34 | from   bb.fetch2 import UnpackError | 
|  | 35 | from   bb.fetch2 import ParameterError | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 36 |  | 
|  | 37 | def subprocess_setup(): | 
|  | 38 | # Python installs a SIGPIPE handler by default. This is usually not what | 
|  | 39 | # non-Python subprocesses expect. | 
|  | 40 | # SIGPIPE errors are known issues with gzip/bash | 
|  | 41 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) | 
|  | 42 |  | 
|  | 43 | class Npm(FetchMethod): | 
|  | 44 |  | 
|  | 45 | """Class to fetch urls via 'npm'""" | 
|  | 46 | def init(self, d): | 
|  | 47 | pass | 
|  | 48 |  | 
|  | 49 | def supports(self, ud, d): | 
|  | 50 | """ | 
|  | 51 | Check to see if a given url can be fetched with npm | 
|  | 52 | """ | 
|  | 53 | return ud.type in ['npm'] | 
|  | 54 |  | 
|  | 55 | def debug(self, msg): | 
|  | 56 | logger.debug(1, "NpmFetch: %s", msg) | 
|  | 57 |  | 
|  | 58 | def clean(self, ud, d): | 
|  | 59 | logger.debug(2, "Calling cleanup %s" % ud.pkgname) | 
|  | 60 | bb.utils.remove(ud.localpath, False) | 
|  | 61 | bb.utils.remove(ud.pkgdatadir, True) | 
|  | 62 | bb.utils.remove(ud.fullmirror, False) | 
|  | 63 |  | 
|  | 64 | def urldata_init(self, ud, d): | 
|  | 65 | """ | 
|  | 66 | init NPM specific variable within url data | 
|  | 67 | """ | 
|  | 68 | if 'downloadfilename' in ud.parm: | 
|  | 69 | ud.basename = ud.parm['downloadfilename'] | 
|  | 70 | else: | 
|  | 71 | ud.basename = os.path.basename(ud.path) | 
|  | 72 |  | 
|  | 73 | # can't call it ud.name otherwise fetcher base class will start doing sha1stuff | 
|  | 74 | # TODO: find a way to get an sha1/sha256 manifest of pkg & all deps | 
|  | 75 | ud.pkgname = ud.parm.get("name", None) | 
|  | 76 | if not ud.pkgname: | 
|  | 77 | raise ParameterError("NPM fetcher requires a name parameter", ud.url) | 
|  | 78 | ud.version = ud.parm.get("version", None) | 
|  | 79 | if not ud.version: | 
|  | 80 | raise ParameterError("NPM fetcher requires a version parameter", ud.url) | 
|  | 81 | ud.bbnpmmanifest = "%s-%s.deps.json" % (ud.pkgname, ud.version) | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 82 | ud.bbnpmmanifest = ud.bbnpmmanifest.replace('/', '-') | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 83 | ud.registry = "http://%s" % (ud.url.replace('npm://', '', 1).split(';'))[0] | 
|  | 84 | prefixdir = "npm/%s" % ud.pkgname | 
|  | 85 | ud.pkgdatadir = d.expand("${DL_DIR}/%s" % prefixdir) | 
|  | 86 | if not os.path.exists(ud.pkgdatadir): | 
|  | 87 | bb.utils.mkdirhier(ud.pkgdatadir) | 
|  | 88 | ud.localpath = d.expand("${DL_DIR}/npm/%s" % ud.bbnpmmanifest) | 
|  | 89 |  | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 90 | self.basecmd = d.getVar("FETCHCMD_wget") or "/usr/bin/env wget -O -t 2 -T 30 -nv --passive-ftp --no-check-certificate " | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 91 | ud.prefixdir = prefixdir | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 92 |  | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 93 | ud.write_tarballs = ((d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0") != "0") | 
| Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 94 | mirrortarball = 'npm_%s-%s.tar.xz' % (ud.pkgname, ud.version) | 
|  | 95 | mirrortarball = mirrortarball.replace('/', '-') | 
|  | 96 | ud.fullmirror = os.path.join(d.getVar("DL_DIR"), mirrortarball) | 
|  | 97 | ud.mirrortarballs = [mirrortarball] | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 98 |  | 
|  | 99 | def need_update(self, ud, d): | 
|  | 100 | if os.path.exists(ud.localpath): | 
|  | 101 | return False | 
|  | 102 | return True | 
|  | 103 |  | 
|  | 104 | def _runwget(self, ud, d, command, quiet): | 
|  | 105 | logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command)) | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 106 | bb.fetch2.check_network_access(d, command, ud.url) | 
|  | 107 | dldir = d.getVar("DL_DIR") | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 108 | runfetchcmd(command, d, quiet, workdir=dldir) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 109 |  | 
|  | 110 | def _unpackdep(self, ud, pkg, data, destdir, dldir, d): | 
|  | 111 | file = data[pkg]['tgz'] | 
|  | 112 | logger.debug(2, "file to extract is %s" % file) | 
|  | 113 | if file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'): | 
|  | 114 | cmd = 'tar xz --strip 1 --no-same-owner --warning=no-unknown-keyword -f %s/%s' % (dldir, file) | 
|  | 115 | else: | 
|  | 116 | bb.fatal("NPM package %s downloaded not a tarball!" % file) | 
|  | 117 |  | 
|  | 118 | # Change to subdir before executing command | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 119 | if not os.path.exists(destdir): | 
|  | 120 | os.makedirs(destdir) | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 121 | path = d.getVar('PATH') | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 122 | if path: | 
|  | 123 | cmd = "PATH=\"%s\" %s" % (path, cmd) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 124 | bb.note("Unpacking %s to %s/" % (file, destdir)) | 
|  | 125 | ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True, cwd=destdir) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 126 |  | 
|  | 127 | if ret != 0: | 
|  | 128 | raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), ud.url) | 
|  | 129 |  | 
|  | 130 | if 'deps' not in data[pkg]: | 
|  | 131 | return | 
|  | 132 | for dep in data[pkg]['deps']: | 
|  | 133 | self._unpackdep(ud, dep, data[pkg]['deps'], "%s/node_modules/%s" % (destdir, dep), dldir, d) | 
|  | 134 |  | 
|  | 135 |  | 
|  | 136 | def unpack(self, ud, destdir, d): | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 137 | dldir = d.getVar("DL_DIR") | 
|  | 138 | with open("%s/npm/%s" % (dldir, ud.bbnpmmanifest)) as datafile: | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 139 | workobj = json.load(datafile) | 
|  | 140 | dldir = "%s/%s" % (os.path.dirname(ud.localpath), ud.pkgname) | 
|  | 141 |  | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 142 | if 'subdir' in ud.parm: | 
|  | 143 | unpackdir = '%s/%s' % (destdir, ud.parm.get('subdir')) | 
|  | 144 | else: | 
|  | 145 | unpackdir = '%s/npmpkg' % destdir | 
|  | 146 |  | 
|  | 147 | self._unpackdep(ud, ud.pkgname, workobj, unpackdir, dldir, d) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 148 |  | 
|  | 149 | def _parse_view(self, output): | 
|  | 150 | ''' | 
|  | 151 | Parse the output of npm view --json; the last JSON result | 
|  | 152 | is assumed to be the one that we're interested in. | 
|  | 153 | ''' | 
| Brad Bishop | 15ae250 | 2019-06-18 21:44:24 -0400 | [diff] [blame^] | 154 | pdata = json.loads(output); | 
|  | 155 | try: | 
|  | 156 | return pdata[-1] | 
|  | 157 | except: | 
|  | 158 | return pdata | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 159 |  | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 160 | def _getdependencies(self, pkg, data, version, d, ud, optional=False, fetchedlist=None): | 
|  | 161 | if fetchedlist is None: | 
|  | 162 | fetchedlist = [] | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 163 | pkgfullname = pkg | 
|  | 164 | if version != '*' and not '/' in version: | 
|  | 165 | pkgfullname += "@'%s'" % version | 
|  | 166 | logger.debug(2, "Calling getdeps on %s" % pkg) | 
|  | 167 | fetchcmd = "npm view %s --json --registry %s" % (pkgfullname, ud.registry) | 
|  | 168 | output = runfetchcmd(fetchcmd, d, True) | 
|  | 169 | pdata = self._parse_view(output) | 
|  | 170 | if not pdata: | 
|  | 171 | raise FetchError("The command '%s' returned no output" % fetchcmd) | 
|  | 172 | if optional: | 
|  | 173 | pkg_os = pdata.get('os', None) | 
|  | 174 | if pkg_os: | 
|  | 175 | if not isinstance(pkg_os, list): | 
|  | 176 | pkg_os = [pkg_os] | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 177 | blacklist = False | 
|  | 178 | for item in pkg_os: | 
|  | 179 | if item.startswith('!'): | 
|  | 180 | blacklist = True | 
|  | 181 | break | 
|  | 182 | if (not blacklist and 'linux' not in pkg_os) or '!linux' in pkg_os: | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 183 | logger.debug(2, "Skipping %s since it's incompatible with Linux" % pkg) | 
|  | 184 | return | 
|  | 185 | #logger.debug(2, "Output URL is %s - %s - %s" % (ud.basepath, ud.basename, ud.localfile)) | 
|  | 186 | outputurl = pdata['dist']['tarball'] | 
|  | 187 | data[pkg] = {} | 
|  | 188 | data[pkg]['tgz'] = os.path.basename(outputurl) | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 189 | if outputurl in fetchedlist: | 
|  | 190 | return | 
|  | 191 |  | 
|  | 192 | self._runwget(ud, d, "%s --directory-prefix=%s %s" % (self.basecmd, ud.prefixdir, outputurl), False) | 
|  | 193 | fetchedlist.append(outputurl) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 194 |  | 
|  | 195 | dependencies = pdata.get('dependencies', {}) | 
|  | 196 | optionalDependencies = pdata.get('optionalDependencies', {}) | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 197 | dependencies.update(optionalDependencies) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 198 | depsfound = {} | 
|  | 199 | optdepsfound = {} | 
|  | 200 | data[pkg]['deps'] = {} | 
|  | 201 | for dep in dependencies: | 
|  | 202 | if dep in optionalDependencies: | 
|  | 203 | optdepsfound[dep] = dependencies[dep] | 
|  | 204 | else: | 
|  | 205 | depsfound[dep] = dependencies[dep] | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 206 | for dep, version in optdepsfound.items(): | 
|  | 207 | self._getdependencies(dep, data[pkg]['deps'], version, d, ud, optional=True, fetchedlist=fetchedlist) | 
|  | 208 | for dep, version in depsfound.items(): | 
|  | 209 | self._getdependencies(dep, data[pkg]['deps'], version, d, ud, fetchedlist=fetchedlist) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 210 |  | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 211 | def _getshrinkeddependencies(self, pkg, data, version, d, ud, lockdown, manifest, toplevel=True): | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 212 | logger.debug(2, "NPM shrinkwrap file is %s" % data) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 213 | if toplevel: | 
|  | 214 | name = data.get('name', None) | 
|  | 215 | if name and name != pkg: | 
|  | 216 | for obj in data.get('dependencies', []): | 
|  | 217 | if obj == pkg: | 
|  | 218 | self._getshrinkeddependencies(obj, data['dependencies'][obj], data['dependencies'][obj]['version'], d, ud, lockdown, manifest, False) | 
|  | 219 | return | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 220 | outputurl = "invalid" | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 221 | if ('resolved' not in data) or (not data['resolved'].startswith('http://') and not data['resolved'].startswith('https://')): | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 222 | # will be the case for ${PN} | 
|  | 223 | fetchcmd = "npm view %s@%s dist.tarball --registry %s" % (pkg, version, ud.registry) | 
|  | 224 | logger.debug(2, "Found this matching URL: %s" % str(fetchcmd)) | 
|  | 225 | outputurl = runfetchcmd(fetchcmd, d, True) | 
|  | 226 | else: | 
|  | 227 | outputurl = data['resolved'] | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 228 | self._runwget(ud, d, "%s --directory-prefix=%s %s" % (self.basecmd, ud.prefixdir, outputurl), False) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 229 | manifest[pkg] = {} | 
|  | 230 | manifest[pkg]['tgz'] = os.path.basename(outputurl).rstrip() | 
|  | 231 | manifest[pkg]['deps'] = {} | 
|  | 232 |  | 
|  | 233 | if pkg in lockdown: | 
|  | 234 | sha1_expected = lockdown[pkg][version] | 
|  | 235 | sha1_data = bb.utils.sha1_file("npm/%s/%s" % (ud.pkgname, manifest[pkg]['tgz'])) | 
|  | 236 | if sha1_expected != sha1_data: | 
|  | 237 | msg = "\nFile: '%s' has %s checksum %s when %s was expected" % (manifest[pkg]['tgz'], 'sha1', sha1_data, sha1_expected) | 
|  | 238 | raise ChecksumError('Checksum mismatch!%s' % msg) | 
|  | 239 | else: | 
|  | 240 | logger.debug(2, "No lockdown data for %s@%s" % (pkg, version)) | 
|  | 241 |  | 
|  | 242 | if 'dependencies' in data: | 
|  | 243 | for obj in data['dependencies']: | 
|  | 244 | logger.debug(2, "Found dep is %s" % str(obj)) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 245 | self._getshrinkeddependencies(obj, data['dependencies'][obj], data['dependencies'][obj]['version'], d, ud, lockdown, manifest[pkg]['deps'], False) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 246 |  | 
|  | 247 | def download(self, ud, d): | 
|  | 248 | """Fetch url""" | 
|  | 249 | jsondepobj = {} | 
|  | 250 | shrinkobj = {} | 
|  | 251 | lockdown = {} | 
|  | 252 |  | 
|  | 253 | if not os.listdir(ud.pkgdatadir) and os.path.exists(ud.fullmirror): | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 254 | dest = d.getVar("DL_DIR") | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 255 | bb.utils.mkdirhier(dest) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 256 | runfetchcmd("tar -xJf %s" % (ud.fullmirror), d, workdir=dest) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 257 | return | 
|  | 258 |  | 
| Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 259 | if ud.parm.get("noverify", None) != '1': | 
|  | 260 | shwrf = d.getVar('NPM_SHRINKWRAP') | 
|  | 261 | logger.debug(2, "NPM shrinkwrap file is %s" % shwrf) | 
|  | 262 | if shwrf: | 
|  | 263 | try: | 
|  | 264 | with open(shwrf) as datafile: | 
|  | 265 | shrinkobj = json.load(datafile) | 
|  | 266 | except Exception as e: | 
|  | 267 | raise FetchError('Error loading NPM_SHRINKWRAP file "%s" for %s: %s' % (shwrf, ud.pkgname, str(e))) | 
|  | 268 | elif not ud.ignore_checksums: | 
|  | 269 | logger.warning('Missing shrinkwrap file in NPM_SHRINKWRAP for %s, this will lead to unreliable builds!' % ud.pkgname) | 
|  | 270 | lckdf = d.getVar('NPM_LOCKDOWN') | 
|  | 271 | logger.debug(2, "NPM lockdown file is %s" % lckdf) | 
|  | 272 | if lckdf: | 
|  | 273 | try: | 
|  | 274 | with open(lckdf) as datafile: | 
|  | 275 | lockdown = json.load(datafile) | 
|  | 276 | except Exception as e: | 
|  | 277 | raise FetchError('Error loading NPM_LOCKDOWN file "%s" for %s: %s' % (lckdf, ud.pkgname, str(e))) | 
|  | 278 | elif not ud.ignore_checksums: | 
|  | 279 | logger.warning('Missing lockdown file in NPM_LOCKDOWN for %s, this will lead to unreproducible builds!' % ud.pkgname) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 280 |  | 
|  | 281 | if ('name' not in shrinkobj): | 
|  | 282 | self._getdependencies(ud.pkgname, jsondepobj, ud.version, d, ud) | 
|  | 283 | else: | 
|  | 284 | self._getshrinkeddependencies(ud.pkgname, shrinkobj, ud.version, d, ud, lockdown, jsondepobj) | 
|  | 285 |  | 
|  | 286 | with open(ud.localpath, 'w') as outfile: | 
|  | 287 | json.dump(jsondepobj, outfile) | 
|  | 288 |  | 
|  | 289 | def build_mirror_data(self, ud, d): | 
|  | 290 | # Generate a mirror tarball if needed | 
|  | 291 | if ud.write_tarballs and not os.path.exists(ud.fullmirror): | 
|  | 292 | # it's possible that this symlink points to read-only filesystem with PREMIRROR | 
|  | 293 | if os.path.islink(ud.fullmirror): | 
|  | 294 | os.unlink(ud.fullmirror) | 
|  | 295 |  | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 296 | dldir = d.getVar("DL_DIR") | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 297 | logger.info("Creating tarball of npm data") | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 298 | runfetchcmd("tar -cJf %s npm/%s npm/%s" % (ud.fullmirror, ud.bbnpmmanifest, ud.pkgname), d, | 
|  | 299 | workdir=dldir) | 
|  | 300 | runfetchcmd("touch %s.done" % (ud.fullmirror), d, workdir=dldir) |