Skip to content

Commit 3cf7c63

Browse files
authored
Small refactorings in tool_runner.dart (#2685)
1 parent dedab6e commit 3cf7c63

File tree

3 files changed

+122
-96
lines changed

3 files changed

+122
-96
lines changed

lib/src/model/documentation_comment.dart

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:dartdoc/src/model/model.dart';
44
import 'package:dartdoc/src/render/model_element_renderer.dart';
55
import 'package:dartdoc/src/utils.dart';
66
import 'package:dartdoc/src/warnings.dart';
7+
import 'package:meta/meta.dart';
78
import 'package:path/path.dart' as path show Context;
89

910
final _templatePattern = RegExp(
@@ -232,30 +233,31 @@ mixin DocumentationComment
232233
// Count the number of invocations of tools in this dartdoc block,
233234
// so that tools can differentiate different blocks from each other.
234235
invocationIndex++;
235-
return await config.tools.runner.run(
236-
args,
237-
(String message) async =>
238-
warn(PackageWarning.toolError, message: message),
239-
content: basicMatch[2],
240-
environment: {
241-
'SOURCE_LINE': characterLocation?.lineNumber.toString(),
242-
'SOURCE_COLUMN': characterLocation?.columnNumber.toString(),
243-
'SOURCE_PATH':
244-
(sourceFileName == null || package?.packagePath == null)
245-
? null
246-
: pathContext.relative(sourceFileName,
247-
from: package.packagePath),
248-
'PACKAGE_PATH': package?.packagePath,
249-
'PACKAGE_NAME': package?.name,
250-
'LIBRARY_NAME': library?.fullyQualifiedName,
251-
'ELEMENT_NAME': fullyQualifiedNameWithoutLibrary,
252-
'INVOCATION_INDEX': invocationIndex.toString(),
253-
'PACKAGE_INVOCATION_INDEX':
254-
(package.toolInvocationIndex++).toString(),
255-
}..removeWhere((key, value) => value == null));
236+
return await config.tools.runner.run(args, content: basicMatch[2],
237+
toolErrorCallback: (String message) async {
238+
print('toolerrocallback for $args');
239+
warn(PackageWarning.toolError, message: message);
240+
}, environment: _toolsEnvironment(invocationIndex: invocationIndex));
256241
});
257242
}
258243

244+
/// The environment variables to use when running a tool.
245+
Map<String, String> _toolsEnvironment({@required int invocationIndex}) {
246+
return {
247+
'SOURCE_LINE': characterLocation?.lineNumber.toString(),
248+
'SOURCE_COLUMN': characterLocation?.columnNumber.toString(),
249+
if (sourceFileName != null && package?.packagePath != null)
250+
'SOURCE_PATH':
251+
pathContext.relative(sourceFileName, from: package.packagePath),
252+
'PACKAGE_PATH': package?.packagePath,
253+
'PACKAGE_NAME': package?.name,
254+
'LIBRARY_NAME': library?.fullyQualifiedName,
255+
'ELEMENT_NAME': fullyQualifiedNameWithoutLibrary,
256+
'INVOCATION_INDEX': invocationIndex.toString(),
257+
'PACKAGE_INVOCATION_INDEX': (package.toolInvocationIndex++).toString(),
258+
}..removeWhere((key, value) => value == null);
259+
}
260+
259261
/// Replace &#123;@example ...&#125; in API comments with the content of named file.
260262
///
261263
/// Syntax:

lib/src/tool_runner.dart

Lines changed: 90 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ library dartdoc.tool_runner;
77
import 'dart:io' show Process, ProcessException;
88

99
import 'package:analyzer/file_system/file_system.dart';
10+
import 'package:dartdoc/src/dartdoc_options.dart';
1011
import 'package:dartdoc/src/io_utils.dart';
1112
import 'package:dartdoc/src/tool_definition.dart';
13+
import 'package:meta/meta.dart';
1214
import 'package:path/path.dart' as p;
13-
import 'dartdoc_options.dart';
1415

