Skip to content

Move ToolDefinition and related classes out of dartdoc_options #2686

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/dartdoc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import 'package:dartdoc/src/logging.dart';
import 'package:dartdoc/src/markdown_processor.dart' show markdownStats;
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/tool_definition.dart';
import 'package:dartdoc/src/tool_runner.dart';
import 'package:dartdoc/src/tuple.dart';
import 'package:dartdoc/src/utils.dart';
Expand Down
227 changes: 7 additions & 220 deletions lib/src/dartdoc_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import 'package:dartdoc/dartdoc.dart';
import 'package:dartdoc/src/experiment_options.dart';
import 'package:dartdoc/src/io_utils.dart';
import 'package:dartdoc/src/source_linker.dart';
import 'package:dartdoc/src/tool_definition.dart';
import 'package:dartdoc/src/tool_runner.dart';
import 'package:dartdoc/src/tuple.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as p show Context, canonicalize, extension;
import 'package:path/path.dart' as p show Context, canonicalize;
import 'package:yaml/yaml.dart';

/// Constants to help with type checking, because T is int and so forth
Expand All @@ -39,7 +39,7 @@ const int _kIntVal = 0;
const double _kDoubleVal = 0.0;
const bool _kBoolVal = true;

const String _kCompileArgsTagName = 'compile_args';
const String compileArgsTagName = 'compile_args';

int get _usageLineLength => stdout.hasTerminal ? stdout.terminalColumns : null;

Expand Down Expand Up @@ -114,219 +114,6 @@ class CategoryConfiguration {
}
}

/// Defines the attributes of a tool in the options file, corresponding to
/// the 'tools' keyword in the options file, and populated by the
/// [ToolConfiguration] class.
class ToolDefinition {
/// A list containing the command and options to be run for this tool. The
/// first argument in the command is the tool executable, and will have its
/// path evaluated relative to the `dartdoc_options.yaml` location. Must not
/// be an empty list, or be null.
final List<String> command;

/// A list containing the command and options to setup phase for this tool.
/// The first argument in the command is the tool executable, and will have
/// its path evaluated relative to the `dartdoc_options.yaml` location. May
/// be null or empty, in which case it will be ignored at setup time.
final List<String> setupCommand;

/// A description of the defined tool. Must not be null.
final String description;

/// If set, then the setup command has been run once for this tool definition.
bool setupComplete = false;

/// Returns true if the given executable path has an extension recognized as a
/// Dart extension (e.g. '.dart' or '.snapshot').
static bool isDartExecutable(String executable) {
var extension = p.extension(executable);
return extension == '.dart' || extension == '.snapshot';
}

/// Creates a ToolDefinition or subclass that is appropriate for the command
/// given.
factory ToolDefinition.fromCommand(
List<String> command,
List<String> setupCommand,
String description,
ResourceProvider resourceProvider,
{List<String> compileArgs}) {
assert(command != null);
assert(command.isNotEmpty);
assert(description != null);
if (isDartExecutable(command[0])) {
return DartToolDefinition(
command, setupCommand, description, resourceProvider,
compileArgs: compileArgs ?? const []);
} else {
if (compileArgs != null && compileArgs.isNotEmpty) {
throw DartdocOptionError(
'Compile arguments may only be specified for Dart tools, but '
'$_kCompileArgsTagName of $compileArgs were specified for '
'$command.');
}
return ToolDefinition(command, setupCommand, description);
}
}

ToolDefinition(this.command, this.setupCommand, this.description)
: assert(command != null),
assert(command.isNotEmpty),
assert(description != null);

@override
String toString() {
final commandString =
'${this is DartToolDefinition ? '(Dart) ' : ''}"${command.join(' ')}"';
if (setupCommand == null) {
return '$runtimeType: $commandString ($description)';
} else {
return '$runtimeType: $commandString, with setup command '
'"${setupCommand.join(' ')}" ($description)';
}
}
}

/// Manages the creation of a single snapshot file in a context where multiple
/// async functions could be trying to use and/or create it.
///
/// To use:
///
/// var s = new Snapshot(...);
///
/// if (s.needsSnapshot) {
/// // create s.snapshotFile, then call:
/// s.snapshotCompleted();
/// } else {
/// await snapshotValid();
/// // use existing s.snapshotFile;
/// }
///
class Snapshot {
File _snapshotFile;

// TODO(srawlins): Deprecate this public getter; change private field to just
// be the absolute path.
File get snapshotFile => _snapshotFile;
final Completer<void> _snapshotCompleter = Completer();

Snapshot(Folder snapshotCache, String toolPath, int serial,
ResourceProvider resourceProvider) {
if (toolPath.endsWith('.snapshot')) {
_needsSnapshot = false;
_snapshotFile = resourceProvider.getFile(toolPath);
snapshotCompleted();
} else {
_snapshotFile = resourceProvider.getFile(resourceProvider.pathContext
.join(resourceProvider.pathContext.absolute(snapshotCache.path),
'snapshot_$serial'));
}
}

bool _needsSnapshot = true;

/// Will return true precisely once, unless [snapshotFile] was already a
/// snapshot. In that case, will always return false.
bool get needsSnapshot {
if (_needsSnapshot == true) {
_needsSnapshot = false;
return true;
}
return _needsSnapshot;
}

Future<void> snapshotValid() => _snapshotCompleter.future;

void snapshotCompleted() => _snapshotCompleter.complete();
}

