Skip to content

Commit b688824

Browse files
authored
Merge pull request #1 from vklab/vklab/per-function-requirements
allow per-function requirements plus base layer
2 parents 3233c60 + b40a70f commit b688824

File tree

4 files changed

+106
-23
lines changed

4 files changed

+106
-23
lines changed

lib/inject.js

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const set = require('lodash.set');
66
const path = require('path');
77
const JSZip = require('jszip');
88
const { writeZip, zipFile } = require('./zipTree');
9+
const { sha256Hash } = require('./shared')
910

1011
BbPromise.promisifyAll(fse);
1112

@@ -16,9 +17,13 @@ BbPromise.promisifyAll(fse);
1617
* @param {Object} options our options object
1718
* @return {Promise} the JSZip object constructed.
1819
*/
19-
function injectRequirements(requirementsPath, packagePath, options) {
20+
function injectRequirements(requirementsPath, packagePath, options, outputPackagePath) {
2021
const noDeploy = new Set(options.noDeploy || []);
2122

23+
if (!outputPackagePath){
24+
outputPackagePath = packagePath;
25+
}
26+
2227
return fse
2328
.readFileAsync(packagePath)
2429
.then(buffer => JSZip.loadAsync(buffer))
@@ -45,7 +50,7 @@ function injectRequirements(requirementsPath, packagePath, options) {
4550
createFolders: false
4651
})
4752
)
48-
.then(() => writeZip(zip, packagePath))
53+
.then(() => writeZip(zip, outputPackagePath))
4954
);
5055
}
5156