1516
typedef ToolErrorCallback = void Function(String message);
1617
typedef FakeResultCallback = String Function(String tool,
@@ -20,9 +21,6 @@ typedef FakeResultCallback = String Function(String tool,
2021
/// limiting both parallelization and the number of open temporary files.
2122
final MultiFutureTracker<void> _toolTracker = MultiFutureTracker(4);
2223

23-
/// Can be called when the ToolRunner is no longer needed.
24-
///
25-
/// This will remove any temporary files created by the tool runner.
2624
class ToolTempFileTracker {
2725
final ResourceProvider resourceProvider;
2826
final Folder temporaryDirectory;
@@ -41,7 +39,7 @@ class ToolTempFileTracker {
4139

4240
int _temporaryFileCount = 0;
4341

44-
Future<File> createTemporaryFile() async {
42+
File createTemporaryFile() {
4543
_temporaryFileCount++;
4644
// TODO(srawlins): Assume [temporaryDirectory]'s path is always absolute.
4745
var tempFile = resourceProvider.getFile(resourceProvider.pathContext.join(
@@ -80,22 +78,24 @@ class ToolRunner {
8078
String commandPath;
8179

8280
if (isDartSetup) {
83-
commandPath = toolConfiguration.resourceProvider.resolvedExecutable;
81+
commandPath = resourceProvider.resolvedExecutable;
8482
} else {
8583
commandPath = args.removeAt(0);
8684
}
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);
8988
tool.setupComplete = true;
9089
}
9190

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 {
9999
String commandString() => ([commandPath] + args).join(' ');
100100
try {
101101
var result =
@@ -120,58 +120,52 @@ class ToolRunner {
120120
}
121121
}
122122

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.
126124
///
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);
131134
Future<String> runner;
132135
// Prevent too many tools from running simultaneously.
133136
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);
136141
return runner;
137142
});
138143
return runner;
139144
}
140145

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 {
143150
assert(args != null);
144151
assert(args.isNotEmpty);
145152
content ??= '';
146153
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)) {
149156
toolErrorCallback(
150-
'Unable to find definition for tool "$tool" in tool map. '
157+
'Unable to find definition for tool "$toolName" in tool map. '
151158
'Did you add it to dartdoc_options.yaml?');
152159
return '';
153160
}
154-
var toolDefinition = toolConfiguration.tools[tool];
161+
var toolDefinition = toolConfiguration.tools[toolName];
155162
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);
169163

170164
// Substitute the temp filename for the "$INPUT" token, and all of the other
171165
// environment variables. Variables are allowed to either be in $(VAR) form,
172166
// or $VAR form.
173167
var envWithInput = {
174-
'INPUT': pathContext.absolute(tmpFile.path),
168+
'INPUT': _tmpFileWithContent(content),
175169
'TOOL_COMMAND': toolDefinition.command[0],
176170
...environment,
177171
};
@@ -183,17 +177,62 @@ class ToolRunner {
183177
// find out where their script was coming from as an absolute path on the
184178
// filesystem.
185179
envWithInput['DART_SNAPSHOT_CACHE'] = pathContext.absolute(
186-
SnapshotCache.createInstance(toolConfiguration.resourceProvider)
187-
.snapshotCache
188-
.path);
180+
SnapshotCache.createInstance(resourceProvider).snapshotCache.path);
189181
if (toolDefinition.setupCommand != null) {
190182
envWithInput['DART_SETUP_COMMAND'] = toolDefinition.setupCommand[0];
191183
}
192184
}
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) {
193231
var substitutions = envWithInput.map<RegExp, String>((key, value) {
194232
var escapedKey = RegExp.escape(key);
195233
return MapEntry(RegExp('\\\$(\\($escapedKey\\)|$escapedKey\\b)'), value);
196234
});
235+
197236
var argsWithInput = <String>[];
198237
for (var arg in args) {
199238
var newArg = arg;
@@ -202,25 +241,10 @@ class ToolRunner {
202241
argsWithInput.add(newArg);
203242
}
204243

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;
223245
}
224246

225-
p.Context get pathContext => toolConfiguration.resourceProvider.pathContext;
247+
ResourceProvider get resourceProvider => toolConfiguration.resourceProvider;
248+
249+
p.Context get pathContext => resourceProvider.pathContext;
226250
}

