blob: 07f532261f70fd628626c46f2ca7b66ca31bc7f6 [file] [log] [blame]
// Copyright (c) 2019, 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 'package:args/args.dart';
import 'package:dwds/dwds.dart';
import 'package:logging/logging.dart';
import '../logging.dart';
import 'shared.dart';
const autoOption = 'auto';
const chromeDebugPortFlag = 'chrome-debug-port';
const debugExtensionFlag = 'debug-extension';
const debugFlag = 'debug';
const enableInjectedClientFlag = 'injected-client';
const hostnameFlag = 'hostname';
const tlsCertChainFlag = 'tls-cert-chain';
const tlsCertKeyFlag = 'tls-cert-key';
const hotReloadFlag = 'hot-reload';
const hotRestartFlag = 'hot-restart';
const launchAppOption = 'launch-app';
const launchInChromeFlag = 'launch-in-chrome';
const userDataDirFlag = 'user-data-dir';
const liveReloadFlag = 'live-reload';
const logRequestsFlag = 'log-requests';
const outputFlag = 'output';
const outputNone = 'NONE';
const releaseFlag = 'release';
const requireBuildWebCompilersFlag = 'build-web-compilers';
const enableExpressionEvaluationFlag = 'enable-expression-evaluation';
const verboseFlag = 'verbose';
const nullSafetyFlag = 'null-safety';
const nullSafetySound = 'sound';
const nullSafetyUnsound = 'unsound';
const nullSafetyAuto = 'auto';
const disableDdsFlag = 'disable-dds';
const enableExperimentOption = 'enable-experiment';
const canaryFeaturesFlag = 'canary';
const offlineFlag = 'offline';
ReloadConfiguration _parseReloadConfiguration(ArgResults argResults) {
var auto = argResults.options.contains(autoOption)
? argResults[autoOption] as String?
: null;
void handleDeprecatedFlag(String flag, String autoFallback) {
if (argResults.options.contains(flag) &&
argResults.wasParsed(flag) &&
(argResults[flag] as bool? ?? false)) {
logWriter(
Level.WARNING,
'--$flag is deprecated please use --auto=$autoFallback instead '
'(this has been automatically selected).');
if (auto != null) {
logWriter(
Level.WARNING,
'Only one --auto option is allowed, got $flag which corresponds '
'to --auto=$autoFallback, but already got --auto=$auto');
} else {
auto = autoFallback;
}
}
}
handleDeprecatedFlag(hotReloadFlag, 'reload');
handleDeprecatedFlag(hotRestartFlag, 'restart');
handleDeprecatedFlag(liveReloadFlag, 'refresh');
switch (auto) {
case 'reload':
logWriter(
Level.WARNING,
'Hot reload is not yet supported for web projects. '
'Please try --auto=restart instead.');
return ReloadConfiguration.none;
case 'restart':
return ReloadConfiguration.hotRestart;
case 'refresh':
return ReloadConfiguration.liveReload;
default:
return ReloadConfiguration.none;
}
}
class Configuration {
final bool? _autoRun;
final int? _chromeDebugPort;
final bool? _debugExtension;
final bool? _debug;
final bool? _enableInjectedClient;
final String? _hostname;
final String? _tlsCertChain;
final String? _tlsCertKey;
final List<String>? _launchApps;
final bool? _launchInChrome;
final String? _userDataDir;
final bool? _logRequests;
final String? _output;
final String? outputInput;
final String? outputPath;
final bool? _release;
final ReloadConfiguration? _reload;
final bool? _requireBuildWebCompilers;
final bool? _enableExpressionEvaluation;
final bool? _verbose;
final bool? _disableDds;
final String? _nullSafety;
final List<String>? _experiments;
final bool? _canaryFeatures;
final bool? _offline;
Configuration({
bool? autoRun,
int? chromeDebugPort,
bool? debugExtension,
bool? debug,
bool? enableInjectedClient,
String? hostname,
String? tlsCertChain,
String? tlsCertKey,
List<String>? launchApps,
bool? launchInChrome,
String? userDataDir,
bool? logRequests,
String? output,
this.outputInput,
this.outputPath,
ReloadConfiguration? reload,
bool? release,
bool? requireBuildWebCompilers,
bool? enableExpressionEvaluation,
bool? verbose,
bool? disableDds,
String? nullSafety,
List<String>? experiments,
bool? canaryFeatures,
bool? offline,
}) : _autoRun = autoRun,
_chromeDebugPort = chromeDebugPort,
_debugExtension = debugExtension,
_debug = debug,
_enableInjectedClient = enableInjectedClient,
_hostname = hostname,
_tlsCertChain = tlsCertChain,
_tlsCertKey = tlsCertKey,
_launchApps = launchApps,
_launchInChrome = launchInChrome,
_userDataDir = userDataDir,
_logRequests = logRequests,
_output = output,
_release = release,
_reload = reload,
_requireBuildWebCompilers = requireBuildWebCompilers,
_disableDds = disableDds,
_enableExpressionEvaluation = enableExpressionEvaluation,
_verbose = verbose,
_nullSafety = nullSafety,
_experiments = experiments,
_canaryFeatures = canaryFeatures,
_offline = offline {
_validateConfiguration();
}
void _validateConfiguration() {
// Both null and an empty string are valid values for outputInput. For any
// other value, we need to ensure it's a top-level dir.
if (outputInput?.isNotEmpty ?? false) ensureIsTopLevelDir(outputInput!);
if ((tlsCertKey != null && tlsCertChain == null) ||
(tlsCertKey == null && tlsCertChain != null)) {
throw InvalidConfiguration(
'Must use --$tlsCertKey with --$tlsCertChain.');
}
if (debug && chromeDebugPort == 0 && !launchInChrome) {
throw InvalidConfiguration(
'Must either use --$chromeDebugPortFlag or --$launchInChromeFlag '
'with --$debugFlag.');
}
// Check that no debugging options were passed if the injected client is
// disabled.
if (!enableInjectedClient) {
if (debug) {
throw InvalidConfiguration(
'--$debugFlag cannot be used with --no-$enableInjectedClientFlag');
}
if (debugExtension) {
throw InvalidConfiguration('--$debugExtensionFlag cannot be used with '
'--no-$enableInjectedClientFlag');
}
if (chromeDebugPort != 0) {
throw InvalidConfiguration('--$chromeDebugPortFlag cannot be used '
'with --no-$enableInjectedClientFlag');
}
}
if (launchApps.isNotEmpty && !launchInChrome) {
throw InvalidConfiguration(
'--$launchAppOption can only be used with --$launchInChromeFlag');
}
if (userDataDir != null && !launchInChrome) {
throw InvalidConfiguration(
'--$userDataDir can only be used with --$launchInChromeFlag');
}
}
/// Creates a new [Configuration] with all non-null fields from
/// [other] overriding the values of `this`.
Configuration _override(Configuration other) => Configuration(
autoRun: other._autoRun ?? _autoRun,
chromeDebugPort: other._chromeDebugPort ?? _chromeDebugPort,
debugExtension: other._debugExtension ?? _debugExtension,
debug: other._debug ?? _debug,
enableInjectedClient:
other._enableInjectedClient ?? _enableInjectedClient,
hostname: other._hostname ?? _hostname,
tlsCertChain: other._tlsCertChain ?? _tlsCertChain,
tlsCertKey: other._tlsCertKey ?? _tlsCertKey,
launchApps: other._launchApps ?? _launchApps,
launchInChrome: other._launchInChrome ?? _launchInChrome,
userDataDir: other._userDataDir ?? _userDataDir,
logRequests: other._logRequests ?? _logRequests,
output: other._output ?? _output,
release: other._release ?? _release,
reload: other._reload ?? _reload,
requireBuildWebCompilers:
other._requireBuildWebCompilers ?? _requireBuildWebCompilers,
disableDds: other._disableDds ?? _disableDds,
enableExpressionEvaluation:
other._enableExpressionEvaluation ?? _enableExpressionEvaluation,
verbose: other._verbose ?? _verbose,
nullSafety: other._nullSafety ?? _nullSafety,
experiments: other._experiments ?? _experiments,
canaryFeatures: other._canaryFeatures ?? _canaryFeatures,
offline: other._offline ?? _offline);
factory Configuration.noInjectedClientDefaults() =>
Configuration(autoRun: false, debug: false, debugExtension: false);
// Whether the application should automatically run when loaded.
bool get autoRun => _autoRun ?? true;
int get chromeDebugPort => _chromeDebugPort ?? 0;
bool get debugExtension => _debugExtension ?? false;
bool get debug => _debug ?? false;
bool get disableDds => _disableDds ?? false;
bool get enableInjectedClient => _enableInjectedClient ?? true;
String get hostname => _hostname ?? 'localhost';
String? get tlsCertChain => _tlsCertChain;
String? get tlsCertKey => _tlsCertKey;
List<String> get launchApps => _launchApps ?? [];
bool get launchInChrome => _launchInChrome ?? false;
String? get userDataDir => _userDataDir;
bool get logRequests => _logRequests ?? false;
String get output => _output ?? outputNone;
bool get release => _release ?? false;
ReloadConfiguration get reload => _reload ?? ReloadConfiguration.none;
bool get requireBuildWebCompilers => _requireBuildWebCompilers ?? true;
bool get enableExpressionEvaluation => _enableExpressionEvaluation ?? true;
bool get verbose => _verbose ?? false;
/// Null safety mode:
///
/// 'sound', 'unsound', or 'auto'.
/// 'auto' indicates that the default `package:build_web_compilers`
/// behavior should be used.
String get nullSafety => _nullSafety ?? 'auto';
List<String> get experiments => _experiments ?? [];
bool get canaryFeatures => _canaryFeatures ?? false;
bool get offline => _offline ?? false;
/// Returns a new configuration with values updated from the parsed args.
static Configuration fromArgs(ArgResults? argResults,
{Configuration? defaultConfiguration}) {
defaultConfiguration ??= Configuration();
if (argResults == null) return defaultConfiguration;
final enableInjectedClient =
argResults.options.contains(enableInjectedClientFlag)
? (argResults[enableInjectedClientFlag] as bool)
: defaultConfiguration.enableInjectedClient;
// Change the defaults when we have no injected client to disable all
// debugging features.
//
// After parsing we check these defaults weren't overridden as well.
if (!enableInjectedClient) {
defaultConfiguration = defaultConfiguration
._override(Configuration.noInjectedClientDefaults());
}
final chromeDebugPort = argResults.options.contains(chromeDebugPortFlag)
? int.parse(argResults[chromeDebugPortFlag] as String)
: defaultConfiguration.chromeDebugPort;
final debugExtension = argResults.options.contains(debugExtensionFlag)
? argResults[debugExtensionFlag] as bool?
: defaultConfiguration.debugExtension;
final debug = argResults.options.contains(debugFlag)
? argResults[debugFlag] as bool?
: defaultConfiguration.debug;
final hostname = argResults.options.contains(hostnameFlag)
? argResults[hostnameFlag] as String?
: defaultConfiguration.hostname;
final tlsCertChain = argResults.options.contains(tlsCertChainFlag)
? argResults[tlsCertChainFlag] as String?
: defaultConfiguration.tlsCertChain;
final tlsCertKey = argResults.options.contains(tlsCertKeyFlag)
? argResults[tlsCertKeyFlag] as String?
: defaultConfiguration.tlsCertKey;
final launchApps = argResults.options.contains(launchAppOption) &&
argResults.wasParsed(launchAppOption)
? argResults[launchAppOption] as List<String>?
: defaultConfiguration.launchApps;
final launchInChrome = argResults.options.contains(launchInChromeFlag) &&
argResults.wasParsed(launchInChromeFlag)
? argResults[launchInChromeFlag] as bool?
// We want to default to launch chrome if the user provides just --debug
// and not --chrome-debug-port.
: debug! &&
!(argResults.options.contains(launchInChromeFlag) &&
argResults.wasParsed(chromeDebugPortFlag))
? true
: defaultConfiguration.launchInChrome;
final userDataDir = argResults.options.contains(userDataDirFlag)
? argResults[userDataDirFlag] as String?
: defaultConfiguration.userDataDir;
final logRequests = argResults.options.contains(logRequestsFlag)
? argResults[logRequestsFlag] as bool?
: defaultConfiguration.logRequests;
final output = argResults.options.contains(outputFlag)
? argResults[outputFlag] as String?
: defaultConfiguration.output;
String? outputPath;
String outputInput;
if (output == 'NONE' || output == null) {
// The empty string means build everything.
outputInput = '';
} else {
final splitOutput = output.split(':');
if (splitOutput.length == 1) {
// The empty string means build everything.
outputInput = '';
outputPath = output;
} else {
outputInput = splitOutput.first;
outputPath = splitOutput.skip(1).join(':');
}
}
final release = argResults.options.contains(releaseFlag)
? argResults[releaseFlag] as bool?
: defaultConfiguration.release;
final requireBuildWebCompilers =
argResults.options.contains(requireBuildWebCompilersFlag)
? argResults[requireBuildWebCompilersFlag] as bool?
: defaultConfiguration.requireBuildWebCompilers;
final enableExpressionEvaluation =
argResults.options.contains(enableExpressionEvaluationFlag)
? argResults[enableExpressionEvaluationFlag] as bool?
: defaultConfiguration.enableExpressionEvaluation;
final verbose = argResults.options.contains(verboseFlag)
? argResults[verboseFlag] as bool?
: defaultConfiguration.verbose;
final nullSafety = argResults.options.contains(nullSafetyFlag)
? argResults[nullSafetyFlag] as String?
: defaultConfiguration.nullSafety;
final disableDds = argResults.options.contains(disableDdsFlag)
? argResults[disableDdsFlag] as bool?
: defaultConfiguration.disableDds;
final experiments = argResults.options.contains(enableExperimentOption) &&
argResults.wasParsed(enableExperimentOption)
? argResults[enableExperimentOption] as List<String>?
: defaultConfiguration.experiments;
final canaryFeatures = argResults.options.contains(canaryFeaturesFlag)
? argResults[canaryFeaturesFlag] as bool?
: defaultConfiguration.canaryFeatures;
final offline = argResults.options.contains(offlineFlag)
? argResults[offlineFlag] as bool?
: defaultConfiguration.verbose;
return Configuration(
autoRun: defaultConfiguration.autoRun,
chromeDebugPort: chromeDebugPort,
debugExtension: debugExtension,
debug: debug,
enableInjectedClient: enableInjectedClient,
hostname: hostname,
tlsCertChain: tlsCertChain,
tlsCertKey: tlsCertKey,
launchApps: launchApps,
launchInChrome: launchInChrome,
userDataDir: userDataDir,
logRequests: logRequests,
output: output,
outputInput: outputInput,
outputPath: outputPath,
release: release,
reload: _parseReloadConfiguration(argResults),
requireBuildWebCompilers: requireBuildWebCompilers,
disableDds: disableDds,
enableExpressionEvaluation: enableExpressionEvaluation,
verbose: verbose,
nullSafety: nullSafety,
experiments: experiments,
canaryFeatures: canaryFeatures,
offline: offline,
);
}
}
class InvalidConfiguration implements Exception {
final String details;
InvalidConfiguration(this.details);
@override
String toString() {
return 'Invalid configuration: $details';
}
}