@@ -87,7 +92,7 @@ function moveModuleUp(source, target, module) {
8792
* @return {Promise} the combined promise for requirements injection.
8893
*/
8994
function injectAllRequirements(funcArtifact) {
90-
if (this.options.layer) {
95+
if (this.options.layer && !this.serverless.service.package.individually) {
9196
// The requirements will be placed in a Layer, so just resolve
9297
return BbPromise.resolve();
9398
}
@@ -118,18 +123,27 @@ function injectAllRequirements(funcArtifact) {
118123
return moveModuleUp(artifact, newArtifact, func.module).then(
119124
() => func
120125
);
121-
} else {
126+
} else {
122127
return func;
123128
}
124129
})
125130
.map(func => {
126-
return this.options.zip
127-
? func
128-
: injectRequirements(
129-
path.join('.serverless', func.module, 'requirements'),
130-
func.package.artifact,
131-
this.options
132-
);
131+
if (this.options.zip) {
132+
return func
133+
} else if (get(func, 'pythonRequirements.fileName')){
134+
const extraRequirementsPath = get(func, 'pythonRequirements.fileName');
135+
const extraRequirementsPathHash = sha256Hash(extraRequirementsPath);
136+
return injectRequirements(
137+
path.join('.serverless', extraRequirementsPathHash, 'requirements'),
138+
func.package.artifact,
139+
this.options
140+
);
141+
}
142+
return injectRequirements(
143+
path.join('.serverless', func.module, 'requirements'),
144+
func.package.artifact,
145+
this.options
146+
);
133147
});
134148
} else if (!this.options.zip) {
135149
return injectRequirements(

lib/pip.js

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ const {
1111
checkForAndDeleteMaxCacheVersions,
1212
sha256Path,
1313
getRequirementsWorkingPath,
14-
getUserCachePath
14+
getUserCachePath,
15+
sha256Hash,
1516
} = require('./shared');
1617

18+
1719
/**
1820
* Omit empty commands.
1921
* In this context, a "command" is a list of arguments. An empty list or falsy value is ommitted.
@@ -469,23 +471,28 @@ function installRequirementsIfNeeded(
469471
modulePath,
470472
options,
471473
funcOptions,
472-
serverless
474+
serverless,
475+
extraTargetPath
473476
) {
474477
// Our source requirements, under our service path, and our module path (if specified)
475478
const fileName = path.join(servicePath, modulePath, options.fileName);
479+
if (!extraTargetPath) {
480+
extraTargetPath = modulePath;
481+
}
476482

477483
// Skip requirements generation, if requirements file doesn't exist
478484
if (!requirementsFileExists(servicePath, options, fileName)) {
485+
serverless.cli.log(`Could not find requirements file at ${fileName}`)
479486
return false;
480487
}
481488

482489
let requirementsTxtDirectory;
483490
// Copy our requirements to another path in .serverless (incase of individually packaged)
484-
if (modulePath && modulePath !== '.') {
491+
if (extraTargetPath && extraTargetPath !== '.') {
485492
requirementsTxtDirectory = path.join(
486493
servicePath,
487494
'.serverless',
488-
modulePath
495+
extraTargetPath
489496
);
490497
} else {
491498
requirementsTxtDirectory = path.join(servicePath, '.serverless');
@@ -579,6 +586,7 @@ function installAllRequirements() {
579586
// Then if we're going to package functions individually...
580587
if (this.serverless.service.package.individually) {
581588
let doneModules = [];
589+
let doneExtraRequirements = [];
582590
this.targetFuncs
583591
.filter(func =>
584592
(func.runtime || this.serverless.service.provider.runtime).match(
@@ -617,14 +625,61 @@ function installAllRequirements() {
617625
if (process.platform == 'win32') {
618626
fse.copySync(reqsInstalledAt, modulePath);
619627
} else {
620-
fse.symlink(reqsInstalledAt, modulePath);
628+
fse.symlinkSync(reqsInstalledAt, modulePath);
621629
}
622630
} else {
623-
fse.rename(reqsInstalledAt, modulePath);
631+
fse.renameSync(reqsInstalledAt, modulePath);
624632
}
625633
}
626634
doneModules.push(f.module);
627635
}
636+
return f;
637+
})
638+
.map(f => {
639+
const extraRequirementsPath = get(f, 'pythonRequirements.fileName');
640+
this.serverless.cli.log(`Found extra requirements ${extraRequirementsPath} for ${f.name}`)
641+
if (extraRequirementsPath) {
642+
const extraRequirementsPathHash = sha256Hash(extraRequirementsPath);
643+
// this.serverless.cli.log(`Computed path hash ${extraRequirementsPathHash}, doneExtraRequirements=${doneExtraRequirements}`)
644+
// If we didn't already process a module (functions can re-use modules)
645+
if (!doneExtraRequirements.includes(extraRequirementsPath)) {
646+
const reqsInstalledAt = installRequirementsIfNeeded(
647+
this.servicePath,
648+
'',
649+
Object.assign({}, this.options, f.pythonRequirements),
650+
f,
651+
this.serverless,
652+
extraRequirementsPathHash
653+
);
654+
// this.serverless.cli.log(`Installed reqs to ${reqsInstalledAt}`)
655+
// Add modulePath into .serverless for each module so it's easier for injecting and for users to see where reqs are
656+
let extraRequirementsDirectoryPath = path.join(
657+
this.servicePath,
658+
'.serverless',
659+
extraRequirementsPathHash,
660+
'requirements'
661+
);
662+
// Only do if we didn't already do it
663+
if (
664+
reqsInstalledAt &&
665+
!fse.existsSync(extraRequirementsDirectoryPath) &&
666+
reqsInstalledAt != extraRequirementsDirectoryPath
667+
) {
668+
if (this.options.useStaticCache) {
669+
// Windows can't symlink so we have to copy on Windows,
670+
// it's not as fast, but at least it works
671+
if (process.platform == 'win32') {
672+
fse.copySync(reqsInstalledAt, extraRequirementsDirectoryPath);
673+
} else {
674+
fse.symlinkSync(reqsInstalledAt, extraRequirementsDirectoryPath);
675+
}
676+
} else {
677+
fse.renameSync(reqsInstalledAt, extraRequirementsDirectoryPath);
678+
}
679+
}
680+
doneExtraRequirements.push(extraRequirementsPath);
681+
}
682+
}
628683
});
629684
} else {
630685
const reqsInstalledAt = installRequirementsIfNeeded(

lib/shared.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ const glob = require('glob-all');
44
const path = require('path');
55
const fse = require('fs-extra');
66
const sha256File = require('sha256-file');
7+
const { createHash } = require('crypto')
8+
const sha256Hash = (s) => {
9+
const hash = createHash('sha256');
10+
hash.update(s);
11+
return hash.digest('hex');
12+
}
713

814
/**
915
* This helper will check if we're using static cache and have max
@@ -108,5 +114,6 @@ module.exports = {
108114
checkForAndDeleteMaxCacheVersions,
109115
getRequirementsWorkingPath,
110116
getUserCachePath,
111-
sha256Path
117+
sha256Path,
118+
sha256Hash
112119
};

lib/zip.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const uniqBy = require('lodash.uniqby');
66
const BbPromise = require('bluebird');
77
const JSZip = require('jszip');
88
const { addTree, writeZip } = require('./zipTree');
9+
const { sha256Hash } = require('./shared')
910

1011
BbPromise.promisifyAll(fse);
1112

@@ -101,16 +102,22 @@ function packRequirements() {
101102
}
102103
return f;
103104
})
104-
.then(funcs => uniqBy(funcs, f => f.module))
105+
.then(funcs => uniqBy(funcs, f => get(f, 'pythonRequirements.fileName', f.module)))
105106
.map(f => {
107+
const extraRequirementsPath = get(f, 'pythonRequirements.fileName');
108+
let extraRequirementsPathHash = f.module;
109+
if (extraRequirementsPath){
110+
extraRequirementsPathHash = sha256Hash(extraRequirementsPath);
111+
}
112+
106113
this.serverless.cli.log(
107-
`Zipping required Python packages for ${f.module}...`
114+
`Zipping required Python packages for ${extraRequirementsPathHash}...`
108115
);
109-
f.package.include.push(`${f.module}/.requirements.zip`);
116+
f.package.include.push(`${extraRequirementsPathHash}/.requirements.zip`);
110117
return addTree(
111118
new JSZip(),
112-
`.serverless/${f.module}/requirements`
113-
).then(zip => writeZip(zip, `${f.module}/.requirements.zip`));
119+
`.serverless/${extraRequirementsPathHash}/requirements`
120+
).then(zip => writeZip(zip, `${extraRequirementsPathHash}/.requirements.zip`));
114121
});
115122
} else {
116123
this.serverless.cli.log('Zipping required Python packages...');

0 commit comments

Comments
 (0)