summaryrefslogtreecommitdiffstats
path: root/meta/classes/npm.bbclass
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes/npm.bbclass')
-rw-r--r--meta/classes/npm.bbclass318
1 files changed, 0 insertions, 318 deletions
diff --git a/meta/classes/npm.bbclass b/meta/classes/npm.bbclass
deleted file mode 100644
index 55a6985fb0..0000000000
--- a/meta/classes/npm.bbclass
+++ /dev/null
@@ -1,318 +0,0 @@
1# Copyright (C) 2020 Savoir-Faire Linux
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5# This bbclass builds and installs an npm package to the target. The package
6# sources files should be fetched in the calling recipe by using the SRC_URI
7# variable. The ${S} variable should be updated depending of your fetcher.
8#
9# Usage:
10# SRC_URI = "..."
11# inherit npm
12#
13# Optional variables:
14# NPM_ARCH:
15# Override the auto generated npm architecture.
16#
17# NPM_INSTALL_DEV:
18# Set to 1 to also install devDependencies.
19
20inherit python3native
21
22DEPENDS_prepend = "nodejs-native "
23RDEPENDS_${PN}_append_class-target = " nodejs"
24
25NPM_INSTALL_DEV ?= "0"
26
27def npm_target_arch_map(target_arch):
28 """Maps arch names to npm arch names"""
29 import re
30 if re.match("p(pc|owerpc)(|64)", target_arch):
31 return "ppc"
32 elif re.match("i.86$", target_arch):
33 return "ia32"
34 elif re.match("x86_64$", target_arch):
35 return "x64"
36 elif re.match("arm64$", target_arch):
37 return "arm"
38 return target_arch
39
40NPM_ARCH ?= "${@npm_target_arch_map(d.getVar("TARGET_ARCH"))}"
41
42NPM_PACKAGE = "${WORKDIR}/npm-package"
43NPM_CACHE = "${WORKDIR}/npm-cache"
44NPM_BUILD = "${WORKDIR}/npm-build"
45
46def npm_global_configs(d):
47 """Get the npm global configuration"""
48 configs = []
49 # Ensure no network access is done
50 configs.append(("offline", "true"))
51 configs.append(("proxy", "http://invalid"))
52 # Configure the cache directory
53 configs.append(("cache", d.getVar("NPM_CACHE")))
54 return configs
55
56def npm_pack(env, srcdir, workdir):
57 """Run 'npm pack' on a specified directory"""
58 import shlex
59 cmd = "npm pack %s" % shlex.quote(srcdir)
60 configs = [("ignore-scripts", "true")]
61 tarball = env.run(cmd, configs=configs, workdir=workdir).strip("\n")
62 return os.path.join(workdir, tarball)
63
64python npm_do_configure() {
65 """
66 Step one: configure the npm cache and the main npm package
67
68 Every dependencies have been fetched and patched in the source directory.
69 They have to be packed (this remove unneeded files) and added to the npm
70 cache to be available for the next step.
71
72 The main package and its associated manifest file and shrinkwrap file have
73 to be configured to take into account these cached dependencies.
74 """
75 import base64
76 import copy
77 import json
78 import re
79 import shlex
80 import tempfile
81 from bb.fetch2.npm import NpmEnvironment
82 from bb.fetch2.npm import npm_unpack
83 from bb.fetch2.npmsw import foreach_dependencies
84 from bb.progress import OutOfProgressHandler
85
86 bb.utils.remove(d.getVar("NPM_CACHE"), recurse=True)
87 bb.utils.remove(d.getVar("NPM_PACKAGE"), recurse=True)
88
89 env = NpmEnvironment(d, configs=npm_global_configs(d))
90
91 def _npm_cache_add(tarball):
92 """Run 'npm cache add' for a specified tarball"""
93 cmd = "npm cache add %s" % shlex.quote(tarball)
94 env.run(cmd)
95
96 def _npm_integrity(tarball):
97 """Return the npm integrity of a specified tarball"""
98 sha512 = bb.utils.sha512_file(tarball)
99 return "sha512-" + base64.b64encode(bytes.fromhex(sha512)).decode()
100
101 def _npm_version(tarball):
102 """Return the version of a specified tarball"""
103 regex = r"-(\d+\.\d+\.\d+(-.*)?(\+.*)?)\.tgz"
104 return re.search(regex, tarball).group(1)
105
106 def _npmsw_dependency_dict(orig, deptree):
107 """
108 Return the sub dictionary in the 'orig' dictionary corresponding to the
109 'deptree' dependency tree. This function follows the shrinkwrap file
110 format.
111 """
112 ptr = orig
113 for dep in deptree:
114 if "dependencies" not in ptr:
115 ptr["dependencies"] = {}
116 ptr = ptr["dependencies"]
117 if dep not in ptr:
118 ptr[dep] = {}
119 ptr = ptr[dep]
120 return ptr
121
122 # Manage the manifest file and shrinkwrap files
123 orig_manifest_file = d.expand("${S}/package.json")
124 orig_shrinkwrap_file = d.expand("${S}/npm-shrinkwrap.json")
125 cached_manifest_file = d.expand("${NPM_PACKAGE}/package.json")
126 cached_shrinkwrap_file = d.expand("${NPM_PACKAGE}/npm-shrinkwrap.json")
127
128 with open(orig_manifest_file, "r") as f:
129 orig_manifest = json.load(f)
130
131 cached_manifest = copy.deepcopy(orig_manifest)
132 cached_manifest.pop("dependencies", None)
133 cached_manifest.pop("devDependencies", None)
134
135 has_shrinkwrap_file = True
136
137 try:
138 with open(orig_shrinkwrap_file, "r") as f:
139 orig_shrinkwrap = json.load(f)
140 except IOError:
141 has_shrinkwrap_file = False
142
143 if has_shrinkwrap_file:
144 cached_shrinkwrap = copy.deepcopy(orig_shrinkwrap)
145 cached_shrinkwrap.pop("dependencies", None)
146
147 # Manage the dependencies
148 progress = OutOfProgressHandler(d, r"^(\d+)/(\d+)$")
149 progress_total = 1 # also count the main package
150 progress_done = 0
151
152 def _count_dependency(name, params, deptree):
153 nonlocal progress_total
154 progress_total += 1
155
156 def _cache_dependency(name, params, deptree):
157 destsubdirs = [os.path.join("node_modules", dep) for dep in deptree]
158 destsuffix = os.path.join(*destsubdirs)
159 with tempfile.TemporaryDirectory() as tmpdir:
160 # Add the dependency to the npm cache
161 destdir = os.path.join(d.getVar("S"), destsuffix)
162 tarball = npm_pack(env, destdir, tmpdir)
163 _npm_cache_add(tarball)
164 # Add its signature to the cached shrinkwrap
165 dep = _npmsw_dependency_dict(cached_shrinkwrap, deptree)
166 dep["version"] = _npm_version(tarball)
167 dep["integrity"] = _npm_integrity(tarball)
168 if params.get("dev", False):
169 dep["dev"] = True
170 # Display progress
171 nonlocal progress_done
172 progress_done += 1
173 progress.write("%d/%d" % (progress_done, progress_total))
174
175 dev = bb.utils.to_boolean(d.getVar("NPM_INSTALL_DEV"), False)
176
177 if has_shrinkwrap_file:
178 foreach_dependencies(orig_shrinkwrap, _count_dependency, dev)
179 foreach_dependencies(orig_shrinkwrap, _cache_dependency, dev)
180
181 # Configure the main package
182 with tempfile.TemporaryDirectory() as tmpdir:
183 tarball = npm_pack(env, d.getVar("S"), tmpdir)
184 npm_unpack(tarball, d.getVar("NPM_PACKAGE"), d)
185
186 # Configure the cached manifest file and cached shrinkwrap file
187 def _update_manifest(depkey):
188 for name in orig_manifest.get(depkey, {}):
189 version = cached_shrinkwrap["dependencies"][name]["version"]
190 if depkey not in cached_manifest:
191 cached_manifest[depkey] = {}
192 cached_manifest[depkey][name] = version
193
194 if has_shrinkwrap_file:
195 _update_manifest("dependencies")
196
197 if dev:
198 if has_shrinkwrap_file:
199 _update_manifest("devDependencies")
200
201 with open(cached_manifest_file, "w") as f:
202 json.dump(cached_manifest, f, indent=2)
203
204 if has_shrinkwrap_file:
205 with open(cached_shrinkwrap_file, "w") as f:
206 json.dump(cached_shrinkwrap, f, indent=2)
207}
208
209python npm_do_compile() {
210 """
211 Step two: install the npm package
212
213 Use the configured main package and the cached dependencies to run the
214 installation process. The installation is done in a directory which is
215 not the destination directory yet.
216
217 A combination of 'npm pack' and 'npm install' is used to ensure that the
218 installed files are actual copies instead of symbolic links (which is the
219 default npm behavior).
220 """
221 import shlex
222 import tempfile
223 from bb.fetch2.npm import NpmEnvironment
224
225 bb.utils.remove(d.getVar("NPM_BUILD"), recurse=True)
226
227 env = NpmEnvironment(d, configs=npm_global_configs(d))
228
229 dev = bb.utils.to_boolean(d.getVar("NPM_INSTALL_DEV"), False)
230
231 with tempfile.TemporaryDirectory() as tmpdir:
232 args = []
233 configs = []
234
235 if dev:
236 configs.append(("also", "development"))
237 else:
238 configs.append(("only", "production"))
239
240 # Report as many logs as possible for debugging purpose
241 configs.append(("loglevel", "silly"))
242
243 # Configure the installation to be done globally in the build directory
244 configs.append(("global", "true"))
245 configs.append(("prefix", d.getVar("NPM_BUILD")))
246
247 # Add node-gyp configuration
248 configs.append(("arch", d.getVar("NPM_ARCH")))
249 configs.append(("release", "true"))
250 sysroot = d.getVar("RECIPE_SYSROOT_NATIVE")
251 nodedir = os.path.join(sysroot, d.getVar("prefix_native").strip("/"))
252 configs.append(("nodedir", nodedir))
253 configs.append(("python", d.getVar("PYTHON")))
254
255 # Add node-pre-gyp configuration
256 args.append(("target_arch", d.getVar("NPM_ARCH")))
257 args.append(("build-from-source", "true"))
258
259 # Pack and install the main package
260 tarball = npm_pack(env, d.getVar("NPM_PACKAGE"), tmpdir)
261 env.run("npm install %s" % shlex.quote(tarball), args=args, configs=configs)
262}
263
264npm_do_install() {
265 # Step three: final install
266 #
267 # The previous installation have to be filtered to remove some extra files.
268
269 rm -rf ${D}
270
271 # Copy the entire lib and bin directories
272 install -d ${D}/${nonarch_libdir}
273 cp --no-preserve=ownership --recursive ${NPM_BUILD}/lib/. ${D}/${nonarch_libdir}
274
275 if [ -d "${NPM_BUILD}/bin" ]
276 then
277 install -d ${D}/${bindir}
278 cp --no-preserve=ownership --recursive ${NPM_BUILD}/bin/. ${D}/${bindir}
279 fi
280
281 # If the package (or its dependencies) uses node-gyp to build native addons,
282 # object files, static libraries or other temporary files can be hidden in
283 # the lib directory. To reduce the package size and to avoid QA issues
284 # (staticdev with static library files) these files must be removed.
285 local GYP_REGEX=".*/build/Release/[^/]*.node"
286
287 # Remove any node-gyp directory in ${D} to remove temporary build files
288 for GYP_D_FILE in $(find ${D} -regex "${GYP_REGEX}")
289 do
290 local GYP_D_DIR=${GYP_D_FILE%/Release/*}
291
292 rm --recursive --force ${GYP_D_DIR}
293 done
294
295 # Copy only the node-gyp release files
296 for GYP_B_FILE in $(find ${NPM_BUILD} -regex "${GYP_REGEX}")
297 do
298 local GYP_D_FILE=${D}/${prefix}/${GYP_B_FILE#${NPM_BUILD}}
299
300 install -d ${GYP_D_FILE%/*}
301 install -m 755 ${GYP_B_FILE} ${GYP_D_FILE}
302 done
303
304 # Remove the shrinkwrap file which does not need to be packed
305 rm -f ${D}/${nonarch_libdir}/node_modules/*/npm-shrinkwrap.json
306 rm -f ${D}/${nonarch_libdir}/node_modules/@*/*/npm-shrinkwrap.json
307
308 # node(1) is using /usr/lib/node as default include directory and npm(1) is
309 # using /usr/lib/node_modules as install directory. Let's make both happy.
310 ln -fs node_modules ${D}/${nonarch_libdir}/node
311}
312
313FILES_${PN} += " \
314 ${bindir} \
315 ${nonarch_libdir} \
316"
317
318EXPORT_FUNCTIONS do_configure do_compile do_install