| // 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); |
| } |
| } |