1
1
package org .jfrog .build .extractor .executor ;
2
2
3
+ import com .google .common .collect .Maps ;
3
4
import org .apache .commons .lang3 .SystemUtils ;
4
5
import org .jfrog .build .api .util .Log ;
5
6
import org .jfrog .build .extractor .UrlUtils ;
8
9
import java .io .IOException ;
9
10
import java .io .InputStream ;
10
11
import java .io .Serializable ;
12
+ import java .nio .file .Path ;
13
+ import java .nio .file .Paths ;
11
14
import java .util .*;
12
15
import java .util .concurrent .ExecutorService ;
13
16
import java .util .concurrent .Executors ;
14
17
import java .util .concurrent .TimeUnit ;
15
- import java .util .stream .Collectors ;
16
18
17
19
import static java .lang .String .format ;
18
20
import static java .lang .String .join ;
@@ -26,15 +28,15 @@ public class CommandExecutor implements Serializable {
26
28
private static final int READER_SHUTDOWN_TIMEOUT_SECONDS = 30 ;
27
29
private static final int PROCESS_TERMINATION_TIMEOUT_SECONDS = 30 ;
28
30
private static final int EXECUTION_TIMEOUT_MINUTES = 120 ;
29
- private Map <String , String > env ;
31
+ private final Map <String , String > env ;
30
32
private final String executablePath ;
31
33
32
34
/**
33
35
* @param executablePath - Executable path.
34
36
* @param env - Environment variables to use during execution.
35
37
*/
36
38
public CommandExecutor (String executablePath , Map <String , String > env ) {
37
- this .executablePath = escapeSpacesInPath ( executablePath );
39
+ this .executablePath = executablePath . trim ( );
38
40
Map <String , String > finalEnvMap = new HashMap <>(System .getenv ());
39
41
if (env != null ) {
40
42
Map <String , String > fixedEnvMap = new HashMap <>(env );
@@ -133,10 +135,9 @@ public CommandResults exeCommand(File execDir, List<String> args, List<String> c
133
135
*/
134
136
public CommandResults exeCommand (File execDir , List <String > args , List <String > credentials , Log logger , long timeout , TimeUnit unit ) throws InterruptedException , IOException {
135
137
List <String > command = new ArrayList <>(args );
136
- command .add (0 , executablePath );
137
138
ExecutorService service = Executors .newFixedThreadPool (2 );
138
139
try {
139
- Process process = runProcess (execDir , command , credentials , env , logger );
140
+ Process process = runProcess (execDir , executablePath , command , credentials , env , logger );
140
141
// The output stream is not necessary in non-interactive scenarios, therefore we can close it now.
141
142
process .getOutputStream ().close ();
142
143
try (InputStream inputStream = process .getInputStream (); InputStream errorStream = process .getErrorStream ()) {
@@ -179,38 +180,86 @@ private CommandResults getCommandResults(boolean terminatedProperly, List<String
179
180
return commandRes ;
180
181
}
181
182
182
- private static Process runProcess (File execDir , List <String > args , List <String > credentials , Map <String , String > env , Log logger ) throws IOException {
183
+ private static Process runProcess (File execDir , String executablePath , List <String > args , List <String > credentials , Map <String , String > env , Log logger ) throws IOException {
184
+ // Make sure to copy the environment variables map to avoid changing the original map or in case it is immutable.
185
+ Map <String , String > newEnv = Maps .newHashMap (env );
186
+
187
+ args = formatCommand (args , credentials , executablePath , newEnv );
188
+ logCommand (logger , args , credentials );
189
+ ProcessBuilder processBuilder = new ProcessBuilder (args )
190
+ .directory (execDir );
191
+ processBuilder .environment ().putAll (newEnv );
192
+ return processBuilder .start ();
193
+ }
194
+
195
+ /**
196
+ * Formats a command for execution, incorporating credentials and environment variables.
197
+ *
198
+ * @param args the list of arguments to be included in the command
199
+ * @param credentials if specified, the credentials will be concatenated to the command
200
+ * @param executablePath the path to the executable to be executed
201
+ * @param env environment variables map. It might be modified as part of the formatting process
202
+ * @return the formatted command as a list of strings, ready for execution
203
+ */
204
+ private static List <String > formatCommand (List <String > args , List <String > credentials , String executablePath , Map <String , String > env ) {
183
205
if (credentials != null ) {
184
206
args .addAll (credentials );
185
207
}
208
+
186
209
if (SystemUtils .IS_OS_WINDOWS ) {
187
- args .addAll (0 , Arrays .asList ("cmd" , "/c" ));
210
+ formatWindowsCommand (args , executablePath , env );
211
+ return args ;
212
+ }
213
+ return formatUnixCommand (args , executablePath );
214
+ }
215
+
216
+ /**
217
+ * Formats a Windows command for execution.
218
+ *
219
+ * @param args the list of arguments to be included in the command
220
+ * @param executablePath the path to the executable to be executed
221
+ * @param env environment variables map. It might be modified as part of the formatting process
222
+ */
223
+ private static void formatWindowsCommand (List <String > args , String executablePath , Map <String , String > env ) {
224
+ Path execPath = Paths .get (executablePath );
225
+ if (execPath .isAbsolute ()) {
226
+ addToWindowsPath (env , execPath );
227
+ args .add (0 , execPath .getFileName ().toString ());
188
228
} else {
189
- String strArgs = join (" " , args );
190
- args = new ArrayList <String >() {{
191
- add ("/bin/sh" );
192
- add ("-c" );
193
- add (strArgs );
194
- }};
229
+ args .add (0 , executablePath .replaceAll (" " , "^ " ));
195
230
}
196
- logCommand (logger , args , credentials );
197
- ProcessBuilder processBuilder = new ProcessBuilder (args )
198
- .directory (execDir );
199
- processBuilder .environment ().putAll (env );
200
- return processBuilder .start ();
231
+ args .addAll (0 , Arrays .asList ("cmd" , "/c" ));
232
+ }
233
+
234
+ private static List <String > formatUnixCommand (List <String > args , String executablePath ) {
235
+ args .add (0 , executablePath .replaceAll (" " , "\\ \\ " ));
236
+ String strArgs = join (" " , args );
237
+ return new ArrayList <String >() {{
238
+ add ("/bin/sh" );
239
+ add ("-c" );
240
+ add (strArgs );
241
+ }};
201
242
}
202
243
203
244
/**
204
- * Escape spaces in the input executable path and trim leading and trailing whitespaces.
245
+ * Inserts the executable directory path at the beginning of the Path environment variable.
246
+ * This is done to handle cases where the executable path contains spaces. In such scenarios, the "cmd" command used
247
+ * to execute this command in Windows may incorrectly parse the path, treating the section after the space as an
248
+ * argument for the command.
205
249
*
206
- * @param executablePath - the executable path to process
207
- * @return escaped and trimmed executable path.
250
+ * @param env environment variables map
251
+ * @param execPath the executable path
208
252
*/
209
- private static String escapeSpacesInPath (String executablePath ) {
210
- if (executablePath == null ) {
211
- return null ;
253
+ static void addToWindowsPath (Map <String , String > env , Path execPath ) {
254
+ String execDirPath = execPath .getParent ().toString ();
255
+
256
+ // Insert the executable directory path to the beginning of the Path environment variable.
257
+ String windowsPathEnvKey = "Path" ;
258
+ if (env .containsKey (windowsPathEnvKey )) {
259
+ env .put (windowsPathEnvKey , execDirPath + File .pathSeparator + env .get (windowsPathEnvKey ));
260
+ } else {
261
+ env .put (windowsPathEnvKey , execDirPath );
212
262
}
213
- return executablePath .trim ().replaceAll (" " , SystemUtils .IS_OS_WINDOWS ? "^ " : "\\ \\ " );
214
263
}
215
264
216
265
private static void logCommand (Log logger , List <String > args , List <String > credentials ) {
0 commit comments