blob: f2e3e522d1b7aaf1c685a1b8847698caf0002cef [file] [log] [blame]
// Copyright 2020 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This example shows how to send a bunch of jobs to ProcessPool for processing.
//
// This example program is actually pretty useful even if you don't use
// process_runner for your Dart project. It can speed up processing of a bunch
// of single-threaded CPU-intensive commands by a multple of the number of
// processor cores you have (modulo being disk/network bound, of course).
// @dart = 2.10
import 'dart:io';
import 'package:process_runner/process_runner.dart';
// This only works for escaped spaces and things in double or single quotes.
// This is just an example, modify to meet your own requirements.
List<String> splitIntoArgs(String args) {
bool inQuote = false;
bool inEscape = false;
String quoteMatch = '';
final List<String> result = <String>[];
final List<String> currentArg = <String>[];
for (int i = 0; i < args.length; ++i) {
final String char = args[i];
if (inEscape) {
switch (char) {
case 'n':
currentArg.add('\n');
break;
case 't':
currentArg.add('\t');
break;
case 'r':
currentArg.add('\r');
break;
case 'b':
currentArg.add('\b');
break;
default:
currentArg.add(char);
break;
}
inEscape = false;
continue;
}
if (char == ' ' && !inQuote) {
result.add(currentArg.join(''));
currentArg.clear();
continue;
}
if (char == r'\') {
inEscape = true;
continue;
}
if (inQuote) {
if (char == quoteMatch) {
inQuote = false;
quoteMatch = '';
} else {
currentArg.add(char);
}
continue;
}
if (char == '"' || char == '"') {
inQuote = !inQuote;
quoteMatch = args[i];
continue;
}
currentArg.add(char);
}
if (currentArg.isNotEmpty) {
result.add(currentArg.join(''));
}
return result;
}
String usage() {
return '''
main.dart [flags]
--[no-]help Print help.
--[no-]report Print progress on the jobs while running.
-w, --workers Specify the number of workers jobs to run simultanously. Defaults to the number of processors on the machine.
-d, --workingDirectory Specify the working directory to run on
(defaults to ".")
-c, --cmd Specify a command to add to the commands to be run. Entire command must be quoted by the shell. Commands specified with this option run before those specified with --cmdFile
-f, --file Specify the name of a file to read commands from, one per line, as they would appear on the command line, with spaces escaped or quoted. Specify "-" to read from stdin.
(defaults to "-")
''';
}
String? findOption(String option, List<String> args) {
for (int i = 0; i < args.length - 1; ++i) {
if (args[i] == option) {
return args[i + 1];
}
}
return null;
}
Iterable<String> findAllOptions(String option, List<String> args) sync* {
for (int i = 0; i < args.length - 1; ++i) {
if (args[i] == option) {
yield args[i + 1];
}
}
}
Future<void> main(List<String> args) async {
// Parse args without ArgParser until ArgParser is null-safe.
if (args.contains('--help')) {
print('main.dart [flags]');
print(usage());
exit(0);
}
final bool printReport = args.contains('--report');
// If the numWorkers is set to null, then the ProcessPool will automatically
// select the number of processes based on how many CPU cores the machine has.
final int? numWorkers = int.tryParse(findOption('workers', args) ?? '');
final Directory workingDirectory = Directory(findOption('workingDirectory', args) ?? '.');
final List<String> cmds = findAllOptions('cmd', args).toList();
// Collect the commands to be run from the command file.
final String commandFile = findOption('file', args) ?? '-';
List<String> fileCommands = <String>[];
// Read from stdin if the --file option is set to '-'.
if (commandFile == '-') {
String? line = stdin.readLineSync();
while (line != null) {
fileCommands.add(line);
line = stdin.readLineSync();
}
} else {
// Read the commands from a file.
final File cmdFile = File(commandFile);
if (!cmdFile.existsSync()) {
print('Command file "$commandFile" doesn\'t exist.');
exit(1);
}
fileCommands = cmdFile.readAsLinesSync();
}
// Collect all the commands, both from the input file, and from the command
// line. The command line commands come first (although they could all be
// executed simultaneously, depending on the number of workers, and number of
// commands).
final List<String> commands = <String>[
...cmds,
...fileCommands,
];
// Split each command entry into a list of strings, taking into account some
// simple quoting and escaping.
final List<List<String>> splitCommands = commands.map<List<String>>(splitIntoArgs).toList();
final ProcessPool pool = ProcessPool(
numWorkers: numWorkers,
printReport: printReport ? ProcessPool.defaultPrintReport : null,
);
final List<WorkerJob> jobs = splitCommands.map<WorkerJob>((List<String> command) {
return WorkerJob(command, workingDirectory: workingDirectory);
}).toList();
await for (final WorkerJob done in pool.startWorkers(jobs)) {
if (printReport) {
print('\nFinished job ${done.name}');
}
stdout.write(done.result!.stdout);
}
}