/// A singleton that keeps track of cached snapshot files. The [dispose]
/// function must be called before process exit to clean up snapshots in the
/// cache.
class SnapshotCache {
static SnapshotCache _instance;

// TODO(srawlins): Make this final.
Folder snapshotCache;
final ResourceProvider _resourceProvider;
final Map<String, Snapshot> snapshots = {};
int _serial = 0;

SnapshotCache._(this._resourceProvider)
: snapshotCache =
_resourceProvider.createSystemTemp('dartdoc_snapshot_cache_');

static SnapshotCache get instance => _instance;

static SnapshotCache createInstance(ResourceProvider resourceProvider) =>
_instance ??= SnapshotCache._(resourceProvider);

Snapshot getSnapshot(String toolPath) {
if (snapshots.containsKey(toolPath)) {
return snapshots[toolPath];
}
snapshots[toolPath] =
Snapshot(snapshotCache, toolPath, _serial, _resourceProvider);
_serial++;
return snapshots[toolPath];
}

void dispose() {
_instance = null;
if (snapshotCache != null && snapshotCache.exists) {
return snapshotCache.delete();
}
return null;
}
}

/// A special kind of tool definition for Dart commands.
class DartToolDefinition extends ToolDefinition {
final ResourceProvider _resourceProvider;

/// A list of arguments to add to the snapshot compilation arguments.
final List<String> compileArgs;

/// Takes a list of args to modify, and returns the name of the executable
/// to run. If no snapshot file existed, then create one and modify the args
/// so that if they are executed with dart, will result in the snapshot being
/// built.
Future<Tuple2<String, Function()>> modifyArgsToCreateSnapshotIfNeeded(
List<String> args) async {
assert(args[0] == command.first);
// Set up flags to create a new snapshot, if needed, and use the first run
// as the training run.
SnapshotCache.createInstance(_resourceProvider);
var snapshot = SnapshotCache.instance.getSnapshot(command.first);
var snapshotFile = snapshot.snapshotFile;
var needsSnapshot = snapshot.needsSnapshot;
if (needsSnapshot) {
args.insertAll(0, [
// TODO(jcollins-g): remove ignore and verbosity resets once
// https://dart-review.googlesource.com/c/sdk/+/181421 is safely
// in the rearview mirror in dev/Flutter.
'--ignore-unrecognized-flags',
'--verbosity=error',
'--snapshot=${_resourceProvider.pathContext.absolute(snapshotFile.path)}',
'--snapshot_kind=app-jit',
...compileArgs,
]);
} else {
await snapshot.snapshotValid();
// replace the first argument with the path to the snapshot.
args[0] = _resourceProvider.pathContext.absolute(snapshotFile.path);
}
return Tuple2(_resourceProvider.resolvedExecutable,
needsSnapshot ? snapshot.snapshotCompleted : null);
}

DartToolDefinition(List<String> command, List<String> setupCommand,
String description, this._resourceProvider,
{this.compileArgs = const []})
: assert(compileArgs != null),
super(command, setupCommand, description);
}

/// A configuration class that can interpret [ToolDefinition]s from a YAML map.
class ToolConfiguration {
final Map<String, ToolDefinition> tools;
Expand Down Expand Up @@ -396,17 +183,17 @@ class ToolConfiguration {

List<String> findArgs() {
List<String> args;
if (toolMap.containsKey(_kCompileArgsTagName)) {
var compileArgs = toolMap[_kCompileArgsTagName];
if (toolMap.containsKey(compileArgsTagName)) {
var compileArgs = toolMap[compileArgsTagName];
if (compileArgs is String) {
args = [toolMap[_kCompileArgsTagName].toString()];
args = [toolMap[compileArgsTagName].toString()];
} else if (compileArgs is YamlList) {
args =
compileArgs.map<String>((node) => node.toString()).toList();
} else {
throw DartdocOptionError(
'Tool compile arguments must be a list of strings. The tool '
'$name has a $_kCompileArgsTagName entry that is a '
'$name has a $compileArgsTagName entry that is a '
'${compileArgs.runtimeType}');
}
}
Expand Down
Loading