test/tool_runner_test.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ echo:
113113
test('can invoke a Dart tool, and second run is a snapshot.', () async {
114114
var result = await runner.run(
115115
['drill', r'--file=$INPUT'],
116-
errorCallback,
117116
content: 'TEST INPUT',
117+
toolErrorCallback: errorCallback,
118118
);
119119
expect(errors, isEmpty);
120120
expect(result, contains('--file=<INPUT_FILE>'));
@@ -125,8 +125,8 @@ echo:
125125
expect(setupFile.existsSync(), isFalse);
126126
result = await runner.run(
127127
['drill', r'--file=$INPUT'],
128-
errorCallback,
129128
content: 'TEST INPUT 2',
129+
toolErrorCallback: errorCallback,
130130
);
131131
expect(errors, isEmpty);
132132
expect(result, contains('--file=<INPUT_FILE>'));
@@ -137,8 +137,8 @@ echo:
137137
test('can invoke a Dart tool', () async {
138138
var result = await runner.run(
139139
['drill', r'--file=$INPUT'],
140-
errorCallback,
141140
content: 'TEST INPUT',
141+
toolErrorCallback: errorCallback,
142142
);
143143
expect(errors, isEmpty);
144144
expect(result, contains('Script location is in snapshot cache.'));
@@ -149,17 +149,17 @@ echo:
149149
test('can invoke a non-Dart tool', () async {
150150
var result = await runner.run(
151151
['non_dart', '--version'],
152-
errorCallback,
153152
content: 'TEST INPUT',
153+
toolErrorCallback: errorCallback,
154154
);
155155
expect(errors, isEmpty);
156156
expect(result, isEmpty); // Output is on stderr.
157157
});
158158
test('can invoke a pre-snapshotted tool', () async {
159159
var result = await runner.run(
160160
['snapshot_drill', r'--file=$INPUT'],
161-
errorCallback,
162161
content: 'TEST INPUT',
162+
toolErrorCallback: errorCallback,
163163
);
164164
expect(errors, isEmpty);
165165
expect(result, contains('--file=<INPUT_FILE>'));
@@ -168,8 +168,8 @@ echo:
168168
test('can invoke a tool with a setup action', () async {
169169
var result = await runner.run(
170170
['setup_drill', r'--file=$INPUT'],
171-
errorCallback,
172171
content: 'TEST INPUT',
172+
toolErrorCallback: errorCallback,
173173
);
174174
expect(errors, isEmpty);
175175
expect(result, contains('--file=<INPUT_FILE>'));
@@ -179,8 +179,8 @@ echo:
179179
test('fails if tool not in tool map', () async {
180180
var result = await runner.run(
181181
['hammer', r'--file=$INPUT'],
182-
errorCallback,
183182
content: 'TEST INPUT',
183+
toolErrorCallback: errorCallback,
184184
);
185185
expect(errors, isNotEmpty);
186186
expect(
@@ -190,8 +190,8 @@ echo:
190190
test('fails if tool returns non-zero status', () async {
191191
var result = await runner.run(
192192
['drill', r'--file=/a/missing/file'],
193-
errorCallback,
194193
content: 'TEST INPUT',
194+
toolErrorCallback: errorCallback,
195195
);
196196
expect(errors, isNotEmpty);
197197
expect(errors[0], contains('Tool "drill" returned non-zero exit code'));
@@ -200,8 +200,8 @@ echo:
200200
test("fails if tool in tool map doesn't exist", () async {
201201
var result = await runner.run(
202202
['missing'],
203-
errorCallback,
204203
content: 'TEST INPUT',
204+
toolErrorCallback: errorCallback,
205205
);
206206
expect(errors, isNotEmpty);
207207
expect(errors[0],

0 commit comments

Comments
 (0)