Skip to content

Commit 5614ab0

Browse files
dee-me-tree-or-lovedschep
authored andcommitted
Reduce size of installed dependencies by stripping (serverless#191)
1 parent 56beaf5 commit 5614ab0

File tree

9 files changed

+271
-1
lines changed

9 files changed

+271
-1
lines changed

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,28 @@ try:
9595
except ImportError:
9696
pass
9797
```
98-
98+
### Slim Package
99+
_Works on non 'win32' environments: Docker, WSL are included_
100+
To remove the tests, information and caches from the installed packages,
101+
enable the `slim` option. This will: `strip` the `.so` files, remove `__pycache__`
102+
directories and `dist-info` directories.
103+
```yaml
104+
custom:
105+
pythonRequirements:
106+
slim: true
107+
```
108+
#### Custom Removal Patterns
109+
To specify additional directories to remove from the installed packages,
110+
define the patterns using regex as a `slimPatterns` option in serverless config:
111+
```yaml
112+
custom:
113+
pythonRequirements:
114+
slim: true
115+
slimPatterns:
116+
- "*.egg-info*"
117+
```
118+
This will remove all folders within the installed requirements that match
119+
the names in `slimPatterns`
99120
## Omitting Packages
100121
You can omit a package from deployment with the `noDeploy` option. Note that
101122
dependencies of omitted packages must explicitly be omitted too.
@@ -266,3 +287,5 @@ For usage of `dockerizePip` on Windows do Step 1 only if running serverless on w
266287
* [@kichik](https://github.com/kichik) - Imposed windows & `noDeploy` support,
267288
switched to adding files straight to zip instead of creating symlinks, and
268289
improved pip chache support when using docker.
290+
* [@dee-me-tree-or-love](https://github.com/dee-me-tree-or-love) - the `slim` package option
291+

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class ServerlessPythonRequirements {
2626
get options() {
2727
const options = Object.assign(
2828
{
29+
slim: false,
2930
zip: false,
3031
cleanupZipHelper: true,
3132
invalidateCaches: false,

lib/pip.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const set = require('lodash.set');
66
const { spawnSync } = require('child_process');
77
const values = require('lodash.values');
88
const { buildImage, getBindPath, getDockerUid } = require('./docker');
9+
const { getSlimPackageCommands } = require('./slim');
910

1011
/**
1112
* Install requirements described in requirementsPath to targetFolder
@@ -122,6 +123,14 @@ function installRequirements(
122123
cmd = pipCmd[0];
123124
cmdOptions = pipCmd.slice(1);
124125
}
126+
127+
// If enabled slimming, strip out the caches, tests and dist-infos
128+
if (options.slim === true || options.slim === 'true') {
129+
const preparedPath = dockerPathForWin(options, targetRequirementsFolder);
130+
const slimCmd = getSlimPackageCommands(options, preparedPath);
131+
cmdOptions.push(...slimCmd);
132+
}
133+
125134
const res = spawnSync(cmd, cmdOptions, { cwd: servicePath, shell: true });
126135
if (res.error) {
127136
if (res.error.code === 'ENOENT') {

lib/slim.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const isWsl = require('is-wsl');
2+
3+
/**
4+
* Get commands to slim the installed requirements
5+
* only for non-windows platforms:
6+
* works for docker builds and when run on UNIX platforms (wsl included)
7+
* @param {Object} options
8+
* @param {string} folderPath
9+
* @return {Array.<String>}
10+
*/
11+
function getSlimPackageCommands(options, folderPath) {
12+
let stripCmd = [];
13+
14+
// Default stripping is done for non-windows environments
15+
if (process.platform !== 'win32' || isWsl) {
16+
stripCmd = getDefaultSLimOptions(folderPath);
17+
18+
// If specified any custom patterns to remove
19+
if (options.slimPatterns instanceof Array) {
20+
// Add the custom specified patterns to remove to the default commands
21+
const customPatterns = options.slimPatterns.map(pattern => {
22+
return getRemovalCommand(folderPath, pattern);
23+
});
24+
stripCmd = stripCmd.concat(customPatterns);
25+
}
26+
}
27+
return stripCmd;
28+
}
29+
30+
/**
31+
* Gets the commands to slim the default (safe) files:
32+
* including removing caches, stripping compiled files, removing dist-infos
33+
* @param {String} folderPath
34+
* @return {Array}
35+
*/
36+
function getDefaultSLimOptions(folderPath) {
37+
return [
38+
`&& find ${folderPath} -name "*.so" -exec strip {} \\;`,
39+
`&& find ${folderPath} -name "*.py[c|o]" -exec rm -rf {} +`,
40+
`&& find ${folderPath} -type d -name "__pycache__*" -exec rm -rf {} +`,
41+
`&& find ${folderPath} -type d -name "*.dist-info*" -exec rm -rf {} +`
42+
];
43+
}
44+
45+
/**
46+
* Get the command created fromt he find and remove template:
47+
* returns a string in form `&& find <folder> -name "<match>" -exec rm -rf {} +`
48+
* @param {String} folderPath
49+
* @param {String} removalMatch
50+
* @return {String}
51+
*/
52+
function getRemovalCommand(folderPath, removalMatch) {
53+
return `&& find ${folderPath} -type d -name "${removalMatch}" -exec rm -rf {} +`;
54+
}
55+
56+
module.exports = {
57+
getSlimPackageCommands,
58+
getDefaultSLimOptions
59+
};

test.bats

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ teardown() {
3232
! ls puck/flask
3333
}
3434

35+
@test "py3.6 can package flask with slim options" {
36+
cd tests/base
37+
npm i $(npm pack ../..)
38+
sls --slim=true package
39+
unzip .serverless/sls-py-req-test.zip -d puck
40+
ls puck/flask
41+
test $(find puck -name "*.pyc" | wc -l) -eq 0
42+
}
43+
44+
@test "py3.6 can package flask with slim & slimPatterns options" {
45+
cd tests/base
46+
mv _slimPatterns.yml slimPatterns.yml
47+
npm i $(npm pack ../..)
48+
sls --slim=true package
49+
mv slimPatterns.yml _slimPatterns.yml
50+
unzip .serverless/sls-py-req-test.zip -d puck
51+
ls puck/flask
52+
test $(find puck -name "*.pyc" | wc -l) -eq 0
53+
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
54+
}
55+
3556
@test "py3.6 doesn't package boto3 by default" {
3657
cd tests/base
3758
npm i $(npm pack ../..)
@@ -59,6 +80,15 @@ teardown() {
5980
ls puck/.requirements.zip puck/unzip_requirements.py
6081
}
6182

83+
@test "py3.6 can package flask with zip & slim & dockerizePip option" {
84+
cd tests/base
85+
npm i $(npm pack ../..)
86+
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
87+
sls --dockerizePip=true --zip=true --slim=true package
88+
unzip .serverless/sls-py-req-test.zip -d puck
89+
ls puck/.requirements.zip puck/unzip_requirements.py
90+
}
91+
6292
@test "py3.6 can package flask with dockerizePip option" {
6393
cd tests/base
6494
npm i $(npm pack ../..)
@@ -68,6 +98,29 @@ teardown() {
6898
ls puck/flask
6999
}
70100

101+
@test "py3.6 can package flask with slim & dockerizePip option" {
102+
cd tests/base
103+
npm i $(npm pack ../..)
104+
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
105+
sls --dockerizePip=true --slim=true package
106+
unzip .serverless/sls-py-req-test.zip -d puck
107+
ls puck/flask
108+
test $(find puck -name "*.pyc" | wc -l) -eq 0
109+
}
110+
111+
@test "py3.6 can package flask with slim & dockerizePip & slimPatterns options" {
112+
cd tests/base
113+
mv _slimPatterns.yml slimPatterns.yml
114+
npm i $(npm pack ../..)
115+
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
116+
sls --dockerizePip=true --slim=true package
117+
mv slimPatterns.yml _slimPatterns.yml
118+
unzip .serverless/sls-py-req-test.zip -d puck
119+
ls puck/flask
120+
test $(find puck -name "*.pyc" | wc -l) -eq 0
121+
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
122+
}
123+
71124
@test "py3.6 uses cache with dockerizePip option" {
72125
cd tests/base
73126
npm i $(npm pack ../..)
@@ -77,6 +130,17 @@ teardown() {
77130
ls .requirements-cache/http
78131
}
79132

133+
@test "py3.6 uses cache with dockerizePip & slim option" {
134+
cd tests/base
135+
npm i $(npm pack ../..)
136+
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
137+
perl -p -i'.bak' -e 's/(pythonRequirements:$)/\1\n pipCmdExtraArgs: ["--cache-dir", ".requirements-cache"]/' serverless.yml
138+
sls --dockerizePip=true --slim=true package
139+
ls .requirements-cache/http
140+
test $(find puck -name "*.pyc" | wc -l) -eq 0
141+
}
142+
143+
80144
@test "py2.7 can package flask with default options" {
81145
cd tests/base
82146
npm i $(npm pack ../..)
@@ -85,6 +149,15 @@ teardown() {
85149
ls puck/flask
86150
}
87151

152+
@test "py2.7 can package flask with slim option" {
153+
cd tests/base
154+
npm i $(npm pack ../..)
155+
sls --runtime=python2.7 --slim=true package
156+
unzip .serverless/sls-py-req-test.zip -d puck
157+
ls puck/flask
158+
test $(find puck -name "*.pyc" | wc -l) -eq 0
159+
}
160+
88161
@test "py2.7 can package flask with zip option" {
89162
cd tests/base
90163
npm i $(npm pack ../..)
@@ -93,6 +166,18 @@ teardown() {
93166
ls puck/.requirements.zip puck/unzip_requirements.py
94167
}
95168

169+
@test "py2.7 can package flask with slim & dockerizePip & slimPatterns options" {
170+
cd tests/base
171+
mv _slimPatterns.yml slimPatterns.yml
172+
npm i $(npm pack ../..)
173+
sls --runtime=python2.7 --slim=true packag
174+
mv slimPatterns.yml _slimPatterns.yml
175+
unzip .serverless/sls-py-req-test.zip -d puck
176+
ls puck/flask
177+
test $(find puck -name "*.pyc" | wc -l) -eq 0
178+
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
179+
}
180+
96181
@test "py2.7 doesn't package boto3 by default" {
97182
cd tests/base
98183
npm i $(npm pack ../..)
@@ -119,6 +204,15 @@ teardown() {
119204
ls puck/.requirements.zip puck/unzip_requirements.py
120205
}
121206

207+
@test "py2.7 can package flask with zip & slim & dockerizePip option" {
208+
cd tests/base
209+
npm i $(npm pack ../..)
210+
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
211+
sls --dockerizePip=true --runtime=python2.7 --zip=true --slim=true package
212+
unzip .serverless/sls-py-req-test.zip -d puck
213+
ls puck/.requirements.zip puck/unzip_requirements.py
214+
}
215+
122216
@test "py2.7 can package flask with dockerizePip option" {
123217
cd tests/base
124218
npm i $(npm pack ../..)
@@ -128,6 +222,29 @@ teardown() {
128222
ls puck/flask
129223
}
130224

225+
@test "py2.7 can package flask with slim & dockerizePip option" {
226+
cd tests/base
227+
npm i $(npm pack ../..)
228+
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
229+
sls --dockerizePip=true --slim=true --runtime=python2.7 package
230+
unzip .serverless/sls-py-req-test.zip -d puck
231+
ls puck/flask
232+
test $(find puck -name "*.pyc" | wc -l) -eq 0
233+
}
234+
235+
@test "py2.7 can package flask with slim & dockerizePip & slimPatterns options" {
236+
cd tests/base
237+
mv _slimPatterns.yml slimPatterns.yml
238+
npm i $(npm pack ../..)
239+
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
240+
sls --dockerizePip=true --slim=true --runtime=python2.7 package
241+
mv slimPatterns.yml _slimPatterns.yml
242+
unzip .serverless/sls-py-req-test.zip -d puck
243+
ls puck/flask
244+
test $(find puck -name "*.pyc" | wc -l) -eq 0
245+
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
246+
}
247+
131248
@test "pipenv py3.6 can package flask with default options" {
132249
cd tests/pipenv
133250
npm i $(npm pack ../..)
@@ -136,6 +253,27 @@ teardown() {
136253
ls puck/flask
137254
}
138255

256+
@test "pipenv py3.6 can package flask with slim option" {
257+
cd tests/pipenv
258+
npm i $(npm pack ../..)
259+
sls --slim=true package
260+
unzip .serverless/sls-py-req-test.zip -d puck
261+
ls puck/flask
262+
test $(find puck -name "*.pyc" | wc -l) -eq 0
263+
}
264+
265+
@test "pipenv py3.6 can package flask with slim & slimPatterns option" {
266+
cd tests/pipenv
267+
npm i $(npm pack ../..)
268+
mv _slimPatterns.yml slimPatterns.yml
269+
sls --slim=true package
270+
mv slimPatterns.yml _slimPatterns.yml
271+
unzip .serverless/sls-py-req-test.zip -d puck
272+
ls puck/flask
273+
test $(find puck -name "*.pyc" | wc -l) -eq 0
274+
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
275+
}
276+
139277
@test "pipenv py3.6 can package flask with zip option" {
140278
cd tests/pipenv
141279
npm i $(npm pack ../..)
@@ -182,6 +320,20 @@ teardown() {
182320
! ls puck3/flask
183321
}
184322

323+
@test "py3.6 can package flask with package individually & slim option" {
324+
cd tests/base
325+
npm i $(npm pack ../..)
326+
sls --individually=true --slim=true package
327+
unzip .serverless/hello.zip -d puck
328+
unzip .serverless/hello2.zip -d puck2
329+
unzip .serverless/hello3.zip -d puck3
330+
ls puck/flask
331+
ls puck2/flask
332+
! ls puck3/flask
333+
test $(find "puck*" -name "*.pyc" | wc -l) -eq 0
334+
}
335+
336+
185337
@test "py2.7 can package flask with package individually option" {
186338
cd tests/base
187339
npm i $(npm pack ../..)
@@ -194,6 +346,20 @@ teardown() {
194346
! ls puck3/flask
195347
}
196348

349+
@test "py2.7 can package flask with package individually & slim option" {
350+
cd tests/base
351+
npm i $(npm pack ../..)
352+
sls --individually=true --slim=true --runtime=python2.7 package
353+
unzip .serverless/hello.zip -d puck
354+
unzip .serverless/hello2.zip -d puck2
355+
unzip .serverless/hello3.zip -d puck3
356+
ls puck/flask
357+
ls puck2/flask
358+
! ls puck3/flask
359+
test $(find puck* -name "*.pyc" | wc -l) -eq 0
360+
}
361+
362+
197363
@test "py3.6 can package only requirements of module" {
198364
cd tests/individually
199365
npm i $(npm pack ../..)

tests/base/_slimPatterns.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
slimPatterns:
2+
- "*.egg-info*"

tests/base/serverless.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ custom:
1010
pythonRequirements:
1111
zip: ${opt:zip, self:custom.defaults.zip}
1212
dockerizePip: ${opt:dockerizePip, self:custom.defaults.dockerizePip}
13+
slim: ${opt:slim, self:custom.defaults.slim}
14+
slimPatterns: ${file(./slimPatterns.yml):slimPatterns, self:custom.defaults.slimPatterns}
1315
vendor: ${opt:vendor, ''}
1416
defaults:
17+
slim: false
18+
slimPatterns: false
1519
zip: false
1620
dockerizePip: false
1721
individually: false

tests/pipenv/_slimPatterns.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
slimPatterns:
2+
- "*.egg-info*"

0 commit comments

Comments
 (0)