OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of pipeline; | 5 part of pipeline; |
6 | 6 |
7 List stdout, stderr, log; | 7 List log; |
8 var replyPort; | 8 var replyPort; |
9 int _procId = 1; | 9 int _procId = 1; |
10 Map _procs = {}; | 10 Map _procs = {}; |
11 | 11 |
12 /** | 12 /** |
13 * Create a file path for a temporary file. The file will be in the | 13 * Create a file path for a temporary file. The file will be in the |
14 * [tmpDir] directory, with name [basis], but with any extension | 14 * [tmpDir] directory, with name [basis], but with any extension |
15 * stripped and replaced by [suffix]. | 15 * stripped and replaced by [suffix]. |
16 */ | 16 */ |
17 String createTempName(String tmpDir, String basis, String suffix) { | 17 String createTempName(String tmpDir, String basis, [String suffix='']) { |
18 var p = new Path(basis); | 18 var p = new Path(basis); |
19 return '$tmpDir${Platform.pathSeparator}' | 19 return '$tmpDir${Platform.pathSeparator}' |
20 '${p.filenameWithoutExtension}${suffix}'; | 20 '${p.filenameWithoutExtension}${suffix}'; |
21 } | 21 } |
22 | 22 |
23 /** | 23 /** |
24 * Given a file path [file], make it absolute if it is relative, | 24 * Given a file path [file], make it absolute if it is relative, |
25 * and return the result as a [Path]. | 25 * and return the result as a [Path]. |
26 */ | 26 */ |
27 Path getAbsolutePath(String file) { | 27 Path getAbsolutePath(String file) { |
28 var p = new Path(file).canonicalize(); | 28 var p = new Path(file).canonicalize(); |
29 if (p.isAbsolute) { | 29 if (p.isAbsolute) { |
30 return p; | 30 return p; |
31 } else { | 31 } else { |
32 var cwd = new Path((new Directory.current()).path); | 32 var cwd = new Path((new Directory.current()).path); |
33 return cwd.join(p); | 33 return cwd.join(p); |
34 } | 34 } |
35 } | 35 } |
36 | 36 |
37 /** Get the directory that contains a [file]. */ | 37 /** Get the directory that contains a [file]. */ |
38 String getDirectory(String file) => | 38 String getDirectory(String file) => |
39 getAbsolutePath(file).directoryPath.toString(); | 39 getAbsolutePath(file).directoryPath.toString(); |
40 | 40 |
41 /** Create a file [fileName] and populate it with [contents]. */ | 41 /** Create a file [fileName] and populate it with [contents]. */ |
42 void writeFile(String fileName, String contents) { | 42 void writeFile(String fileName, String contents) { |
43 var file = new File(fileName); | 43 var file = new File(fileName); |
44 var ostream = file.openOutputStream(FileMode.WRITE); | 44 file.writeAsStringSync(contents); |
45 ostream.writeString(contents); | |
46 ostream.close(); | |
47 } | 45 } |
48 | 46 |
49 /* | 47 /* |
50 * Run an external process [cmd] with command line arguments [args]. | 48 * Run an external process [cmd] with command line arguments [args]. |
51 * [timeout] can be used to forcefully terminate the process after | 49 * [timeout] can be used to forcefully terminate the process after |
52 * some number of seconds. This is used by runCommand and startProcess. | 50 * some number of seconds. This is used by runCommand and startProcess. |
53 * If [procId] is non-zero (i.e. called from startProcess) then a reference | 51 * If [procId] is non-zero (i.e. called from startProcess) then a reference |
54 * to the [Process] will be put in a map with key [procId]; in this case | 52 * to the [Process] will be put in a map with key [procId]; in this case |
55 * the process can be terminated later by calling [stopProcess] and | 53 * the process can be terminated later by calling [stopProcess] and |
56 * passing in the [procId]. | 54 * passing in the [procId]. |
57 * [outputMonitor] is an optional function that will be called back with each | 55 * [outputMonitor] is an optional function that will be called back with each |
58 * line of output from the process. | 56 * line of output from the process. |
59 * Returns a [Future] for when the process terminates. | 57 * Returns a [Future] for when the process terminates. |
60 */ | 58 */ |
61 Future _processHelper(String command, List<String> args, | 59 Future _processHelper(String command, List<String> args, |
62 [int timeout = 300, int procId = 0, Function outputMonitor]) { | 60 List stdout, List stderr, |
63 var completer = procId == 0 ? new Completer() : null; | 61 [int timeout = 30, int procId = 0, Function outputMonitor]) { |
| 62 var timer = null; |
| 63 if (Platform.operatingSystem == 'windows' && command.endsWith('.bat')) { |
| 64 var oldArgs = args; |
| 65 args = new List(); |
| 66 args.add('/c'); |
| 67 // TODO(gram): We may need some escaping here if any of the |
| 68 // components contain spaces. |
| 69 args.add("$command ${oldArgs.join(' ')}"); |
| 70 command='cmd.exe'; |
| 71 } |
64 log.add('Running $command ${args.join(" ")}'); | 72 log.add('Running $command ${args.join(" ")}'); |
65 var timer = null; | 73 |
66 var stdoutHandler, stderrHandler; | 74 return Process.start(command, args) |
67 var processFuture = Process.start(command, args); | 75 .then((process) { |
68 processFuture.then((process) { | 76 _procs[procId.toString()] = process; |
69 _procs[procId] = process; | |
70 | 77 |
71 timer = new Timer(new Duration(seconds: timeout), () { | 78 var stdoutFuture = _pipeStream(process.stdout, stdout, outputMonitor); |
72 timer = null; | 79 var stderrFuture = _pipeStream(process.stderr, stderr, outputMonitor); |
73 process.kill(); | |
74 }); | |
75 | 80 |
76 process.onExit = (exitCode) { | 81 timer = new Timer(new Duration(seconds: timeout), () { |
77 if (timer != null) { | 82 timer = null; |
78 timer.cancel(); | 83 process.kill(); |
79 } | 84 }); |
80 process.close(); | 85 return Future.wait([process.exitCode, stdoutFuture, stderrFuture]) |
81 if (completer != null) { | 86 .then((values) { |
82 completer.complete(exitCode); | 87 if (timer != null) { |
83 } | 88 timer.cancel(); |
84 }; | 89 } |
85 | 90 return values[0]; |
86 _pipeStream(process.stdout, stdout, outputMonitor); | 91 }); |
87 _pipeStream(process.stderr, stderr, outputMonitor); | 92 }) |
88 }); | 93 .catchError((e) { |
89 processFuture.handleException((e) { | 94 stderr.add("Error starting process:"); |
90 stderr.add("Error starting process:"); | 95 stderr.add(" Command: $command"); |
91 stderr.add(" Command: $command"); | 96 stderr.add(" Error: ${e.toString()}"); |
92 stderr.add(" Error: $e"); | 97 return new Future.value(-1); |
93 completePipeline(-1); | 98 }); |
94 return true; | |
95 }); | |
96 | |
97 return completer.future; | |
98 } | 99 } |
99 | 100 |
100 void _pipeStream(InputStream stream, List<String> destination, | 101 Future _pipeStream(Stream stream, List<String> destination, |
101 Function outputMonitor) { | 102 Function outputMonitor) { |
102 var source = new StringInputStream(stream); | 103 return stream |
103 source.onLine = () { | 104 .transform(new StringDecoder()) |
104 if (source.available() == 0) return; | 105 .transform(new LineTransformer()) |
105 var line = source.readLine(); | 106 .listen((String line) { |
106 while (null != line) { | |
107 if (config["immediate"] && line.startsWith('###')) { | 107 if (config["immediate"] && line.startsWith('###')) { |
108 // TODO - when we dump the list later skip '###' messages if immediate. | |
109 print(line.substring(3)); | 108 print(line.substring(3)); |
110 } | 109 } |
111 if (outputMonitor != null) { | 110 if (outputMonitor != null) { |
112 outputMonitor(line); | 111 outputMonitor(line); |
113 } | 112 } |
114 destination.add(line); | 113 destination.add(line); |
115 line = source.readLine(); | 114 }) |
116 } | 115 .asFuture(); |
117 }; | |
118 } | 116 } |
119 | 117 |
120 /** | 118 /** |
121 * Run an external process [cmd] with command line arguments [args]. | 119 * Run an external process [cmd] with command line arguments [args]. |
122 * [timeout] can be used to forcefully terminate the process after | 120 * [timeout] can be used to forcefully terminate the process after |
123 * some number of seconds. | 121 * some number of seconds. |
124 * Returns a [Future] for when the process terminates. | 122 * Returns a [Future] for when the process terminates. |
125 */ | 123 */ |
126 Future runCommand(String command, List<String> args, | 124 Future runCommand(String command, List<String> args, |
127 [int timeout = 300, Function outputMonitor]) { | 125 List stdout, List stderr, |
128 return _processHelper(command, args, timeout, outputMonitor:outputMonitor); | 126 [int timeout = 30, Function outputMonitor]) { |
| 127 return _processHelper(command, args, stdout, stderr, |
| 128 timeout, 0, outputMonitor); |
129 } | 129 } |
130 | 130 |
131 /** | 131 /** |
132 * Start an external process [cmd] with command line arguments [args]. | 132 * Start an external process [cmd] with command line arguments [args]. |
133 * Returns an ID by which it can later be stopped. | 133 * Returns an ID by which it can later be stopped. |
134 */ | 134 */ |
135 int startProcess(String command, List<String> args, [Function outputMonitor]) { | 135 int startProcess(String command, List<String> args, List stdout, List stderr, |
| 136 [Function outputMonitor]) { |
136 int id = _procId++; | 137 int id = _procId++; |
137 _processHelper(command, args, 3000, id, | 138 var f = _processHelper(command, args, stdout, stderr, 3000, id, |
138 outputMonitor:outputMonitor).then((e) { | 139 outputMonitor); |
139 _procs.remove(id); | 140 if (f != null) { |
140 }); | 141 f.then((e) { |
| 142 _procs.remove(id.toString()); |
| 143 }); |
| 144 } |
141 return id; | 145 return id; |
142 } | 146 } |
143 | 147 |
144 /** Checks if a process is still running. */ | 148 /** Checks if a process is still running. */ |
145 bool isProcessRunning(int id) { | 149 bool isProcessRunning(int id) { |
146 return _procs.containsKey(id); | 150 return _procs.containsKey(id.toString()); |
147 } | 151 } |
148 | 152 |
149 /** | 153 /** |
150 * Stop a process previously started with [startProcess] or [runCommand], | 154 * Stop a process previously started with [startProcess] or [runCommand], |
151 * given the id string. | 155 * given the id string. |
152 */ | 156 */ |
153 void stopProcess(int id) { | 157 void stopProcess(int id) { |
154 if (_procs.containsKey(id)) { | 158 var sid = id.toString(); |
155 Process p = _procs.remove(id); | 159 if (_procs.containsKey(sid)) { |
| 160 Process p = _procs.remove(sid); |
156 p.kill(); | 161 p.kill(); |
157 } | 162 } |
158 } | 163 } |
159 | 164 |
160 /** Delete a file named [fname] if it exists. */ | 165 /** Delete a file named [fname] if it exists. */ |
161 bool cleanup(String fname) { | 166 bool cleanup(String fname) { |
162 if (fname != null && !config['keep-files']) { | 167 if (fname != null && config['clean-files']) { |
163 var f = new File(fname); | 168 var f = new File(fname); |
164 try { | 169 try { |
165 if (f.existsSync()) { | 170 if (f.existsSync()) { |
166 logMessage('Removing $fname'); | 171 logMessage('Removing $fname'); |
167 f.deleteSync(); | 172 f.deleteSync(); |
168 } | 173 } |
169 } catch (e) { | 174 } catch (e) { |
170 return false; | 175 return false; |
171 } | 176 } |
172 } | 177 } |
173 return true; | 178 return true; |
174 } | 179 } |
175 | 180 |
| 181 /** Delete a directory named [dname] if it exists. */ |
| 182 bool cleanupDir(String dname) { |
| 183 if (dname != null && config['clean-files']) { |
| 184 var d = new Directory(dname); |
| 185 try { |
| 186 if (d.existsSync()) { |
| 187 logMessage('Removing $dname'); |
| 188 d.deleteSync(recursive: true); |
| 189 } |
| 190 } catch (e) { |
| 191 return false; |
| 192 } |
| 193 } |
| 194 return true; |
| 195 } |
| 196 |
176 initPipeline(port) { | 197 initPipeline(port) { |
177 replyPort = port; | 198 replyPort = port; |
178 stdout = new List(); | 199 stdout = new List(); |
179 stderr = new List(); | 200 stderr = new List(); |
180 log = new List(); | 201 log = new List(); |
181 } | 202 } |
182 | 203 |
183 void completePipeline([exitCode = 0]) { | 204 void completePipeline(List stdout, List stderr, [exitCode = 0]) { |
184 replyPort.send([stdout, stderr, log, exitCode]); | 205 replyPort.send([stdout, stderr, log, exitCode]); |
185 } | 206 } |
186 | 207 |
187 /** Utility function to log diagnostic messages. */ | 208 /** Utility function to log diagnostic messages. */ |
188 void logMessage(msg) => log.add(msg); | 209 void logMessage(msg) => log.add(msg); |
| 210 |
| 211 /** Turn file paths into standard form with forward slashes. */ |
| 212 String normalizePath(String p) => (new Path(p)).toString(); |
OLD | NEW |