recipetool: create: shrinkwrap and lockdown npm modules

"npm shrinkwrap" creates a file that ensures that the exact same
versions get fetched the next time the recipe is built. lockdown is
similar but also includes sha1sums of the modules thus validating they
haven't changed between builds. These ensure that the build is
reproducible.

Fixes [YOCTO #9225].

(From OE-Core rev: 277377f13b2b771915eb853e336ca24b84523ed1)

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Paul Eggleton 2016-03-09 17:48:55 +13:00 committed by Richard Purdie
parent 309b2e6c57
commit bc0e99d2b1
1 changed files with 57 additions and 0 deletions

View File

@ -15,14 +15,27 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import logging
import subprocess
import tempfile
import shutil
import json
from recipetool.create import RecipeHandler, split_pkg_licenses
logger = logging.getLogger('recipetool')
tinfoil = None
def tinfoil_init(instance):
global tinfoil
tinfoil = instance
class NpmRecipeHandler(RecipeHandler):
lockdownpath = None
def _handle_license(self, data):
'''
Handle the license value from an npm package.json file
@ -34,7 +47,44 @@ class NpmRecipeHandler(RecipeHandler):
license = license.get('type', None)
return None
def _shrinkwrap(self, srctree, localfilesdir, extravalues, lines_before):
try:
runenv = dict(os.environ, PATH=tinfoil.config_data.getVar('PATH', True))
bb.process.run('npm shrinkwrap', cwd=srctree, stderr=subprocess.STDOUT, env=runenv, shell=True)
except bb.process.ExecutionError as e:
logger.warn('npm shrinkwrap failed:\n%s' % e.stdout)
return
tmpfile = os.path.join(localfilesdir, 'npm-shrinkwrap.json')
shutil.move(os.path.join(srctree, 'npm-shrinkwrap.json'), tmpfile)
extravalues.setdefault('extrafiles', {})
extravalues['extrafiles']['npm-shrinkwrap.json'] = tmpfile
lines_before.append('NPM_SHRINKWRAP := "${THISDIR}/${PN}/npm-shrinkwrap.json"')
def _lockdown(self, srctree, localfilesdir, extravalues, lines_before):
runenv = dict(os.environ, PATH=tinfoil.config_data.getVar('PATH', True))
if not NpmRecipeHandler.lockdownpath:
NpmRecipeHandler.lockdownpath = tempfile.mkdtemp('recipetool-npm-lockdown')
bb.process.run('npm install lockdown --prefix %s' % NpmRecipeHandler.lockdownpath,
cwd=srctree, stderr=subprocess.STDOUT, env=runenv, shell=True)
relockbin = os.path.join(NpmRecipeHandler.lockdownpath, 'node_modules', 'lockdown', 'relock.js')
if not os.path.exists(relockbin):
logger.warn('Could not find relock.js within lockdown directory; skipping lockdown')
return
try:
bb.process.run('node %s' % relockbin, cwd=srctree, stderr=subprocess.STDOUT, env=runenv, shell=True)
except bb.process.ExecutionError as e:
logger.warn('lockdown-relock failed:\n%s' % e.stdout)
return
tmpfile = os.path.join(localfilesdir, 'lockdown.json')
shutil.move(os.path.join(srctree, 'lockdown.json'), tmpfile)
extravalues.setdefault('extrafiles', {})
extravalues['extrafiles']['lockdown.json'] = tmpfile
lines_before.append('NPM_LOCKDOWN := "${THISDIR}/${PN}/lockdown.json"')
def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
import bb.utils
import oe
from collections import OrderedDict
@ -58,6 +108,13 @@ class NpmRecipeHandler(RecipeHandler):
if 'homepage' in data:
lines_before.append('HOMEPAGE = "%s"' % data['homepage'])
# Shrinkwrap
localfilesdir = tempfile.mkdtemp(prefix='recipetool-npm')
self._shrinkwrap(srctree, localfilesdir, extravalues, lines_before)
# Lockdown
self._lockdown(srctree, localfilesdir, extravalues, lines_before)
# Split each npm module out to is own package
npmpackages = oe.package.npm_split_package_dirs(srctree)
for item in handled: