diff --git a/lib/layer.js b/lib/layer.js index fe2a4a00..6fe9ca4c 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -3,6 +3,7 @@ const fse = require('fs-extra'); const path = require('path'); const JSZip = require('jszip'); const { writeZip, addTree } = require('./zipTree'); +const { sha256Path, getRequirementsLayerPath } = require('./shared'); BbPromise.promisifyAll(fse); @@ -11,13 +12,49 @@ BbPromise.promisifyAll(fse); * @return {Promise} the JSZip object constructed. */ function zipRequirements() { - const rootZip = new JSZip(); const src = path.join('.serverless', 'requirements'); - const runtimepath = 'python'; - - return addTree(rootZip.folder(runtimepath), src).then(() => - writeZip(rootZip, path.join('.serverless', 'pythonRequirements.zip')) + const reqChecksum = sha256Path(path.join('.serverless', 'requirements.txt')); + const targetZipPath = path.join('.serverless', 'pythonRequirements.zip'); + const zipCachePath = getRequirementsLayerPath( + reqChecksum, + targetZipPath, + this.options, + this.serverless ); + + const promises = []; + if (fse.existsSync(zipCachePath)) { + let layerProgress; + if (this.progress && this.log) { + layerProgress = this.progress.get('python-layer-requirements'); + layerProgress.update( + 'Using cached Python Requirements Lambda Layer file' + ); + this.log.info('Found cached Python Requirements Lambda Layer file'); + } else { + this.serverless.cli.log( + 'Found cached Python Requirements Lambda Layer file' + ); + } + } else { + const rootZip = new JSZip(); + const runtimepath = 'python'; + + promises.push( + addTree(rootZip.folder(runtimepath), src).then(() => + writeZip(rootZip, zipCachePath) + ) + ); + } + return BbPromise.all(promises).then(() => { + if (zipCachePath !== targetZipPath) { + if (process.platform === 'win32') { + fse.copySync(zipCachePath, targetZipPath); + } else { + fse.symlink(zipCachePath, targetZipPath, 'file'); + } + } + }); } /** diff --git a/lib/shared.js b/lib/shared.js index 426d6c50..bebb3f09 100644 --- a/lib/shared.js +++ b/lib/shared.js @@ -86,6 +86,26 @@ function getRequirementsWorkingPath( return path.join(requirementsTxtDirectory, 'requirements'); } +/** + * Path of a cached requirements layer archive file + * @param {string} subfolder + * @param {string} fallback + * @param {Object} options + * @param {Object} serverless + * @return {string} + */ +function getRequirementsLayerPath(hash, fallback, options, serverless) { + // If we want to use the static cache + if (hash && options && options.useStaticCache) { + const architecture = serverless.service.provider.architecture || 'x86_64'; + hash = `${hash}_${architecture}_slspyc.zip`; + return path.join(getUserCachePath(options), hash); + } + + // If we don't want to use the static cache, then fallback to requirements file in .serverless directory + return fallback; +} + /** * The static cache path that will be used for this system + options, used if static cache is enabled * @param {Object} options @@ -117,6 +137,7 @@ function sha256Path(fullpath) { module.exports = { checkForAndDeleteMaxCacheVersions, getRequirementsWorkingPath, + getRequirementsLayerPath, getUserCachePath, sha256Path, };