@@ -3,7 +3,7 @@ const rimraf = require('rimraf');
3
3
const path = require ( 'path' ) ;
4
4
const get = require ( 'lodash.get' ) ;
5
5
const set = require ( 'lodash.set' ) ;
6
- const { spawnSync } = require ( 'child_process ' ) ;
6
+ const spawn = require ( 'child-process-ext/spawn ' ) ;
7
7
const { quote } = require ( 'shell-quote' ) ;
8
8
const { buildImage, getBindPath, getDockerUid } = require ( './docker' ) ;
9
9
const { getStripCommand, getStripMode, deleteFiles } = require ( './slim' ) ;
@@ -96,16 +96,23 @@ function generateRequirementsFile(
96
96
}
97
97
}
98
98
99
- function pipAcceptsSystem ( pythonBin ) {
99
+ async function pipAcceptsSystem ( pythonBin ) {
100
100
// Check if pip has Debian's --system option and set it if so
101
- const pipTestRes = spawnSync ( pythonBin , [ '-m' , 'pip' , 'help' , 'install' ] ) ;
102
- if ( pipTestRes . error ) {
103
- if ( pipTestRes . error . code === 'ENOENT' ) {
101
+ try {
102
+ const pipTestRes = await spawn ( pythonBin , [ '-m' , 'pip' , 'help' , 'install' ] ) ;
103
+ return (
104
+ pipTestRes . stdoutBuffer &&
105
+ pipTestRes . stdoutBuffer . toString ( ) . indexOf ( '--system' ) >= 0
106
+ ) ;
107
+ } catch ( e ) {
108
+ if (
109
+ e . stderrBuffer &&
110
+ e . stderrBuffer . toString ( ) . includes ( 'command not found' )
111
+ ) {
104
112
throw new Error ( `${ pythonBin } not found! Try the pythonBin option.` ) ;
105
113
}
106
- throw pipTestRes . error ;
114
+ throw e ;
107
115
}
108
- return pipTestRes . stdout . toString ( ) . indexOf ( '--system' ) >= 0 ;
109
116
}
110
117
111
118
/**
@@ -115,7 +122,7 @@ function pipAcceptsSystem(pythonBin) {
115
122
* @param {Object } options
116
123
* @return {undefined }
117
124
*/
118
- function installRequirements ( targetFolder , serverless , options ) {
125
+ async function installRequirements ( targetFolder , serverless , options ) {
119
126
const targetRequirementsTxt = path . join ( targetFolder , 'requirements.txt' ) ;
120
127
121
128
serverless . cli . log (
@@ -176,7 +183,7 @@ function installRequirements(targetFolder, serverless, options) {
176
183
pipCmd . push ( '--cache-dir' , downloadCacheDir ) ;
177
184
}
178
185
179
- if ( pipAcceptsSystem ( options . pythonBin ) ) {
186
+ if ( await pipAcceptsSystem ( options . pythonBin ) ) {
180
187
pipCmd . push ( '--system' ) ;
181
188
}
182
189
}
@@ -191,7 +198,7 @@ function installRequirements(targetFolder, serverless, options) {
191
198
serverless . cli . log (
192
199
`Building custom docker image from ${ options . dockerFile } ...`
193
200
) ;
194
- dockerImage = buildImage (
201
+ dockerImage = await buildImage (
195
202
options . dockerFile ,
196
203
options . dockerBuildCmdExtraArgs
197
204
) ;
@@ -201,7 +208,9 @@ function installRequirements(targetFolder, serverless, options) {
201
208
serverless . cli . log ( `Docker Image: ${ dockerImage } ` ) ;
202
209
203
210
// Prepare bind path depending on os platform
204
- const bindPath = dockerPathForWin ( getBindPath ( serverless , targetFolder ) ) ;
211
+ const bindPath = dockerPathForWin (
212
+ await getBindPath ( serverless , targetFolder )
213
+ ) ;
205
214
206
215
dockerCmd . push ( 'docker' , 'run' , '--rm' , '-v' , `${ bindPath } :/var/task:z` ) ;
207
216
if ( options . dockerSsh ) {
@@ -233,7 +242,7 @@ function installRequirements(targetFolder, serverless, options) {
233
242
fse . closeSync (
234
243
fse . openSync ( path . join ( downloadCacheDir , 'requirements.txt' ) , 'w' )
235
244
) ;
236
- const windowsized = getBindPath ( serverless , downloadCacheDir ) ;
245
+ const windowsized = await getBindPath ( serverless , downloadCacheDir ) ;
237
246
// And now push it to a volume mount and to pip...
238
247
dockerCmd . push ( '-v' , `${ windowsized } :${ dockerDownloadCacheDir } :z` ) ;
239
248
pipCmd . push ( '--cache-dir' , dockerDownloadCacheDir ) ;
@@ -262,7 +271,7 @@ function installRequirements(targetFolder, serverless, options) {
262
271
] ) ;
263
272
} else {
264
273
// Use same user so --cache-dir works
265
- dockerCmd . push ( '-u' , getDockerUid ( bindPath ) ) ;
274
+ dockerCmd . push ( '-u' , await getDockerUid ( bindPath ) ) ;
266
275
}
267
276
268
277
for ( let path of options . dockerExtraFiles ) {
@@ -315,22 +324,23 @@ function installRequirements(targetFolder, serverless, options) {
315
324
316
325
serverless . cli . log ( `Running ${ quote ( dockerCmd ) } ...` ) ;
317
326
318
- filterCommands ( mainCmds ) . forEach ( ( [ cmd , ...args ] ) => {
319
- const res = spawnSync ( cmd , args ) ;
320
- if ( res . error ) {
321
- if ( res . error . code === 'ENOENT' ) {
327
+ for ( const [ cmd , ...args ] of mainCmds ) {
328
+ try {
329
+ await spawn ( cmd , args ) ;
330
+ } catch ( e ) {
331
+ if (
332
+ e . stderrBuffer &&
333
+ e . stderrBuffer . toString ( ) . includes ( 'command not found' )
334
+ ) {
322
335
const advice =
323
336
cmd . indexOf ( 'python' ) > - 1
324
337
? 'Try the pythonBin option'
325
338
: 'Please install it' ;
326
339
throw new Error ( `${ cmd } not found! ${ advice } ` ) ;
327
340
}
328
- throw res . error ;
329
- }
330
- if ( res . status !== 0 ) {
331
- throw new Error ( `STDOUT: ${ res . stdout } \n\nSTDERR: ${ res . stderr } ` ) ;
341
+ throw e ;
332
342
}
333
- } ) ;
343
+ }
334
344
// If enabled slimming, delete files in slimPatterns
335
345
if ( options . slim === true || options . slim === 'true' ) {
336
346
deleteFiles ( options , targetFolder ) ;
@@ -489,7 +499,7 @@ function requirementsFileExists(servicePath, options, fileName) {
489
499
* @param {Object } serverless
490
500
* @return {string }
491
501
*/
492
- function installRequirementsIfNeeded (
502
+ async function installRequirementsIfNeeded (
493
503
servicePath ,
494
504
modulePath ,
495
505
options ,
@@ -573,7 +583,7 @@ function installRequirementsIfNeeded(
573
583
fse . copySync ( slsReqsTxt , path . join ( workingReqsFolder , 'requirements.txt' ) ) ;
574
584
575
585
// Then install our requirements from this folder
576
- installRequirements ( workingReqsFolder , serverless , options ) ;
586
+ await installRequirements ( workingReqsFolder , serverless , options ) ;
577
587
578
588
// Copy vendor libraries to requirements folder
579
589
if ( options . vendor ) {
@@ -596,63 +606,64 @@ function installRequirementsIfNeeded(
596
606
* pip install the requirements to the requirements directory
597
607
* @return {undefined }
598
608
*/
599
- function installAllRequirements ( ) {
609
+ async function installAllRequirements ( ) {
600
610
// fse.ensureDirSync(path.join(this.servicePath, '.serverless'));
601
611
// First, check and delete cache versions, if enabled
602
612
checkForAndDeleteMaxCacheVersions ( this . options , this . serverless ) ;
603
613
604
614
// Then if we're going to package functions individually...
605
615
if ( this . serverless . service . package . individually ) {
606
616
let doneModules = [ ] ;
607
- this . targetFuncs
608
- . filter ( ( func ) =>
609
- ( func . runtime || this . serverless . service . provider . runtime ) . match (
610
- / ^ p y t h o n .* /
611
- )
617
+ const filteredFuncs = this . targetFuncs . filter ( ( func ) =>
618
+ ( func . runtime || this . serverless . service . provider . runtime ) . match (
619
+ / ^ p y t h o n .* /
612
620
)
613
- . map ( ( f ) => {
614
- if ( ! get ( f , 'module' ) ) {
615
- set ( f , [ 'module' ] , '.' ) ;
616
- }
617
- // If we didn't already process a module (functions can re-use modules)
618
- if ( ! doneModules . includes ( f . module ) ) {
619
- const reqsInstalledAt = installRequirementsIfNeeded (
620
- this . servicePath ,
621
- f . module ,
622
- this . options ,
623
- f ,
624
- this . serverless
625
- ) ;
626
- // Add modulePath into .serverless for each module so it's easier for injecting and for users to see where reqs are
627
- let modulePath = path . join (
628
- this . servicePath ,
629
- ' .serverless' ,
630
- ` ${ f . module } ` ,
631
- 'requirements'
632
- ) ;
633
- // Only do if we didn't already do it
634
- if (
635
- reqsInstalledAt &&
636
- ! fse . existsSync ( modulePath ) &&
637
- reqsInstalledAt != modulePath
638
- ) {
639
- if ( this . options . useStaticCache ) {
640
- // Windows can't symlink so we have to copy on Windows,
641
- // it's not as fast, but at least it works
642
- if ( process . platform == 'win32' ) {
643
- fse . copySync ( reqsInstalledAt , modulePath ) ;
644
- } else {
645
- fse . symlink ( reqsInstalledAt , modulePath ) ;
646
- }
621
+ ) ;
622
+
623
+ for ( const f of filteredFuncs ) {
624
+ if ( ! get ( f , 'module' ) ) {
625
+ set ( f , [ ' module' ] , '.' ) ;
626
+ }
627
+
628
+ // If we didn't already process a module (functions can re-use modules)
629
+ if ( ! doneModules . includes ( f . module ) ) {
630
+ const reqsInstalledAt = await installRequirementsIfNeeded (
631
+ this . servicePath ,
632
+ f . module ,
633
+ this . options ,
634
+ f ,
635
+ this . serverless
636
+ ) ;
637
+ // Add modulePath into .serverless for each module so it's easier for injecting and for users to see where reqs are
638
+ let modulePath = path . join (
639
+ this . servicePath ,
640
+ '.serverless' ,
641
+ ` ${ f . module } ` ,
642
+ 'requirements'
643
+ ) ;
644
+ // Only do if we didn't already do it
645
+ if (
646
+ reqsInstalledAt &&
647
+ ! fse . existsSync ( modulePath ) &&
648
+ reqsInstalledAt != modulePath
649
+ ) {
650
+ if ( this . options . useStaticCache ) {
651
+ // Windows can't symlink so we have to copy on Windows,
652
+ // it's not as fast, but at least it works
653
+ if ( process . platform == 'win32' ) {
654
+ fse . copySync ( reqsInstalledAt , modulePath ) ;
647
655
} else {
648
- fse . rename ( reqsInstalledAt , modulePath ) ;
656
+ fse . symlink ( reqsInstalledAt , modulePath ) ;
649
657
}
658
+ } else {
659
+ fse . rename ( reqsInstalledAt , modulePath ) ;
650
660
}
651
- doneModules . push ( f . module ) ;
652
661
}
653
- } ) ;
662
+ doneModules . push ( f . module ) ;
663
+ }
664
+ }
654
665
} else {
655
- const reqsInstalledAt = installRequirementsIfNeeded (
666
+ const reqsInstalledAt = await installRequirementsIfNeeded (
656
667
this . servicePath ,
657
668
'' ,
658
669
this . options ,
0 commit comments