@@ -7,10 +7,11 @@ library dartdoc.tool_runner;
7
7
import 'dart:io' show Process, ProcessException;
8
8
9
9
import 'package:analyzer/file_system/file_system.dart' ;
10
+ import 'package:dartdoc/src/dartdoc_options.dart' ;
10
11
import 'package:dartdoc/src/io_utils.dart' ;
11
12
import 'package:dartdoc/src/tool_definition.dart' ;
13
+ import 'package:meta/meta.dart' ;
12
14
import 'package:path/path.dart' as p;
13
- import 'dartdoc_options.dart' ;
14
15
15
16
typedef ToolErrorCallback = void Function (String message);
16
17
typedef FakeResultCallback = String Function (String tool,
@@ -20,9 +21,6 @@ typedef FakeResultCallback = String Function(String tool,
20
21
/// limiting both parallelization and the number of open temporary files.
21
22
final MultiFutureTracker <void > _toolTracker = MultiFutureTracker (4 );
22
23
23
- /// Can be called when the ToolRunner is no longer needed.
24
- ///
25
- /// This will remove any temporary files created by the tool runner.
26
24
class ToolTempFileTracker {
27
25
final ResourceProvider resourceProvider;
28
26
final Folder temporaryDirectory;
@@ -41,7 +39,7 @@ class ToolTempFileTracker {
41
39
42
40
int _temporaryFileCount = 0 ;
43
41
44
- Future < File > createTemporaryFile () async {
42
+ File createTemporaryFile () {
45
43
_temporaryFileCount++ ;
46
44
// TODO(srawlins): Assume [temporaryDirectory]'s path is always absolute.
47
45
var tempFile = resourceProvider.getFile (resourceProvider.pathContext.join (
@@ -80,22 +78,24 @@ class ToolRunner {
80
78
String commandPath;
81
79
82
80
if (isDartSetup) {
83
- commandPath = toolConfiguration. resourceProvider.resolvedExecutable;
81
+ commandPath = resourceProvider.resolvedExecutable;
84
82
} else {
85
83
commandPath = args.removeAt (0 );
86
84
}
87
- await _runProcess (
88
- name, '' , commandPath, args, environment, toolErrorCallback);
85
+ // We do not use the stdout of the setup process.
86
+ await _runProcess (name, '' , commandPath, args, environment,
87
+ toolErrorCallback: toolErrorCallback);
89
88
tool.setupComplete = true ;
90
89
}
91
90
92
- Future <String > _runProcess (
93
- String name,
94
- String content,
95
- String commandPath,
96
- List <String > args,
97
- Map <String , String > environment,
98
- ToolErrorCallback toolErrorCallback) async {
91
+ /// Runs the tool with [Process.run] , awaiting the exit code, and returning
92
+ /// the stdout.
93
+ ///
94
+ /// If the process's exit code is not 0, or if a [ProcessException] is thrown,
95
+ /// calls [toolErrorCallback] with a detailed error message, and returns `''` .
96
+ Future <String > _runProcess (String name, String content, String commandPath,
97
+ List <String > args, Map <String , String > environment,
98
+ {@required ToolErrorCallback toolErrorCallback}) async {
99
99
String commandString () => ([commandPath] + args).join (' ' );
100
100
try {
101
101
var result =
@@ -120,58 +120,52 @@ class ToolRunner {
120
120
}
121
121
}
122
122
123
- /// Run a tool. The name of the tool is the first argument in the [args] .
124
- /// The content to be sent to to the tool is given in the optional [content] ,
125
- /// and the stdout of the tool is returned.
123
+ /// Run a tool.
126
124
///
127
- /// The [args] must not be null, and it must have at least one member (the name
128
- /// of the tool).
129
- Future <String > run (List <String > args, ToolErrorCallback toolErrorCallback,
130
- {String content, Map <String , String > environment}) async {
125
+ /// The name of the tool is the first argument in the [args] . The content to
126
+ /// be sent to to the tool is given in the optional [content] . The stdout of
127
+ /// the tool is returned.
128
+ Future <String > run (List <String > args,
129
+ {@required String content,
130
+ @required ToolErrorCallback toolErrorCallback,
131
+ Map <String , String > environment}) async {
132
+ assert (args != null );
133
+ assert (args.isNotEmpty);
131
134
Future <String > runner;
132
135
// Prevent too many tools from running simultaneously.
133
136
await _toolTracker.addFutureFromClosure (() {
134
- runner = _run (args, toolErrorCallback,
135
- content: content, environment: environment);
137
+ runner = _run (args,
138
+ toolErrorCallback: toolErrorCallback,
139
+ content: content,
140
+ environment: environment);
136
141
return runner;
137
142
});
138
143
return runner;
139
144
}
140
145
141
- Future <String > _run (List <String > args, ToolErrorCallback toolErrorCallback,
142
- {String content, Map <String , String > environment}) async {
146
+ Future <String > _run (List <String > args,
147
+ {@required ToolErrorCallback toolErrorCallback,
148
+ String content,
149
+ Map <String , String > environment}) async {
143
150
assert (args != null );
144
151
assert (args.isNotEmpty);
145
152
content ?? = '' ;
146
153
environment ?? = < String , String > {};
147
- var tool = args.removeAt (0 );
148
- if (! toolConfiguration.tools.containsKey (tool )) {
154
+ var toolName = args.removeAt (0 );
155
+ if (! toolConfiguration.tools.containsKey (toolName )) {
149
156
toolErrorCallback (
150
- 'Unable to find definition for tool "$tool " in tool map. '
157
+ 'Unable to find definition for tool "$toolName " in tool map. '
151
158
'Did you add it to dartdoc_options.yaml?' );
152
159
return '' ;
153
160
}
154
- var toolDefinition = toolConfiguration.tools[tool ];
161
+ var toolDefinition = toolConfiguration.tools[toolName ];
155
162
var toolArgs = toolDefinition.command;
156
- // Ideally, we would just be able to send the input text into stdin, but
157
- // there's no way to do that synchronously, and converting dartdoc to an
158
- // async model of execution is a huge amount of work. Using dart:cli's
159
- // waitFor feels like a hack (and requires a similar amount of work anyhow
160
- // to fix order of execution issues). So, instead, we have the tool take a
161
- // filename as part of its arguments, and write the input to a temporary
162
- // file before running the tool synchronously.
163
-
164
- // Write the content to a temp file.
165
- var tmpFile = await ToolTempFileTracker .createInstance (
166
- toolConfiguration.resourceProvider)
167
- .createTemporaryFile ();
168
- tmpFile.writeAsStringSync (content);
169
163
170
164
// Substitute the temp filename for the "$INPUT" token, and all of the other
171
165
// environment variables. Variables are allowed to either be in $(VAR) form,
172
166
// or $VAR form.
173
167
var envWithInput = {
174
- 'INPUT' : pathContext. absolute (tmpFile.path ),
168
+ 'INPUT' : _tmpFileWithContent (content ),
175
169
'TOOL_COMMAND' : toolDefinition.command[0 ],
176
170
...environment,
177
171
};
@@ -183,17 +177,62 @@ class ToolRunner {
183
177
// find out where their script was coming from as an absolute path on the
184
178
// filesystem.
185
179
envWithInput['DART_SNAPSHOT_CACHE' ] = pathContext.absolute (
186
- SnapshotCache .createInstance (toolConfiguration.resourceProvider)
187
- .snapshotCache
188
- .path);
180
+ SnapshotCache .createInstance (resourceProvider).snapshotCache.path);
189
181
if (toolDefinition.setupCommand != null ) {
190
182
envWithInput['DART_SETUP_COMMAND' ] = toolDefinition.setupCommand[0 ];
191
183
}
192
184
}
185
+
186
+ var argsWithInput = [
187
+ ...toolArgs,
188
+ ..._substituteInArgs (args, envWithInput),
189
+ ];
190
+
191
+ if (toolDefinition.setupCommand != null && ! toolDefinition.setupComplete) {
192
+ await _runSetup (
193
+ toolName, toolDefinition, envWithInput, toolErrorCallback);
194
+ }
195
+
196
+ var toolStateForArgs = await toolDefinition.toolStateForArgs (argsWithInput);
197
+ var commandPath = toolStateForArgs.commandPath;
198
+ argsWithInput = toolStateForArgs.args;
199
+ var callCompleter = toolStateForArgs.onProcessComplete;
200
+ var stdout = _runProcess (
201
+ toolName, content, commandPath, argsWithInput, envWithInput,
202
+ toolErrorCallback: toolErrorCallback);
203
+
204
+ if (callCompleter == null ) {
205
+ return stdout;
206
+ } else {
207
+ return stdout.whenComplete (callCompleter);
208
+ }
209
+ }
210
+
211
+ /// Returns the path to the temp file after [content] is written to it.
212
+ String _tmpFileWithContent (String content) {
213
+ // Ideally, we would just be able to send the input text into stdin, but
214
+ // there's no way to do that synchronously, and converting dartdoc to an
215
+ // async model of execution is a huge amount of work. Using dart:cli's
216
+ // waitFor feels like a hack (and requires a similar amount of work anyhow
217
+ // to fix order of execution issues). So, instead, we have the tool take a
218
+ // filename as part of its arguments, and write the input to a temporary
219
+ // file before running the tool synchronously.
220
+
221
+ // Write the content to a temp file.
222
+ var tmpFile = ToolTempFileTracker .createInstance (resourceProvider)
223
+ .createTemporaryFile ();
224
+ tmpFile.writeAsStringSync (content);
225
+ return pathContext.absolute (tmpFile.path);
226
+ }
227
+
228
+ // TODO(srawlins): Unit tests.
229
+ List <String > _substituteInArgs (
230
+ List <String > args, Map <String , String > envWithInput) {
193
231
var substitutions = envWithInput.map <RegExp , String >((key, value) {
194
232
var escapedKey = RegExp .escape (key);
195
233
return MapEntry (RegExp ('\\\$ (\\ ($escapedKey \\ )|$escapedKey \\ b)' ), value);
196
234
});
235
+
197
236
var argsWithInput = < String > [];
198
237
for (var arg in args) {
199
238
var newArg = arg;
@@ -202,25 +241,10 @@ class ToolRunner {
202
241
argsWithInput.add (newArg);
203
242
}
204
243
205
- if (toolDefinition.setupCommand != null && ! toolDefinition.setupComplete) {
206
- await _runSetup (tool, toolDefinition, envWithInput, toolErrorCallback);
207
- }
208
-
209
- argsWithInput = toolArgs + argsWithInput;
210
- var toolStateForArgs = await toolDefinition.toolStateForArgs (argsWithInput);
211
- var commandPath = toolStateForArgs.commandPath;
212
- argsWithInput = toolStateForArgs.args;
213
- var callCompleter = toolStateForArgs.onProcessComplete;
214
-
215
- if (callCompleter != null ) {
216
- return _runProcess (tool, content, commandPath, argsWithInput,
217
- envWithInput, toolErrorCallback)
218
- .whenComplete (callCompleter);
219
- } else {
220
- return _runProcess (tool, content, commandPath, argsWithInput,
221
- envWithInput, toolErrorCallback);
222
- }
244
+ return argsWithInput;
223
245
}
224
246
225
- p.Context get pathContext => toolConfiguration.resourceProvider.pathContext;
247
+ ResourceProvider get resourceProvider => toolConfiguration.resourceProvider;
248
+
249
+ p.Context get pathContext => resourceProvider.pathContext;
226
250
}
0 commit comments