| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'dart:io'; | |
| import 'dart:isolate'; | |
| import 'dart:math' as math; | |
| import 'package:args/args.dart'; | |
| import 'package:path/path.dart' as p; | |
| import 'core.dart'; | |
| import 'sdk.dart'; | |
| /// For commands where we are able to initialize the [ArgParser], this value | |
| /// is used as the usageLineLength. | |
| int? get dartdevUsageLineLength => | |
| stdout.hasTerminal ? stdout.terminalColumns : null; | |
| /// Runs a tool's snapshot in an isolate. | |
| /// | |
| /// Waits for the spawned isolate to exit before returning. | |
| Future<int> runFromSnapshot({ | |
| required String snapshot, | |
| required List<String> args, | |
| required bool verbose, | |
| }) async { | |
| if (!checkArtifactExists(snapshot)) return 255; | |
| int retval = 0; | |
| final result = Completer<int>(); | |
| final exitPort = ReceivePort() | |
| ..listen((msg) { | |
| result.complete(0); | |
| }); | |
| final errorPort = ReceivePort() | |
| ..listen((error) { | |
| log.stderr(error.toString()); | |
| result.complete(255); | |
| }); | |
| try { | |
| await Isolate.spawnUri( | |
| Uri.file(snapshot), | |
| args, | |
| null, | |
| onExit: exitPort.sendPort, | |
| onError: errorPort.sendPort, | |
| ); | |
| retval = await result.future; | |
| } catch (e, st) { | |
| log.stderr(e.toString()); | |
| if (verbose) { | |
| log.stderr(st.toString()); | |
| } | |
| retval = 255; | |
| } | |
| errorPort.close(); | |
| exitPort.close(); | |
| return retval; | |
| } | |
| /// Global options for dartdev. | |
| /// | |
| /// ** READ THIS BEFORE MODIFYING ** | |
| /// | |
| /// Adding or changing behavior for global flags may have consequences for | |
| /// integration with the VM. Check `runtime/bin/main_options.cc` in the | |
| /// Dart SDK if adding or changing any flags. This is most important for | |
| /// those that are intended to be run without a script such as | |
| /// `dart --disable-analytics` as there is special handling. Any flags | |
| /// added here should also be tested by hand with a compiled SDK as unit tests | |
| /// running `dartdev.dart` directly do not hit that code path. | |
| ArgParser globalDartdevOptionsParser({bool verbose = false}) { | |
| var argParser = ArgParser( | |
| usageLineLength: dartdevUsageLineLength, | |
| allowTrailingOptions: false, | |
| ); | |
| argParser.addFlag('verbose', | |
| abbr: 'v', negatable: false, help: 'Show additional command output.'); | |
| argParser.addFlag('version', | |
| negatable: false, help: 'Print the Dart SDK version.'); | |
| argParser.addFlag('enable-analytics', | |
| negatable: false, help: 'Enable analytics.'); | |
| argParser.addFlag('disable-analytics', | |
| negatable: false, help: 'Disable analytics.'); | |
| argParser.addFlag('disable-telemetry', | |
| negatable: false, help: 'Disable telemetry.', hide: true); | |
| argParser.addFlag('diagnostics', | |
| negatable: false, help: 'Show tool diagnostic output.', hide: !verbose); | |
| argParser.addFlag( | |
| 'analytics', | |
| defaultsTo: true, | |
| negatable: true, | |
| help: 'Allow or disallow analytics for this `dart *` run without ' | |
| 'changing the analytics configuration. ' | |
| 'Deprecated: use `--suppress-analytics` instead.', | |
| hide: true, | |
| ); | |
| argParser.addFlag( | |
| 'suppress-analytics', | |
| negatable: false, | |
| help: 'Disallow analytics for this `dart *` run without changing the ' | |
| 'analytics configuration.', | |
| ); | |
| return argParser; | |
| } | |
| /// Emit the given word with the correct pluralization. | |
| String pluralize(String word, int count) => count == 1 ? word : '${word}s'; | |
| /// Make an absolute [filePath] relative to [dir] (for display purposes). | |
| String relativePath(String filePath, Directory dir) { | |
| var root = dir.absolute.path; | |
| if (filePath.startsWith(root)) { | |
| return filePath.substring(root.length + 1); | |
| } | |
| return filePath; | |
| } | |
| /// String utility to trim some suffix from the end of a [String]. | |
| String trimEnd(String s, String? suffix) { | |
| if (suffix != null && suffix.isNotEmpty && s.endsWith(suffix)) { | |
| return s.substring(0, s.length - suffix.length); | |
| } | |
| return s; | |
| } | |
| extension FileSystemEntityExtension on FileSystemEntity { | |
| String get name => p.basename(path); | |
| String get basenameWithoutExtension => p.basenameWithoutExtension(path); | |
| bool get isDartFile => this is File && p.extension(path) == '.dart'; | |
| } | |
| /// Wraps [text] to the given [width], if provided. | |
| String wrapText(String text, {int? width}) { | |
| if (width == null) { | |
| return text; | |
| } | |
| var buffer = StringBuffer(); | |
| var lineMaxEndIndex = width; | |
| var lineStartIndex = 0; | |
| while (true) { | |
| if (lineMaxEndIndex >= text.length) { | |
| buffer.write(text.substring(lineStartIndex, text.length)); | |
| break; | |
| } else { | |
| var lastSpaceIndex = text.lastIndexOf(' ', lineMaxEndIndex); | |
| if (lastSpaceIndex == -1 || lastSpaceIndex <= lineStartIndex) { | |
| // No space between [lineStartIndex] and [lineMaxEndIndex]. Get the | |
| // _next_ space. | |
| lastSpaceIndex = text.indexOf(' ', lineMaxEndIndex); | |
| if (lastSpaceIndex == -1) { | |
| // No space at all after [lineStartIndex]. | |
| lastSpaceIndex = text.length; | |
| buffer.write(text.substring(lineStartIndex, lastSpaceIndex)); | |
| break; | |
| } | |
| } | |
| buffer.write(text.substring(lineStartIndex, lastSpaceIndex)); | |
| buffer.writeln(); | |
| lineStartIndex = lastSpaceIndex + 1; | |
| } | |
| lineMaxEndIndex = lineStartIndex + width; | |
| } | |
| return buffer.toString(); | |
| } | |
| // A valid Dart identifier that can be used for a package, i.e. no | |
| // capital letters. | |
| // https://dart.dev/guides/language/language-tour#important-concepts | |
| final RegExp _identifierRegExp = RegExp(r'^[a-z_][a-z\d_]*$'); | |
| // non-contextual dart keywords. | |
| // https://dart.dev/guides/language/language-tour#keywords | |
| const Set<String> _keywords = <String>{ | |
| 'abstract', | |
| 'as', | |
| 'assert', | |
| 'async', | |
| 'await', | |
| 'break', | |
| 'case', | |
| 'catch', | |
| 'class', | |
| 'const', | |
| 'continue', | |
| 'covariant', | |
| 'default', | |
| 'deferred', | |
| 'do', | |
| 'dynamic', | |
| 'else', | |
| 'enum', | |
| 'export', | |
| 'extends', | |
| 'extension', | |
| 'external', | |
| 'factory', | |
| 'false', | |
| 'final', | |
| 'finally', | |
| 'for', | |
| 'function', | |
| 'get', | |
| 'hide', | |
| 'if', | |
| 'implements', | |
| 'import', | |
| 'in', | |
| 'inout', | |
| 'interface', | |
| 'is', | |
| 'late', | |
| 'library', | |
| 'mixin', | |
| 'native', | |
| 'new', | |
| 'null', | |
| 'of', | |
| 'on', | |
| 'operator', | |
| 'out', | |
| 'part', | |
| 'patch', | |
| 'required', | |
| 'rethrow', | |
| 'return', | |
| 'set', | |
| 'show', | |
| 'source', | |
| 'static', | |
| 'super', | |
| 'switch', | |
| 'sync', | |
| 'this', | |
| 'throw', | |
| 'true', | |
| 'try', | |
| 'typedef', | |
| 'var', | |
| 'void', | |
| 'while', | |
| 'with', | |
| 'yield', | |
| }; | |
| /// Whether [name] is a valid Pub package. | |
| bool isValidPackageName(String name) => | |
| _identifierRegExp.hasMatch(name) && !_keywords.contains(name); | |
| /// Convert a directory name into a reasonably legal pub package name. | |
| String normalizeProjectName(String name) { | |
| name = name.replaceAll('-', '_').replaceAll(' ', '_'); | |
| // Strip any extension (like .dart). | |
| var dotIndex = name.indexOf('.'); | |
| if (dotIndex >= 0) { | |
| name = name.substring(0, dotIndex); | |
| } | |
| return name; | |
| } | |
| /// A utility class to generate a markdown table into a string. | |
| /// | |
| /// To use this class: | |
| /// | |
| /// ``` | |
| /// var table = MarkdownTable(); | |
| /// for (var foo in foos) { | |
| /// table.startRow() | |
| /// ..cell(foo.bar) | |
| /// ..cell(foo.baz.toStringAsFixed(1), right: true) | |
| /// ..cell(foo.qux); | |
| /// } | |
| /// print(table.finish()); | |
| /// ``` | |
| class MarkdownTable { | |
| static const int defaultMaxWidth = 90; | |
| static const int _minWidth = 3; | |
| final List<List<_MarkdownCell>> _data = []; | |
| MarkdownRow startRow() { | |
| _data.add([]); | |
| return MarkdownRow(this); | |
| } | |
| String finish() { | |
| if (_data.isEmpty) return ''; | |
| var header = _data.first; | |
| var widths = <int>[]; | |
| for (int col = 0; col < header.length; col++) { | |
| var width = _data.map((row) { | |
| var item = row.length >= col ? row[col] : null; | |
| return item?.value.length ?? 0; | |
| }).reduce(math.max); | |
| widths.add(math.max(width, _minWidth)); | |
| } | |
| var buffer = StringBuffer(); | |
| for (var row in _data) { | |
| buffer.write('| '); | |
| for (int col = 0; col < row.length; col++) { | |
| if (col != 0) buffer.write(' | '); | |
| var cell = row[col]; | |
| var width = math.min(widths[col], defaultMaxWidth); | |
| var value = cell.value; | |
| buffer.write(cell.right ? value.padLeft(width) : value.padRight(width)); | |
| } | |
| buffer.writeln(' |'); | |
| if (row == _data.first) { | |
| // Write the alignment row. | |
| buffer.write('| '); | |
| for (int col = 0; col < row.length; col++) { | |
| if (col != 0) buffer.write(' | '); | |
| var cell = row[col]; | |
| var width = math.min(widths[col], defaultMaxWidth); | |
| var value = cell.right ? '--:' : '---'; | |
| buffer.write(value.padLeft(width, '-')); | |
| } | |
| buffer.writeln(' |'); | |
| } | |
| } | |
| return buffer.toString(); | |
| } | |
| } | |
| /// Used to build a row in a markdown table. | |
| class MarkdownRow { | |
| final MarkdownTable _table; | |
| MarkdownRow(this._table); | |
| void cell(String value, {bool right = false}) { | |
| _table._data.last.add(_MarkdownCell(value, right)); | |
| } | |
| } | |
| class _MarkdownCell { | |
| final String value; | |
| final bool right; | |
| _MarkdownCell(this.value, this.right); | |
| } | |
| /// The lowest macOS version that Dart supports. | |
| /// | |
| /// From https://dart.dev/get-dart#system-requirements | |
| const minimumSupportedMacOSVersion = 12; |