diff options
author | Paul Eggleton <paul.eggleton@linux.intel.com> | 2016-09-05 15:58:01 +1200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2016-09-08 00:36:49 +0100 |
commit | ff259b095d1a84d1dc9b004c669c8f35659c3c2b (patch) | |
tree | b907b000b830f4955273463d886896660ff88a18 | |
parent | fa90c2f54d7954dd3149404b38a0899d2e4962cf (diff) | |
download | poky-ff259b095d1a84d1dc9b004c669c8f35659c3c2b.tar.gz |
recipetool: create: support node.js code outside of npm
If you have your own node.js application you may not publish it (or at
least not immediately) in an npm registry - it might just be in a
repository on github or on your local machine. Add support to recipetool
create for creating recipes to build such applications - extract their
dependencies, fetch them, and add corresponding npm:// URLs to SRC_URI,
and ensure that LICENSE / LIC_FILES_CHKSUM are updated to match. For
example, you can now run:
recipetool create https://github.com/diversario/node-ssdp
(I had to borrow some code from bitbake/lib/bb/fetch2/npm.py to
implement this functionality; this should be refactored out but now
isn't the time to do that refactoring.)
Part of the fix for [YOCTO #9537].
(From OE-Core rev: 4fb8b399c05a1b66986fc76e13525f6c5e0d9b58)
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r-- | meta/classes/npm.bbclass | 4 | ||||
-rw-r--r-- | scripts/lib/recipetool/create_npm.py | 111 |
2 files changed, 114 insertions, 1 deletions
diff --git a/meta/classes/npm.bbclass b/meta/classes/npm.bbclass index 55c7c3e278..fce4c1146f 100644 --- a/meta/classes/npm.bbclass +++ b/meta/classes/npm.bbclass | |||
@@ -16,6 +16,10 @@ def npm_oe_arch_map(target_arch, d): | |||
16 | NPM_ARCH ?= "${@npm_oe_arch_map(d.getVar('TARGET_ARCH', True), d)}" | 16 | NPM_ARCH ?= "${@npm_oe_arch_map(d.getVar('TARGET_ARCH', True), d)}" |
17 | 17 | ||
18 | npm_do_compile() { | 18 | npm_do_compile() { |
19 | # Copy in any additionally fetched modules | ||
20 | if [ -d ${WORKDIR}/node_modules ] ; then | ||
21 | cp -a ${WORKDIR}/node_modules ${S}/ | ||
22 | fi | ||
19 | # changing the home directory to the working directory, the .npmrc will | 23 | # changing the home directory to the working directory, the .npmrc will |
20 | # be created in this directory | 24 | # be created in this directory |
21 | export HOME=${WORKDIR} | 25 | export HOME=${WORKDIR} |
diff --git a/scripts/lib/recipetool/create_npm.py b/scripts/lib/recipetool/create_npm.py index e5aaa60bf8..e794614978 100644 --- a/scripts/lib/recipetool/create_npm.py +++ b/scripts/lib/recipetool/create_npm.py | |||
@@ -21,7 +21,7 @@ import subprocess | |||
21 | import tempfile | 21 | import tempfile |
22 | import shutil | 22 | import shutil |
23 | import json | 23 | import json |
24 | from recipetool.create import RecipeHandler, split_pkg_licenses | 24 | from recipetool.create import RecipeHandler, split_pkg_licenses, handle_license_vars |
25 | 25 | ||
26 | logger = logging.getLogger('recipetool') | 26 | logger = logging.getLogger('recipetool') |
27 | 27 | ||
@@ -83,6 +83,66 @@ class NpmRecipeHandler(RecipeHandler): | |||
83 | extravalues['extrafiles']['lockdown.json'] = tmpfile | 83 | extravalues['extrafiles']['lockdown.json'] = tmpfile |
84 | lines_before.append('NPM_LOCKDOWN := "${THISDIR}/${PN}/lockdown.json"') | 84 | lines_before.append('NPM_LOCKDOWN := "${THISDIR}/${PN}/lockdown.json"') |
85 | 85 | ||
86 | def _handle_dependencies(self, d, deps, lines_before, srctree): | ||
87 | import scriptutils | ||
88 | # If this isn't a single module we need to get the dependencies | ||
89 | # and add them to SRC_URI | ||
90 | def varfunc(varname, origvalue, op, newlines): | ||
91 | if varname == 'SRC_URI': | ||
92 | if not origvalue.startswith('npm://'): | ||
93 | src_uri = origvalue.split() | ||
94 | changed = False | ||
95 | for dep, depdata in deps.items(): | ||
96 | version = self.get_node_version(dep, depdata, d) | ||
97 | if version: | ||
98 | url = 'npm://registry.npmjs.org;name=%s;version=%s;subdir=node_modules/%s' % (dep, version, dep) | ||
99 | scriptutils.fetch_uri(d, url, srctree) | ||
100 | src_uri.append(url) | ||
101 | changed = True | ||
102 | if changed: | ||
103 | return src_uri, None, -1, True | ||
104 | return origvalue, None, 0, True | ||
105 | updated, newlines = bb.utils.edit_metadata(lines_before, ['SRC_URI'], varfunc) | ||
106 | if updated: | ||
107 | del lines_before[:] | ||
108 | for line in newlines: | ||
109 | # Hack to avoid newlines that edit_metadata inserts | ||
110 | if line.endswith('\n'): | ||
111 | line = line[:-1] | ||
112 | lines_before.append(line) | ||
113 | return updated | ||
114 | |||
115 | def _replace_license_vars(self, srctree, lines_before, handled, extravalues, d): | ||
116 | for item in handled: | ||
117 | if isinstance(item, tuple): | ||
118 | if item[0] == 'license': | ||
119 | del item | ||
120 | break | ||
121 | |||
122 | calledvars = [] | ||
123 | def varfunc(varname, origvalue, op, newlines): | ||
124 | if varname in ['LICENSE', 'LIC_FILES_CHKSUM']: | ||
125 | for i, e in enumerate(reversed(newlines)): | ||
126 | if not e.startswith('#'): | ||
127 | stop = i | ||
128 | while stop > 0: | ||
129 | newlines.pop() | ||
130 | stop -= 1 | ||
131 | break | ||
132 | calledvars.append(varname) | ||
133 | if len(calledvars) > 1: | ||
134 | # The second time around, put the new license text in | ||
135 | insertpos = len(newlines) | ||
136 | handle_license_vars(srctree, newlines, handled, extravalues, d) | ||
137 | return None, None, 0, True | ||
138 | return origvalue, None, 0, True | ||
139 | updated, newlines = bb.utils.edit_metadata(lines_before, ['LICENSE', 'LIC_FILES_CHKSUM'], varfunc) | ||
140 | if updated: | ||
141 | del lines_before[:] | ||
142 | lines_before.extend(newlines) | ||
143 | else: | ||
144 | raise Exception('Did not find license variables') | ||
145 | |||
86 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): | 146 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): |
87 | import bb.utils | 147 | import bb.utils |
88 | import oe | 148 | import oe |
@@ -108,6 +168,12 @@ class NpmRecipeHandler(RecipeHandler): | |||
108 | if 'homepage' in data: | 168 | if 'homepage' in data: |
109 | extravalues['HOMEPAGE'] = data['homepage'] | 169 | extravalues['HOMEPAGE'] = data['homepage'] |
110 | 170 | ||
171 | deps = data.get('dependencies', {}) | ||
172 | updated = self._handle_dependencies(tinfoil.config_data, deps, lines_before, srctree) | ||
173 | if updated: | ||
174 | # We need to redo the license stuff | ||
175 | self._replace_license_vars(srctree, lines_before, handled, extravalues, tinfoil.config_data) | ||
176 | |||
111 | # Shrinkwrap | 177 | # Shrinkwrap |
112 | localfilesdir = tempfile.mkdtemp(prefix='recipetool-npm') | 178 | localfilesdir = tempfile.mkdtemp(prefix='recipetool-npm') |
113 | self._shrinkwrap(srctree, localfilesdir, extravalues, lines_before) | 179 | self._shrinkwrap(srctree, localfilesdir, extravalues, lines_before) |
@@ -148,9 +214,52 @@ class NpmRecipeHandler(RecipeHandler): | |||
148 | lines_before[i] = 'LICENSE = "%s"' % ' & '.join(all_licenses) | 214 | lines_before[i] = 'LICENSE = "%s"' % ' & '.join(all_licenses) |
149 | break | 215 | break |
150 | 216 | ||
217 | # Need to move S setting after inherit npm | ||
218 | for i, line in enumerate(lines_before): | ||
219 | if line.startswith('S ='): | ||
220 | lines_before.pop(i) | ||
221 | lines_after.insert(0, '# Must be set after inherit npm since that itself sets S') | ||
222 | lines_after.insert(1, line) | ||
223 | break | ||
224 | |||
151 | return True | 225 | return True |
152 | 226 | ||
153 | return False | 227 | return False |
154 | 228 | ||
229 | # FIXME this is duplicated from lib/bb/fetch2/npm.py | ||
230 | def _parse_view(self, output): | ||
231 | ''' | ||
232 | Parse the output of npm view --json; the last JSON result | ||
233 | is assumed to be the one that we're interested in. | ||
234 | ''' | ||
235 | pdata = None | ||
236 | outdeps = {} | ||
237 | datalines = [] | ||
238 | bracelevel = 0 | ||
239 | for line in output.splitlines(): | ||
240 | if bracelevel: | ||
241 | datalines.append(line) | ||
242 | elif '{' in line: | ||
243 | datalines = [] | ||
244 | datalines.append(line) | ||
245 | bracelevel = bracelevel + line.count('{') - line.count('}') | ||
246 | if datalines: | ||
247 | pdata = json.loads('\n'.join(datalines)) | ||
248 | return pdata | ||
249 | |||
250 | # FIXME this is effectively duplicated from lib/bb/fetch2/npm.py | ||
251 | # (split out from _getdependencies()) | ||
252 | def get_node_version(self, pkg, version, d): | ||
253 | import bb.fetch2 | ||
254 | pkgfullname = pkg | ||
255 | if version != '*' and not '/' in version: | ||
256 | pkgfullname += "@'%s'" % version | ||
257 | logger.debug(2, "Calling getdeps on %s" % pkg) | ||
258 | runenv = dict(os.environ, PATH=d.getVar('PATH', True)) | ||
259 | fetchcmd = "npm view %s --json" % pkgfullname | ||
260 | output, _ = bb.process.run(fetchcmd, stderr=subprocess.STDOUT, env=runenv, shell=True) | ||
261 | data = self._parse_view(output) | ||
262 | return data.get('version', None) | ||
263 | |||
155 | def register_recipe_handlers(handlers): | 264 | def register_recipe_handlers(handlers): |
156 | handlers.append((NpmRecipeHandler(), 60)) | 265 | handlers.append((NpmRecipeHandler(), 60)) |