@@ -3,7 +3,7 @@ const rimraf = require('rimraf');
33const path = require ( 'path' ) ;
44const get = require ( 'lodash.get' ) ;
55const set = require ( 'lodash.set' ) ;
6- const { spawnSync } = require ( 'child_process ' ) ;
6+ const spawn = require ( 'child-process-ext/spawn ' ) ;
77const { quote } = require ( 'shell-quote' ) ;
88const { buildImage, getBindPath, getDockerUid } = require ( './docker' ) ;
99const { getStripCommand, getStripMode, deleteFiles } = require ( './slim' ) ;
@@ -96,16 +96,23 @@ function generateRequirementsFile(
9696 }
9797}
9898
99- function pipAcceptsSystem ( pythonBin ) {
99+ async function pipAcceptsSystem ( pythonBin ) {
100100 // 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+ ) {
104112 throw new Error ( `${ pythonBin } not found! Try the pythonBin option.` ) ;
105113 }
106- throw pipTestRes . error ;
114+ throw e ;
107115 }
108- return pipTestRes . stdout . toString ( ) . indexOf ( '--system' ) >= 0 ;
109116}
110117
111118/**
@@ -115,7 +122,7 @@ function pipAcceptsSystem(pythonBin) {
115122 * @param {Object } options
116123 * @return {undefined }
117124 */
118- function installRequirements ( targetFolder , serverless , options ) {
125+ async function installRequirements ( targetFolder , serverless , options ) {
119126 const targetRequirementsTxt = path . join ( targetFolder , 'requirements.txt' ) ;
120127
121128 serverless . cli . log (
@@ -176,7 +183,7 @@ function installRequirements(targetFolder, serverless, options) {
176183 pipCmd . push ( '--cache-dir' , downloadCacheDir ) ;
177184 }
178185
179- if ( pipAcceptsSystem ( options . pythonBin ) ) {
186+ if ( await pipAcceptsSystem ( options . pythonBin ) ) {
180187 pipCmd . push ( '--system' ) ;
181188 }
182189 }
@@ -191,7 +198,7 @@ function installRequirements(targetFolder, serverless, options) {
191198 serverless . cli . log (
192199 `Building custom docker image from ${ options . dockerFile } ...`
193200 ) ;
194- dockerImage = buildImage (
201+ dockerImage = await buildImage (
195202 options . dockerFile ,
196203 options . dockerBuildCmdExtraArgs
197204 ) ;
@@ -201,7 +208,9 @@ function installRequirements(targetFolder, serverless, options) {
201208 serverless . cli . log ( `Docker Image: ${ dockerImage } ` ) ;
202209
203210 // Prepare bind path depending on os platform
204- const bindPath = dockerPathForWin ( getBindPath ( serverless , targetFolder ) ) ;
211+ const bindPath = dockerPathForWin (
212+ await getBindPath ( serverless , targetFolder )
213+ ) ;
205214
206215 dockerCmd . push ( 'docker' , 'run' , '--rm' , '-v' , `${ bindPath } :/var/task:z` ) ;
207216 if ( options . dockerSsh ) {
@@ -233,7 +242,7 @@ function installRequirements(targetFolder, serverless, options) {
233242 fse . closeSync (
234243 fse . openSync ( path . join ( downloadCacheDir , 'requirements.txt' ) , 'w' )
235244 ) ;
236- const windowsized = getBindPath ( serverless , downloadCacheDir ) ;
245+ const windowsized = await getBindPath ( serverless , downloadCacheDir ) ;
237246 // And now push it to a volume mount and to pip...
238247 dockerCmd . push ( '-v' , `${ windowsized } :${ dockerDownloadCacheDir } :z` ) ;
239248 pipCmd . push ( '--cache-dir' , dockerDownloadCacheDir ) ;
@@ -262,7 +271,7 @@ function installRequirements(targetFolder, serverless, options) {
262271 ] ) ;
263272 } else {
264273 // Use same user so --cache-dir works
265- dockerCmd . push ( '-u' , getDockerUid ( bindPath ) ) ;
274+ dockerCmd . push ( '-u' , await getDockerUid ( bindPath ) ) ;
266275 }
267276
268277 for ( let path of options . dockerExtraFiles ) {
@@ -315,22 +324,23 @@ function installRequirements(targetFolder, serverless, options) {
315324
316325 serverless . cli . log ( `Running ${ quote ( dockerCmd ) } ...` ) ;
317326
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+ ) {
322335 const advice =
323336 cmd . indexOf ( 'python' ) > - 1
324337 ? 'Try the pythonBin option'
325338 : 'Please install it' ;
326339 throw new Error ( `${ cmd } not found! ${ advice } ` ) ;
327340 }
328- throw res . error ;
329- }
330- if ( res . status !== 0 ) {
331- throw new Error ( `STDOUT: ${ res . stdout } \n\nSTDERR: ${ res . stderr } ` ) ;
341+ throw e ;
332342 }
333- } ) ;
343+ }
334344 // If enabled slimming, delete files in slimPatterns
335345 if ( options . slim === true || options . slim === 'true' ) {
336346 deleteFiles ( options , targetFolder ) ;
@@ -489,7 +499,7 @@ function requirementsFileExists(servicePath, options, fileName) {
489499 * @param {Object } serverless
490500 * @return {string }
491501 */
492- function installRequirementsIfNeeded (
502+ async function installRequirementsIfNeeded (
493503 servicePath ,
494504 modulePath ,
495505 options ,
@@ -573,7 +583,7 @@ function installRequirementsIfNeeded(
573583 fse . copySync ( slsReqsTxt , path . join ( workingReqsFolder , 'requirements.txt' ) ) ;
574584
575585 // Then install our requirements from this folder
576- installRequirements ( workingReqsFolder , serverless , options ) ;
586+ await installRequirements ( workingReqsFolder , serverless , options ) ;
577587
578588 // Copy vendor libraries to requirements folder
579589 if ( options . vendor ) {
@@ -596,63 +606,64 @@ function installRequirementsIfNeeded(
596606 * pip install the requirements to the requirements directory
597607 * @return {undefined }
598608 */
599- function installAllRequirements ( ) {
609+ async function installAllRequirements ( ) {
600610 // fse.ensureDirSync(path.join(this.servicePath, '.serverless'));
601611 // First, check and delete cache versions, if enabled
602612 checkForAndDeleteMaxCacheVersions ( this . options , this . serverless ) ;
603613
604614 // Then if we're going to package functions individually...
605615 if ( this . serverless . service . package . individually ) {
606616 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 .* /
612620 )
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 ) ;
647655 } else {
648- fse . rename ( reqsInstalledAt , modulePath ) ;
656+ fse . symlink ( reqsInstalledAt , modulePath ) ;
649657 }
658+ } else {
659+ fse . rename ( reqsInstalledAt , modulePath ) ;
650660 }
651- doneModules . push ( f . module ) ;
652661 }
653- } ) ;
662+ doneModules . push ( f . module ) ;
663+ }
664+ }
654665 } else {
655- const reqsInstalledAt = installRequirementsIfNeeded (
666+ const reqsInstalledAt = await installRequirementsIfNeeded (
656667 this . servicePath ,
657668 '' ,
658669 this . options ,
0 commit comments