@@ -6,16 +6,42 @@ const set = require('lodash.set');
66const  {  spawnSync }  =  require ( 'child_process' ) ; 
77const  {  quote }  =  require ( 'shell-quote' ) ; 
88const  {  buildImage,  getBindPath,  getDockerUid }  =  require ( './docker' ) ; 
9- const  {  getStripCommand,  deleteFiles }  =  require ( './slim' ) ; 
9+ const  {  getStripCommand,  getStripMode ,   deleteFiles }  =  require ( './slim' ) ; 
1010const  { 
1111  checkForAndDeleteMaxCacheVersions, 
1212  md5Path, 
1313  getRequirementsWorkingPath, 
1414  getUserCachePath
1515}  =  require ( './shared' ) ; 
1616
17- function  quote_single ( quoteme )  { 
18-   return  quote ( [ quoteme ] ) ; 
17+ /** 
18+  * Omit empty commands. 
19+  * In this context, a "command" is a list of arguments. An empty list or falsy value is ommitted. 
20+  * @param  {string[][] } many commands to merge. 
21+  * @return  {string[][] } a list of valid commands. 
22+  */ 
23+ function  filterCommands ( commands )  { 
24+   return  commands . filter ( ( cmd )  =>  Boolean ( cmd )  &&  cmd . length  >  0 ) ; 
25+ } 
26+ 
27+ /** 
28+  * Render zero or more commands as a single command for a Unix environment. 
29+  * In this context, a "command" is a list of arguments. An empty list or falsy value is ommitted. 
30+  * 
31+  * @param  {string[][] } many commands to merge. 
32+  * @return  {string[] } a single list of words. 
33+  */ 
34+ function  mergeCommands ( commands )  { 
35+   const  cmds  =  filterCommands ( commands ) ; 
36+   if  ( cmds . length  ===  0 )  { 
37+     throw  new  Error ( 'Expected at least one non-empty command' ) 
38+   }  else  if  ( cmds . length  ===  1 )  { 
39+     return  cmds [ 0 ] ; 
40+   }  else  { 
41+     // Quote the arguments in each command and join them all using &&. 
42+     const  script  =  cmds . map ( quote ) . join ( ' && ' ) ; 
43+     return  [ "/bin/sh" ,  "-c" ,  script ] ; 
44+   } 
1945} 
2046
2147/** 
@@ -51,6 +77,25 @@ function installRequirementsFile(
5177  } 
5278} 
5379
80+ function  pipAcceptsSystem ( pythonBin )  { 
81+   // Check if pip has Debian's --system option and set it if so 
82+   const  pipTestRes  =  spawnSync ( pythonBin ,  [ 
83+     '-m' , 
84+     'pip' , 
85+     'help' , 
86+     'install' 
87+   ] ) ; 
88+   if  ( pipTestRes . error )  { 
89+     if  ( pipTestRes . error . code  ===  'ENOENT' )  { 
90+       throw  new  Error ( 
91+         `${ pythonBin }  
92+       ) ; 
93+     } 
94+     throw  pipTestRes . error ; 
95+   } 
96+   return  pipTestRes . stdout . toString ( ) . indexOf ( '--system' )  >=  0 ; 
97+ } 
98+ 
5499/** 
55100 * Install requirements described from requirements in the targetFolder into that same targetFolder 
56101 * @param  {string } targetFolder 
@@ -65,15 +110,16 @@ function installRequirements(targetFolder, serverless, options) {
65110    `Installing requirements from ${ targetRequirementsTxt }  
66111  ) ; 
67112
68-   let  cmd ; 
69-   let  cmdOptions ; 
70-   let  pipCmd  =  [ 
113+   const  dockerCmd  =  [ ] ; 
114+   const  pipCmd  =  [ 
71115    options . pythonBin , 
72116    '-m' , 
73117    'pip' , 
74118    'install' , 
75119    ...options . pipCmdExtraArgs 
76120  ] ; 
121+   const  pipCmds  =  [ pipCmd ] ; 
122+   const  postCmds  =  [ ] ; 
77123  // Check if we're using the legacy --cache-dir command... 
78124  if  ( options . pipCmdExtraArgs . indexOf ( '--cache-dir' )  >  - 1 )  { 
79125    if  ( options . dockerizePip )  { 
@@ -94,8 +140,8 @@ function installRequirements(targetFolder, serverless, options) {
94140
95141  if  ( ! options . dockerizePip )  { 
96142    // Push our local OS-specific paths for requirements and target directory 
97-     pipCmd . push ( '-t' ,  dockerPathForWin ( options ,   targetFolder ) ) ; 
98-     pipCmd . push ( '-r' ,  dockerPathForWin ( options ,   targetRequirementsTxt ) ) ; 
143+     pipCmd . push ( '-t' ,  dockerPathForWin ( targetFolder ) , 
144+                  '-r' ,  dockerPathForWin ( targetRequirementsTxt ) ) ; 
99145    // If we want a download cache... 
100146    if  ( options . useDownloadCache )  { 
101147      const  downloadCacheDir  =  path . join ( 
@@ -104,35 +150,17 @@ function installRequirements(targetFolder, serverless, options) {
104150      ) ; 
105151      serverless . cli . log ( `Using download cache directory ${ downloadCacheDir }  ) ; 
106152      fse . ensureDirSync ( downloadCacheDir ) ; 
107-       pipCmd . push ( '--cache-dir' ,  quote_single ( downloadCacheDir ) ) ; 
153+       pipCmd . push ( '--cache-dir' ,  downloadCacheDir ) ; 
108154    } 
109155
110-     // Check if pip has Debian's --system option and set it if so 
111-     const  pipTestRes  =  spawnSync ( options . pythonBin ,  [ 
112-       '-m' , 
113-       'pip' , 
114-       'help' , 
115-       'install' 
116-     ] ) ; 
117-     if  ( pipTestRes . error )  { 
118-       if  ( pipTestRes . error . code  ===  'ENOENT' )  { 
119-         throw  new  Error ( 
120-           `${ options . pythonBin }   +  'Try the pythonBin option.' 
121-         ) ; 
122-       } 
123-       throw  pipTestRes . error ; 
124-     } 
125-     if  ( pipTestRes . stdout . toString ( ) . indexOf ( '--system' )  >=  0 )  { 
156+     if  ( pipAcceptsSystem ( options . pythonBin ) )  { 
126157      pipCmd . push ( '--system' ) ; 
127158    } 
128159  } 
129160  // If we are dockerizing pip 
130161  if  ( options . dockerizePip )  { 
131-     cmd  =  'docker' ; 
132- 
133162    // Push docker-specific paths for requirements and target directory 
134-     pipCmd . push ( '-t' ,  '/var/task/' ) ; 
135-     pipCmd . push ( '-r' ,  '/var/task/requirements.txt' ) ; 
163+     pipCmd . push ( '-t' ,  '/var/task/' ,  '-r' ,  '/var/task/requirements.txt' ) ; 
136164
137165    // Build docker image if required 
138166    let  dockerImage ; 
@@ -148,28 +176,18 @@ function installRequirements(targetFolder, serverless, options) {
148176
149177    // Prepare bind path depending on os platform 
150178    const  bindPath  =  dockerPathForWin ( 
151-       options , 
152179      getBindPath ( serverless ,  targetFolder ) 
153180    ) ; 
154181
155-     cmdOptions   =   [ 'run' ,  '--rm' ,  '-v' ,  `${ bindPath }  ] ; 
182+     dockerCmd . push ( 'docker' ,   'run' ,  '--rm' ,  '-v' ,  `${ bindPath }  ) ; 
156183    if  ( options . dockerSsh )  { 
157184      // Mount necessary ssh files to work with private repos 
158-       cmdOptions . push ( 
159-         '-v' , 
160-         quote_single ( `${ process . env . HOME }  ) 
161-       ) ; 
162-       cmdOptions . push ( 
163-         '-v' , 
164-         quote_single ( 
165-           `${ process . env . HOME }  
166-         ) 
167-       ) ; 
168-       cmdOptions . push ( 
169-         '-v' , 
170-         quote_single ( `${ process . env . SSH_AUTH_SOCK }  ) 
185+       dockerCmd . push ( 
186+         '-v' ,  `${ process . env . HOME }  , 
187+         '-v' ,  `${ process . env . HOME }  , 
188+         '-v' ,  `${ process . env . SSH_AUTH_SOCK }  , 
189+         '-e' ,  'SSH_AUTH_SOCK=/tmp/ssh_sock' 
171190      ) ; 
172-       cmdOptions . push ( '-e' ,  'SSH_AUTH_SOCK=/tmp/ssh_sock' ) ; 
173191    } 
174192
175193    // If we want a download cache... 
@@ -189,104 +207,100 @@ function installRequirements(targetFolder, serverless, options) {
189207      ) ; 
190208      const  windowsized  =  getBindPath ( serverless ,  downloadCacheDir ) ; 
191209      // And now push it to a volume mount and to pip... 
192-       cmdOptions . push ( 
210+       dockerCmd . push ( 
193211        '-v' , 
194-         quote_single ( `${ windowsized } ${ dockerDownloadCacheDir }  ) 
212+         `${ windowsized } ${ dockerDownloadCacheDir }  
195213      ) ; 
196-       pipCmd . push ( '--cache-dir' ,  quote_single ( dockerDownloadCacheDir ) ) ; 
214+       pipCmd . push ( '--cache-dir' ,  dockerDownloadCacheDir ) ; 
197215    } 
198216
199217    if  ( options . dockerEnv )  { 
200218      // Add environment variables to docker run cmd 
201219      options . dockerEnv . forEach ( function ( item )  { 
202-         cmdOptions . push ( '-e' ,  item ) ; 
220+         dockerCmd . push ( '-e' ,  item ) ; 
203221      } ) ; 
204222    } 
205223
206224    if  ( process . platform  ===  'linux' )  { 
207225      // Use same user so requirements folder is not root and so --cache-dir works 
208-       var  commands  =  [ ] ; 
209226      if  ( options . useDownloadCache )  { 
210227        // Set the ownership of the download cache dir to root 
211-         commands . push ( quote ( [ 'chown' ,  '-R' ,  '0:0' ,  dockerDownloadCacheDir ] ) ) ; 
228+         pipCmds . unshift ( [ 'chown' ,  '-R' ,  '0:0' ,  dockerDownloadCacheDir ] ) ; 
212229      } 
213230      // Install requirements with pip 
214-       commands . push ( pipCmd . join ( ' ' ) ) ; 
215231      // Set the ownership of the current folder to user 
216-       commands . push ( 
217-         quote ( [ 
218-           'chown' , 
219-           '-R' , 
220-           `${ process . getuid ( ) } ${ process . getgid ( ) }  , 
221-           '/var/task' 
222-         ] ) 
223-       ) ; 
232+       pipCmds . push ( [ 'chown' ,  '-R' ,  `${ process . getuid ( ) } ${ process . getgid ( ) }  ,  '/var/task' ] ) ; 
224233      if  ( options . useDownloadCache )  { 
225234        // Set the ownership of the download cache dir back to user 
226-         commands . push ( 
227-           quote ( [ 
235+         pipCmds . push ( 
236+           [ 
228237            'chown' , 
229238            '-R' , 
230239            `${ process . getuid ( ) } ${ process . getgid ( ) }  , 
231240            dockerDownloadCacheDir 
232-           ] ) 
241+           ] 
233242        ) ; 
234243      } 
235-       pipCmd  =  [ '/bin/bash' ,  '-c' ,  '"'  +  commands . join ( ' && ' )  +  '"' ] ; 
236244    }  else  { 
237245      // Use same user so --cache-dir works 
238-       cmdOptions . push ( '-u' ,  quote_single ( getDockerUid ( bindPath ) ) ) ; 
246+       dockerCmd . push ( '-u' ,  getDockerUid ( bindPath ) ) ; 
239247    } 
240-     cmdOptions . push ( dockerImage ) ; 
241-     cmdOptions . push ( ...pipCmd ) ; 
242-   }  else  { 
243-     cmd  =  pipCmd [ 0 ] ; 
244-     cmdOptions  =  pipCmd . slice ( 1 ) ; 
248+     dockerCmd . push ( dockerImage ) ; 
245249  } 
246250
247251  // If enabled slimming, strip so files 
248-   if  ( options . slim  ===  true  ||  options . slim  ===  'true' )  { 
249-     const  preparedPath  =  dockerPathForWin ( options ,  targetFolder ) ; 
250-     cmdOptions . push ( getStripCommand ( options ,  preparedPath ) ) ; 
252+   switch  ( getStripMode ( options ) )  { 
253+     case  'docker' :
254+       pipCmds . push ( getStripCommand ( options ,  '/var/task' ) ) ; 
255+     case  'direct' :
256+       postCmds . push ( getStripCommand ( options ,  dockerPathForWin ( targetFolder ) ) ) ; 
251257  } 
258+ 
252259  let  spawnArgs  =  {  shell : true  } ; 
253260  if  ( process . env . SLS_DEBUG )  { 
254261    spawnArgs . stdio  =  'inherit' ; 
255262  } 
256-   const  res  =  spawnSync ( cmd ,  cmdOptions ,  spawnArgs ) ; 
257-   if  ( res . error )  { 
258-     if  ( res . error . code  ===  'ENOENT' )  { 
259-       if  ( options . dockerizePip )  { 
260-         throw  new  Error ( 'docker not found! Please install it.' ) ; 
261-       } 
262-       throw  new  Error ( 
263-         `${ options . pythonBin }  
264-       ) ; 
265-     } 
266-     throw  res . error ; 
267-   } 
268-   if  ( res . status  !==  0 )  { 
269-     throw  new  Error ( res . stderr ) ; 
263+   let  mainCmds  =  [ ] ; 
264+   if  ( dockerCmd . length )  { 
265+     dockerCmd . push ( ...mergeCommands ( pipCmds ) ) ; 
266+     mainCmds  =  [ dockerCmd ] ; 
267+   }  else  { 
268+     mainCmds  =  pipCmds ; 
270269  } 
270+   mainCmds . push ( ...postCmds ) ; 
271+ 
272+   serverless . cli . log ( `Running ${ quote ( dockerCmd ) }  ) ; 
273+ 
274+   filterCommands ( mainCmds ) . forEach ( ( [ cmd ,  ...args ] )  =>  { 
275+       const  res  =  spawnSync ( cmd ,  args ) ; 
276+       if  ( res . error )  { 
277+         if  ( res . error . code  ===  'ENOENT' )  { 
278+           const  advice  =  cmd . indexOf ( 'python' )  >  - 1  ? 'Try the pythonBin option'  : 'Please install it' ; 
279+           throw  new  Error ( `${ cmd } ${ advice }  ) ; 
280+         } 
281+         throw  res . error ; 
282+       } 
283+       if  ( res . status  !==  0 )  { 
284+         throw  new  Error ( res . stderr ) ; 
285+       } 
286+   } ) ; 
271287  // If enabled slimming, delete files in slimPatterns 
272288  if  ( options . slim  ===  true  ||  options . slim  ===  'true' )  { 
273289    deleteFiles ( options ,  targetFolder ) ; 
274290  } 
275291} 
276292
277293/** 
278-  * convert path from Windows style to Linux style, if needed 
279-  * @param  {Object } options 
294+  * Convert path from Windows style to Linux style, if needed. 
280295 * @param  {string } path 
281296 * @return  {string } 
282297 */ 
283- function  dockerPathForWin ( options ,   path )  { 
298+ function  dockerPathForWin ( path )  { 
284299  if  ( process . platform  ===  'win32' )  { 
285-     return  `" ${ path . replace ( / \\ / g,  '/' ) } "` ; 
286-   }  else  if   ( process . platform   ===   'win32'   &&   ! options . dockerizePip )   { 
300+     return  path . replace ( / \\ / g,  '/' ) ; 
301+   }  else  { 
287302    return  path ; 
288303  } 
289-   return  quote_single ( path ) ; 
290304} 
291305
292306/** create a filtered requirements.txt without anything from noDeploy 
0 commit comments