Unverified Commit e7073f9a authored by Yegor's avatar Yegor Committed by GitHub

add --dart-defines option (#44083)

parent 9206bd02
......@@ -34,6 +34,7 @@ class AotBuilder {
Iterable<DarwinArch> iosBuildArchs = defaultIOSArchs,
List<String> extraFrontEndOptions,
List<String> extraGenSnapshotOptions,
@required List<String> dartDefines,
}) async {
if (platform == null) {
throwToolExit('No AOT build platform specified');
......@@ -78,6 +79,7 @@ class AotBuilder {
trackWidgetCreation: false,
outputPath: outputPath,
extraFrontEndOptions: extraFrontEndOptions,
dartDefines: dartDefines,
);
if (kernelOut == null) {
throwToolExit('Compiler terminated unexpectedly.');
......
......@@ -285,6 +285,7 @@ class AOTSnapshotter {
@required String packagesPath,
@required String outputPath,
@required bool trackWidgetCreation,
@required List<String> dartDefines,
List<String> extraFrontEndOptions = const <String>[],
}) async {
final FlutterProject flutterProject = FlutterProject.current();
......@@ -315,6 +316,7 @@ class AOTSnapshotter {
aot: true,
buildMode: buildMode,
trackWidgetCreation: trackWidgetCreation,
dartDefines: dartDefines,
));
// Write path to frontend_server, since things need to be re-generated when that changes.
......
......@@ -46,6 +46,7 @@ class DwdsWebRunnerFactory extends WebRunnerFactory {
@required FlutterProject flutterProject,
@required bool ipv6,
@required DebuggingOptions debuggingOptions,
@required List<String> dartDefines,
}) {
if (featureFlags.isWebIncrementalCompilerEnabled) {
return _ExperimentalResidentWebRunner(
......@@ -55,6 +56,7 @@ class DwdsWebRunnerFactory extends WebRunnerFactory {
debuggingOptions: debuggingOptions,
ipv6: ipv6,
stayResident: stayResident,
dartDefines: dartDefines,
);
}
return _DwdsResidentWebRunner(
......@@ -64,6 +66,7 @@ class DwdsWebRunnerFactory extends WebRunnerFactory {
debuggingOptions: debuggingOptions,
ipv6: ipv6,
stayResident: stayResident,
dartDefines: dartDefines,
);
}
}
......@@ -77,6 +80,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
@required bool ipv6,
@required DebuggingOptions debuggingOptions,
bool stayResident = true,
@required this.dartDefines,
}) : super(
<FlutterDevice>[],
target: target ?? fs.path.join('lib', 'main.dart'),
......@@ -87,6 +91,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
final FlutterDevice device;
final FlutterProject flutterProject;
final List<String> dartDefines;
DateTime firstBuildTime;
// Only the debug builds of the web support the service protocol.
......@@ -348,6 +353,7 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner {
@required bool ipv6,
@required DebuggingOptions debuggingOptions,
bool stayResident = true,
@required List<String> dartDefines,
}) : super(
device,
flutterProject: flutterProject,
......@@ -355,6 +361,7 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner {
debuggingOptions: debuggingOptions,
ipv6: ipv6,
stayResident: stayResident,
dartDefines: dartDefines,
);
@override
......@@ -540,6 +547,7 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
@required bool ipv6,
@required DebuggingOptions debuggingOptions,
bool stayResident = true,
@required List<String> dartDefines,
}) : super(
device,
flutterProject: flutterProject,
......@@ -547,6 +555,7 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
debuggingOptions: debuggingOptions,
ipv6: ipv6,
stayResident: stayResident,
dartDefines: dartDefines,
);
@override
......@@ -594,6 +603,7 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
hostname: debuggingOptions.hostname,
port: debuggingOptions.port,
skipDwds: device is WebServerDevice || !debuggingOptions.buildInfo.isDebug,
dartDefines: dartDefines,
);
// When connecting to a browser, update the message with a seemsSlow notification
// to handle the case where we fail to connect.
......
......@@ -81,6 +81,7 @@ typedef WebFsFactory = Future<WebFs> Function({
@required bool initializePlatform,
@required String hostname,
@required String port,
@required List<String> dartDefines,
});
/// The dev filesystem responsible for building and serving web applications.
......@@ -97,6 +98,7 @@ class WebFs {
this._target,
this._buildInfo,
this._initializePlatform,
this._dartDefines,
);
/// The server uri.
......@@ -111,6 +113,7 @@ class WebFs {
final String _target;
final BuildInfo _buildInfo;
final bool _initializePlatform;
final List<String> _dartDefines;
StreamSubscription<void> _connectedApps;
static const String _kHostName = 'localhost';
......@@ -146,7 +149,7 @@ class WebFs {
/// Recompile the web application and return whether this was successful.
Future<bool> recompile() async {
if (!_useBuildRunner) {
await buildWeb(_flutterProject, _target, _buildInfo, _initializePlatform);
await buildWeb(_flutterProject, _target, _buildInfo, _initializePlatform, _dartDefines);
return true;
}
_client.startBuild();
......@@ -173,6 +176,7 @@ class WebFs {
@required bool initializePlatform,
@required String hostname,
@required String port,
@required List<String> dartDefines,
}) async {
// workaround for https://github.com/flutter/flutter/issues/38290
if (!flutterProject.dartTool.existsSync()) {
......@@ -302,7 +306,7 @@ class WebFs {
handler = pipeline.addHandler(proxyHandler('http://localhost:$daemonAssetPort/web/'));
}
} else {
await buildWeb(flutterProject, target, buildInfo, initializePlatform);
await buildWeb(flutterProject, target, buildInfo, initializePlatform, dartDefines);
firstBuildCompleter.complete(true);
}
......@@ -325,6 +329,7 @@ class WebFs {
target,
buildInfo,
initializePlatform,
dartDefines,
);
if (!await firstBuildCompleter.future) {
throw const BuildException();
......
......@@ -7,6 +7,7 @@ import '../../base/build.dart';
import '../../base/file_system.dart';
import '../../build_info.dart';
import '../../compile.dart';
import '../../convert.dart';
import '../../globals.dart';
import '../../project.dart';
import '../build_system.dart';
......@@ -50,6 +51,9 @@ const String kFileSystemScheme = 'FileSystemScheme';
/// If provided, must be used along with [kFileSystemScheme].
const String kFileSystemRoots = 'FileSystemRoots';
/// Defines specified via the `--dart-define` command-line option.
const String kDartDefines = 'DartDefines';
/// The define to control what iOS architectures are built for.
///
/// This is expected to be a comma-separated list of architectures. If not
......@@ -208,6 +212,7 @@ class KernelSnapshot extends Target {
extraFrontEndOptions: extraFrontEndOptions,
fileSystemRoots: fileSystemRoots,
fileSystemScheme: fileSystemScheme,
dartDefines: parseDartDefines(environment),
);
if (output == null || output.errorCount != 0) {
throw Exception('Errors during snapshot creation: $output');
......@@ -357,3 +362,22 @@ class ReleaseCopyFlutterAotBundle extends CopyFlutterAotBundle {
AotElfRelease(),
];
}
/// Dart defines are encoded inside [Environment] as a JSON array.
List<String> parseDartDefines(Environment environment) {
if (!environment.defines.containsKey(kDartDefines)) {
return const <String>[];
}
final String dartDefinesJson = environment.defines[kDartDefines];
try {
final List<Object> parsedDefines = jsonDecode(dartDefinesJson);
return parsedDefines.cast<String>();
} on FormatException catch (_) {
throw Exception(
'The value of -D$kDartDefines is not formatted correctly.\n'
'The value must be a JSON-encoded list of strings but was:\n'
'$dartDefinesJson'
);
}
}
......@@ -130,6 +130,7 @@ class Dart2JSTarget extends Target {
? PackageMap.globalGeneratedPackagesPath
: PackageMap.globalPackagesPath;
final File outputFile = environment.buildDir.childFile('main.dart.js');
final ProcessResult result = await processManager.run(<String>[
artifacts.getArtifactPath(Artifact.engineDartBinary),
artifacts.getArtifactPath(Artifact.dart2jsSnapshot),
......@@ -147,6 +148,8 @@ class Dart2JSTarget extends Target {
'-Ddart.vm.profile=true'
else
'-Ddart.vm.product=true',
for (String dartDefine in parseDartDefines(environment))
'-D$dartDefine',
environment.buildDir.childFile('main.dart').path,
]);
if (result.exitCode != 0) {
......
......@@ -109,6 +109,7 @@ class CodeGeneratingKernelCompiler implements KernelCompiler {
TargetModel targetModel = TargetModel.flutter,
String initializeFromDill,
String platformDill,
List<String> dartDefines,
}) async {
if (fileSystemRoots != null || fileSystemScheme != null || depFilePath != null || targetModel != null || sdkRoot != null || packagesPath != null) {
printTrace('fileSystemRoots, fileSystemScheme, depFilePath, targetModel,'
......@@ -147,6 +148,7 @@ class CodeGeneratingKernelCompiler implements KernelCompiler {
depFilePath: depFilePath,
targetModel: targetModel,
initializeFromDill: initializeFromDill,
dartDefines: dartDefines,
);
}
}
......@@ -172,6 +174,7 @@ class CodeGeneratingResidentCompiler implements ResidentCompiler {
String initializeFromDill,
bool runCold = false,
TargetPlatform targetPlatform,
@required List<String> dartDefines,
}) async {
codeGenerator.updatePackages(flutterProject);
final ResidentCompiler residentCompiler = ResidentCompiler(
......@@ -191,6 +194,7 @@ class CodeGeneratingResidentCompiler implements ResidentCompiler {
targetModel: TargetModel.flutter,
unsafePackageSerialization: unsafePackageSerialization,
initializeFromDill: initializeFromDill,
dartDefines: dartDefines,
);
if (runCold) {
return residentCompiler;
......
......@@ -61,6 +61,7 @@ class AttachCommand extends FlutterCommand {
usesIpv6Flag();
usesFilesystemOptions(hide: !verboseHelp);
usesFuchsiaOptions(hide: !verboseHelp);
usesDartDefines();
argParser
..addOption(
'debug-port',
......@@ -274,6 +275,7 @@ class AttachCommand extends FlutterCommand {
target: argResults['target'],
targetModel: TargetModel(argResults['target-model']),
buildMode: getBuildMode(),
dartDefines: dartDefines,
);
flutterDevice.observatoryUris = <Uri>[ observatoryUri ];
final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice];
......
......@@ -18,6 +18,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
usesTargetOption();
addBuildModeFlags();
usesPubOption();
usesDartDefines();
argParser
..addOption('output-dir', defaultsTo: getAotBuildDirectory())
..addOption('target-platform',
......@@ -86,6 +87,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
iosBuildArchs: argResults['ios-arch'].map<DarwinArch>(getIOSArchForName),
extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
dartDefines: dartDefines,
);
return null;
}
......
......@@ -35,6 +35,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
usesTargetOption();
usesFlavorOption();
usesPubOption();
usesDartDefines();
argParser
..addFlag('debug',
negatable: true,
......@@ -294,6 +295,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
quiet: true,
reportTimings: false,
iosBuildArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64],
dartDefines: dartDefines,
);
const String appFrameworkName = 'App.framework';
......
......@@ -18,6 +18,7 @@ class BuildWebCommand extends BuildSubCommand {
usesTargetOption();
usesPubOption();
addBuildModeFlags(excludeDebug: true);
usesDartDefines();
argParser.addFlag('web-initialize-platform',
defaultsTo: true,
negatable: true,
......@@ -53,7 +54,13 @@ class BuildWebCommand extends BuildSubCommand {
if (buildInfo.isDebug) {
throwToolExit('debug builds cannot be built directly for the web. Try using "flutter run"');
}
await buildWeb(flutterProject, target, buildInfo, argResults['web-initialize-platform']);
await buildWeb(
flutterProject,
target,
buildInfo,
argResults['web-initialize-platform'],
dartDefines,
);
return null;
}
}
......@@ -424,6 +424,7 @@ class AppDomain extends Domain {
viewFilter: isolateFilter,
target: target,
buildMode: options.buildInfo.mode,
dartDefines: command?.dartDefines,
);
ResidentRunner runner;
......@@ -436,6 +437,7 @@ class AppDomain extends Domain {
debuggingOptions: options,
ipv6: ipv6,
stayResident: true,
dartDefines: command?.dartDefines,
);
} else if (enableHotReload) {
runner = HotRunner(
......
......@@ -31,6 +31,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
// Used by run and drive commands.
RunCommandBase({ bool verboseHelp = false }) {
addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp);
usesDartDefines();
usesFlavorOption();
argParser
..addFlag('trace-startup',
......@@ -426,6 +427,7 @@ class RunCommand extends RunCommandBase {
experimentalFlags: expFlags,
target: argResults['target'],
buildMode: getBuildMode(),
dartDefines: dartDefines,
),
];
// Only support "web mode" with a single web device due to resident runner
......@@ -459,6 +461,7 @@ class RunCommand extends RunCommandBase {
ipv6: ipv6,
debuggingOptions: _createDebuggingOptions(),
stayResident: stayResident,
dartDefines: dartDefines,
);
} else {
runner = ColdRunner(
......
......@@ -288,6 +288,7 @@ class KernelCompiler {
String fileSystemScheme,
String initializeFromDill,
String platformDill,
@required List<String> dartDefines,
}) async {
final String frontendServer = artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk
......@@ -316,6 +317,8 @@ class KernelCompiler {
sdkRoot,
'--target=$targetModel',
'-Ddart.developer.causal_async_stacks=$causalAsyncStacks',
for (Object dartDefine in dartDefines)
'-D$dartDefine',
..._buildModeOptions(buildMode),
if (trackWidgetCreation) '--track-widget-creation',
if (!linkPlatformKernelIn) '--no-link-platform',
......@@ -464,6 +467,7 @@ abstract class ResidentCompiler {
bool unsafePackageSerialization,
List<String> experimentalFlags,
String platformDill,
List<String> dartDefines,
}) = DefaultResidentCompiler;
......@@ -524,8 +528,10 @@ class DefaultResidentCompiler implements ResidentCompiler {
this.unsafePackageSerialization,
this.experimentalFlags,
this.platformDill,
List<String> dartDefines,
}) : assert(sdkRoot != null),
_stdoutHandler = StdoutHandler(consumer: compilerMessageConsumer),
dartDefines = dartDefines ?? const <String>[],
// This is a URI, not a file path, so the forward slash is correct even on Windows.
sdkRoot = sdkRoot.endsWith('/') ? sdkRoot : '$sdkRoot/';
......@@ -539,6 +545,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
final String initializeFromDill;
final bool unsafePackageSerialization;
final List<String> experimentalFlags;
final List<String> dartDefines;
/// The path to the root of the Dart SDK used to compile.
///
......@@ -594,9 +601,9 @@ class DefaultResidentCompiler implements ResidentCompiler {
if (_server == null) {
return _compile(
_mapFilename(request.mainPath, packageUriMapper),
request.outputPath,
_mapFilename(request.packagesFilePath ?? packagesPath, /* packageUriMapper= */ null),
_mapFilename(request.mainPath, packageUriMapper),
request.outputPath,
_mapFilename(request.packagesFilePath ?? packagesPath, /* packageUriMapper= */ null),
);
}
......@@ -648,6 +655,8 @@ class DefaultResidentCompiler implements ResidentCompiler {
'--incremental',
'--target=$targetModel',
'-Ddart.developer.causal_async_stacks=$causalAsyncStacks',
for (Object dartDefine in dartDefines)
'-D$dartDefine',
if (outputPath != null) ...<String>[
'--output-dill',
outputPath,
......
......@@ -41,6 +41,7 @@ class FlutterDevice {
List<String> experimentalFlags,
ResidentCompiler generator,
@required BuildMode buildMode,
List<String> dartDefines,
}) : assert(trackWidgetCreation != null),
generator = generator ?? ResidentCompiler(
artifacts.getArtifactPath(
......@@ -54,6 +55,7 @@ class FlutterDevice {
fileSystemScheme: fileSystemScheme,
targetModel: targetModel,
experimentalFlags: experimentalFlags,
dartDefines: dartDefines,
);
/// Create a [FlutterDevice] with optional code generation enabled.
......@@ -69,6 +71,7 @@ class FlutterDevice {
TargetModel targetModel = TargetModel.flutter,
List<String> experimentalFlags,
ResidentCompiler generator,
List<String> dartDefines,
}) async {
ResidentCompiler generator;
final TargetPlatform targetPlatform = await device.targetPlatform;
......@@ -86,12 +89,14 @@ class FlutterDevice {
targetModel: TargetModel.dartdevc,
experimentalFlags: experimentalFlags,
platformDill: artifacts.getArtifactPath(Artifact.webPlatformKernelDill, mode: buildMode),
dartDefines: dartDefines,
);
} else if (flutterProject.hasBuilders) {
generator = await CodeGeneratingResidentCompiler.create(
targetPlatform: targetPlatform,
buildMode: buildMode,
flutterProject: flutterProject,
dartDefines: dartDefines,
);
} else {
generator = ResidentCompiler(
......@@ -106,6 +111,7 @@ class FlutterDevice {
fileSystemScheme: fileSystemScheme,
targetModel: targetModel,
experimentalFlags: experimentalFlags,
dartDefines: dartDefines,
);
}
return FlutterDevice(
......@@ -119,6 +125,7 @@ class FlutterDevice {
targetPlatform: targetPlatform,
generator: generator,
buildMode: buildMode,
dartDefines: dartDefines,
);
}
......
......@@ -269,6 +269,20 @@ abstract class FlutterCommand extends Command<void> {
valueHelp: 'x.y.z');
}
void usesDartDefines() {
argParser.addMultiOption(
'dart-define',
help: 'Passed to the Dart compiler building this application as a -D flag.\n'
'Values supported by this option are compiler implementation specific.\n'
'Multiple defines can be passed by repeating --dart-define multiple times.',
valueHelp: 'FOO=bar',
hide: true,
);
}
/// The values passed via the `--dart-define` option.
List<String> get dartDefines => argResults['dart-define'];
void usesIsolateFilterOption({ @required bool hide }) {
argParser.addOption('isolate-filter',
defaultsTo: null,
......
......@@ -103,6 +103,7 @@ class TestCompiler {
// We already ran codegen once at the start, we only need to
// configure builders.
runCold: true,
dartDefines: const <String>[],
);
}
return ResidentCompiler(
......@@ -113,6 +114,7 @@ class TestCompiler {
compilerMessageConsumer: _reportCompilerMessage,
initializeFromDill: testFilePath,
unsafePackageSerialization: false,
dartDefines: const <String>[],
);
}
......
......@@ -12,6 +12,7 @@ import '../build_info.dart';
import '../build_system/build_system.dart';
import '../build_system/targets/dart.dart';
import '../build_system/targets/web.dart';
import '../convert.dart';
import '../globals.dart';
import '../platform_plugins.dart';
import '../plugins.dart';
......@@ -21,7 +22,13 @@ import '../reporting/reporting.dart';
/// The [WebCompilationProxy] instance.
WebCompilationProxy get webCompilationProxy => context.get<WebCompilationProxy>();
Future<void> buildWeb(FlutterProject flutterProject, String target, BuildInfo buildInfo, bool initializePlatform) async {
Future<void> buildWeb(
FlutterProject flutterProject,
String target,
BuildInfo buildInfo,
bool initializePlatform,
List<String> dartDefines,
) async {
if (!flutterProject.web.existsSync()) {
throwToolExit('Missing index.html.');
}
......@@ -42,6 +49,7 @@ Future<void> buildWeb(FlutterProject flutterProject, String target, BuildInfo bu
kTargetFile: target,
kInitializePlatform: initializePlatform.toString(),
kHasWebPlugins: hasWebPlugins.toString(),
kDartDefines: jsonEncode(dartDefines),
},
));
if (!result.success) {
......
......@@ -23,5 +23,6 @@ abstract class WebRunnerFactory {
@required FlutterProject flutterProject,
@required bool ipv6,
@required DebuggingOptions debuggingOptions,
@required List<String> dartDefines,
});
}
......@@ -57,6 +57,7 @@ void main() {
fs.path.join('lib', 'main.dart'),
BuildInfo.debug,
false,
const <String>[],
), throwsA(isInstanceOf<ToolExit>()));
}));
......@@ -69,6 +70,7 @@ void main() {
ipv6: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
stayResident: true,
dartDefines: const <String>[],
);
expect(await runner.run(), 1);
}));
......
......@@ -2,20 +2,30 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/run.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/version.dart';
import 'package:flutter_tools/src/web/web_runner.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
import '../../src/testbed.dart';
void main() {
group('run', () {
......@@ -178,6 +188,63 @@ void main() {
}, overrides: <Type, Generator>{
DeviceManager: () => mockDeviceManager,
});
group('--dart-define option', () {
MemoryFileSystem fs;
MockProcessManager mockProcessManager;
MockWebRunnerFactory mockWebRunnerFactory;
setUpAll(() {
when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
return Stream<Device>.fromIterable(<Device>[
FakeDevice().._targetPlatform = TargetPlatform.web_javascript,
]);
});
});
RunCommand command;
List<String> args;
setUp(() {
command = TestRunCommand();
args = <String> [
'run',
'--dart-define=FOO=bar',
'--no-hot',
'--no-pub',
];
applyMocksToCommand(command);
fs = MemoryFileSystem();
mockProcessManager = MockProcessManager();
mockWebRunnerFactory = MockWebRunnerFactory();
});
testUsingContext('populates the environment', () async {
final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_run_test.');
fs.currentDirectory = tempDir;
final Directory libDir = tempDir.childDirectory('lib');
libDir.createSync();
final File mainFile = libDir.childFile('main.dart');
mainFile.writeAsStringSync('void main() {}');
final Directory webDir = tempDir.childDirectory('web');
webDir.createSync();
final File indexFile = libDir.childFile('index.html');
indexFile.writeAsStringSync('<h1>Hello</h1>');
await createTestCommandRunner(command).run(args);
expect(mockWebRunnerFactory._dartDefines, <String>['FOO=bar']);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(
isWebEnabled: true,
),
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
DeviceManager: () => mockDeviceManager,
FlutterVersion: () => mockStableFlutterVersion,
WebRunnerFactory: () => mockWebRunnerFactory,
});
});
});
}
......@@ -207,7 +274,7 @@ class MockStableFlutterVersion extends MockFlutterVersion {
class FakeDevice extends Fake implements Device {
static const int kSuccess = 1;
static const int kFailure = -1;
final TargetPlatform _targetPlatform = TargetPlatform.ios;
TargetPlatform _targetPlatform = TargetPlatform.ios;
void _throwToolExit(int code) => throwToolExit(null, exitCode: code);
......@@ -263,3 +330,32 @@ class FakeDevice extends Fake implements Device {
return null;
}
}
class MockWebRunnerFactory extends Mock implements WebRunnerFactory {
List<String> _dartDefines;
@override
ResidentRunner createWebRunner(
FlutterDevice device, {
String target,
bool stayResident,
FlutterProject flutterProject,
bool ipv6,
DebuggingOptions debuggingOptions,
List<String> dartDefines,
}) {
_dartDefines = dartDefines;
return MockWebRunner();
}
}
class MockWebRunner extends Mock implements ResidentRunner {
@override
Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
String route,
}) async {
return 0;
}
}
......@@ -136,6 +136,7 @@ flutter_tools:lib/''');
fileSystemScheme: anyNamed('fileSystemScheme'),
platformDill: anyNamed('platformDill'),
initializeFromDill: anyNamed('initializeFromDill'),
dartDefines: anyNamed('dartDefines'),
)).thenAnswer((Invocation invocation) async {
return null;
});
......@@ -163,6 +164,7 @@ flutter_tools:lib/''');
fileSystemRoots: anyNamed('fileSystemRoots'),
fileSystemScheme: anyNamed('fileSystemScheme'),
linkPlatformKernelIn: anyNamed('linkPlatformKernelIn'),
dartDefines: anyNamed('dartDefines'),
)).thenAnswer((Invocation _) async {
return const CompilerOutput('example', 0, <Uri>[]);
});
......@@ -191,6 +193,7 @@ flutter_tools:lib/''');
fileSystemRoots: anyNamed('fileSystemRoots'),
fileSystemScheme: anyNamed('fileSystemScheme'),
linkPlatformKernelIn: false,
dartDefines: anyNamed('dartDefines'),
)).thenAnswer((Invocation _) async {
return const CompilerOutput('example', 0, <Uri>[]);
});
......@@ -221,6 +224,7 @@ flutter_tools:lib/''');
fileSystemRoots: anyNamed('fileSystemRoots'),
fileSystemScheme: anyNamed('fileSystemScheme'),
linkPlatformKernelIn: false,
dartDefines: anyNamed('dartDefines'),
)).thenAnswer((Invocation _) async {
return const CompilerOutput('example', 0, <Uri>[]);
});
......@@ -431,6 +435,7 @@ class FakeKernelCompiler implements KernelCompiler {
String fileSystemScheme,
String platformDill,
String initializeFromDill,
List<String> dartDefines,
}) async {
fs.file(outputFilePath).createSync(recursive: true);
return CompilerOutput(outputFilePath, 0, null);
......
......@@ -236,6 +236,80 @@ void main() {
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
}));
test('Dart2JSTarget calls dart2js with Dart defines in release mode', () => testbed.run(() async {
environment.defines[kBuildMode] = 'release';
environment.defines[kDartDefines] = '["FOO=bar","BAZ=qux"]';
when(processManager.run(any)).thenAnswer((Invocation invocation) async {
return FakeProcessResult(exitCode: 0);
});
await const Dart2JSTarget().build(environment);
final List<String> expected = <String>[
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
'--libraries-spec=' + fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'),
'-O4',
'-o',
environment.buildDir.childFile('main.dart.js').absolute.path,
'--packages=.packages',
'-Ddart.vm.product=true',
'-DFOO=bar',
'-DBAZ=qux',
environment.buildDir.childFile('main.dart').absolute.path,
];
verify(processManager.run(expected)).called(1);
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
}));
test('Dart2JSTarget calls dart2js with Dart defines in profile mode', () => testbed.run(() async {
environment.defines[kBuildMode] = 'profile';
environment.defines[kDartDefines] = '["FOO=bar","BAZ=qux"]';
when(processManager.run(any)).thenAnswer((Invocation invocation) async {
return FakeProcessResult(exitCode: 0);
});
await const Dart2JSTarget().build(environment);
final List<String> expected = <String>[
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
'--libraries-spec=' + fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'),
'-O4',
'--no-minify',
'-o',
environment.buildDir.childFile('main.dart.js').absolute.path,
'--packages=.packages',
'-Ddart.vm.profile=true',
'-DFOO=bar',
'-DBAZ=qux',
environment.buildDir.childFile('main.dart').absolute.path,
];
verify(processManager.run(expected)).called(1);
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
}));
test('Dart2JSTarget throws developer-friendly exception on misformatted DartDefines', () => testbed.run(() async {
environment.defines[kBuildMode] = 'profile';
environment.defines[kDartDefines] = '[misformatted json';
try {
await const Dart2JSTarget().build(environment);
fail('Call to build() must not have succeeded.');
} on Exception catch(exception) {
expect(
'$exception',
'Exception: The value of -D$kDartDefines is not formatted correctly.\n'
'The value must be a JSON-encoded list of strings but was:\n'
'[misformatted json',
);
}
// Should not attempt to run any processes.
verifyNever(processManager.run(any));
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
}));
}
class MockProcessManager extends Mock implements ProcessManager {}
......
......@@ -25,6 +25,8 @@ void main() {
MockStdIn mockFrontendServerStdIn;
MockStream mockFrontendServerStdErr;
List<String> latestCommand;
setUp(() {
mockProcessManager = MockProcessManager();
mockFrontendServer = MockProcess();
......@@ -38,7 +40,10 @@ void main() {
when(mockFrontendServer.stdin).thenReturn(mockFrontendServerStdIn);
when(mockProcessManager.canRun(any)).thenReturn(true);
when(mockProcessManager.start(any)).thenAnswer(
(Invocation invocation) => Future<Process>.value(mockFrontendServer));
(Invocation invocation) {
latestCommand = invocation.positionalArguments.first;
return Future<Process>.value(mockFrontendServer);
});
when(mockFrontendServer.exitCode).thenAnswer((_) async => 0);
});
......@@ -55,6 +60,7 @@ void main() {
mainPath: '/path/to/main.dart',
buildMode: BuildMode.debug,
trackWidgetCreation: false,
dartDefines: const <String>[],
);
expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
......@@ -79,6 +85,7 @@ void main() {
buildMode: BuildMode.release,
trackWidgetCreation: false,
aot: true,
dartDefines: const <String>[],
);
expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
......@@ -103,6 +110,7 @@ void main() {
mainPath: '/path/to/main.dart',
buildMode: BuildMode.debug,
trackWidgetCreation: false,
dartDefines: const <String>[],
);
expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
......@@ -130,6 +138,7 @@ void main() {
mainPath: '/path/to/main.dart',
buildMode: BuildMode.debug,
trackWidgetCreation: false,
dartDefines: const <String>[],
);
expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
expect(bufferLogger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
......@@ -139,6 +148,27 @@ void main() {
OutputPreferences: () => OutputPreferences(showColor: false),
Platform: kNoColorTerminalPlatform,
});
testUsingContext('passes dartDefines to the kernel compiler', () async {
// Use unsuccessful result because it's easier to setup in test. We only care about arguments passed to the compiler.
when(mockFrontendServer.exitCode).thenAnswer((_) async => 255);
when(mockFrontendServer.stdout).thenAnswer((Invocation invocation) => Stream<List<int>>.fromFuture(
Future<List<int>>.value(<int>[])
));
final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(null);
await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot',
mainPath: '/path/to/main.dart',
buildMode: BuildMode.debug,
trackWidgetCreation: false,
dartDefines: const <String>['FOO=bar', 'BAZ=qux'],
);
expect(latestCommand, containsAllInOrder(<String>['-DFOO=bar', '-DBAZ=qux']));
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
OutputPreferences: () => OutputPreferences(showColor: false),
Platform: kNoColorTerminalPlatform,
});
}
class MockProcess extends Mock implements Process {}
......
......@@ -41,6 +41,7 @@ void main() {
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
);
},
overrides: <Type, Generator>{
......@@ -52,6 +53,7 @@ void main() {
@required bool initializePlatform,
@required String hostname,
@required String port,
@required List<String> dartDefines,
}) async {
return mockWebFs;
},
......
......@@ -73,6 +73,7 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
);
},
overrides: <Type, Generator>{
......@@ -84,6 +85,7 @@ void main() {
@required bool initializePlatform,
@required String hostname,
@required String port,
@required List<String> dartDefines,
}) async {
return mockWebFs;
},
......@@ -133,6 +135,7 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
);
expect(profileResidentWebRunner.debuggingEnabled, false);
......@@ -150,6 +153,7 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
);
expect(profileResidentWebRunner.supportsServiceProtocol, false);
......@@ -204,6 +208,7 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: false,
dartDefines: const <String>[],
);
expect(await residentWebRunner.run(), 0);
......@@ -240,6 +245,7 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
......
......@@ -126,6 +126,7 @@ void main() {
initializePlatform: true,
hostname: null,
port: null,
dartDefines: const <String>[],
);
// Since the .packages file is missing in the memory filesystem, this should
// be called.
......@@ -155,6 +156,7 @@ void main() {
initializePlatform: false,
hostname: null,
port: null,
dartDefines: const <String>[],
);
// The build daemon is told to build once.
......@@ -175,6 +177,7 @@ void main() {
initializePlatform: false,
hostname: 'foo',
port: '1234',
dartDefines: const <String>[],
);
expect(webFs.uri, contains('foo:1234'));
......@@ -207,6 +210,7 @@ void main() {
initializePlatform: false,
hostname: 'foo',
port: '1234',
dartDefines: const <String>[],
), throwsA(isInstanceOf<Exception>()));
}));
}
......
......@@ -486,29 +486,50 @@ class FlutterRunTestDriver extends FlutterTestDriver {
pidFile: pidFile,
);
// Stash the PID so that we can terminate the VM more reliably than using
// _process.kill() (`flutter` is a shell script so _process itself is a
// shell, not the flutter tool's Dart process).
final Map<String, dynamic> connected = await _waitFor(event: 'daemon.connected');
_processPid = connected['params']['pid'];
final Completer<void> prematureExitGuard = Completer<void>();
// If the process exits before all of the `await`s below are done, then it
// exited prematurely. This causes the currently suspended `await` to
// deadlock until the test times out. Instead, this causes the test to fail
// fast.
unawaited(_process.exitCode.then((_) {
if (!prematureExitGuard.isCompleted) {
prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}');
}
}));
// Set this up now, but we don't wait it yet. We want to make sure we don't
// miss it while waiting for debugPort below.
final Future<Map<String, dynamic>> started = _waitFor(event: 'app.started', timeout: appStartTimeout);
unawaited(() async {
try {
// Stash the PID so that we can terminate the VM more reliably than using
// _process.kill() (`flutter` is a shell script so _process itself is a
// shell, not the flutter tool's Dart process).
final Map<String, dynamic> connected = await _waitFor(event: 'daemon.connected');
_processPid = connected['params']['pid'];
// Set this up now, but we don't wait it yet. We want to make sure we don't
// miss it while waiting for debugPort below.
final Future<Map<String, dynamic>> started = _waitFor(event: 'app.started', timeout: appStartTimeout);
if (withDebugger) {
final Map<String, dynamic> debugPort = await _waitFor(event: 'app.debugPort', timeout: appStartTimeout);
final String wsUriString = debugPort['params']['wsUri'];
_vmServiceWsUri = Uri.parse(wsUriString);
await connectToVmService(pauseOnExceptions: pauseOnExceptions);
if (!startPaused) {
await resume(waitForNextPause: false);
}
}
if (withDebugger) {
final Map<String, dynamic> debugPort = await _waitFor(event: 'app.debugPort', timeout: appStartTimeout);
final String wsUriString = debugPort['params']['wsUri'];
_vmServiceWsUri = Uri.parse(wsUriString);
await connectToVmService(pauseOnExceptions: pauseOnExceptions);
if (!startPaused) {
await resume(waitForNextPause: false);
// Now await the started event; if it had already happened the future will
// have already completed.
_currentRunningAppId = (await started)['params']['appId'];
prematureExitGuard.complete();
} catch(error, stackTrace) {
prematureExitGuard.completeError(error, stackTrace);
}
}
}());
// Now await the started event; if it had already happened the future will
// have already completed.
_currentRunningAppId = (await started)['params']['appId'];
return prematureExitGuard.future;
}
Future<void> hotRestart({ bool pause = false }) => _restart(fullRestart: true, pause: pause);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment