mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Fixes https://github.com/flutter/flutter/issues/130277 This PR does two things: 1. introduce a hidden `flutter build _preview` command, that will build a debug windows desktop app and copy it into the SDK's binary cache. This command is only intended to be run during packaging. 2. introduce a new device type, called `PreviewDevice`, which relies on the prebuilt desktop debug app from step 1, copies it into the target app's assets build folder, and then hot reloads their dart code into it.
433 lines
16 KiB
Dart
433 lines
16 KiB
Dart
// Copyright 2014 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.
|
|
|
|
import 'package:args/command_runner.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/artifacts.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/base/process.dart';
|
|
import 'package:flutter_tools/src/build_info.dart';
|
|
import 'package:flutter_tools/src/build_system/build_system.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/commands/build.dart';
|
|
import 'package:flutter_tools/src/commands/build_web.dart';
|
|
import 'package:flutter_tools/src/features.dart';
|
|
import 'package:flutter_tools/src/runner/flutter_command.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/fakes.dart';
|
|
import '../../src/test_build_system.dart';
|
|
import '../../src/test_flutter_command_runner.dart';
|
|
|
|
void main() {
|
|
late FileSystem fileSystem;
|
|
final Platform fakePlatform = FakePlatform(
|
|
environment: <String, String>{
|
|
'FLUTTER_ROOT': '/',
|
|
},
|
|
);
|
|
late ProcessUtils processUtils;
|
|
late BufferLogger logger;
|
|
late ProcessManager processManager;
|
|
late Artifacts artifacts;
|
|
|
|
setUpAll(() {
|
|
Cache.flutterRoot = '';
|
|
Cache.disableLocking();
|
|
});
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem.test();
|
|
fileSystem.file('pubspec.yaml')
|
|
..createSync()
|
|
..writeAsStringSync('name: foo\n');
|
|
fileSystem.file('.packages').createSync();
|
|
fileSystem.file(fileSystem.path.join('web', 'index.html')).createSync(recursive: true);
|
|
fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
|
|
artifacts = Artifacts.test(fileSystem: fileSystem);
|
|
logger = BufferLogger.test();
|
|
processManager = FakeProcessManager.empty();
|
|
processUtils = ProcessUtils(
|
|
logger: logger,
|
|
processManager: processManager,
|
|
);
|
|
});
|
|
|
|
testUsingContext('Refuses to build for web when missing index.html', () async {
|
|
fileSystem.file(fileSystem.path.join('web', 'index.html')).deleteSync();
|
|
final CommandRunner<void> runner = createTestCommandRunner(BuildCommand(
|
|
artifacts: artifacts,
|
|
androidSdk: FakeAndroidSdk(),
|
|
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processUtils: processUtils,
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
));
|
|
|
|
expect(
|
|
() => runner.run(<String>['build', 'web', '--no-pub']),
|
|
throwsToolExit(message: 'Missing index.html.')
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('Refuses to build a debug build for web', () async {
|
|
final CommandRunner<void> runner = createTestCommandRunner(BuildCommand(
|
|
artifacts: artifacts,
|
|
androidSdk: FakeAndroidSdk(),
|
|
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processUtils: processUtils,
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
));
|
|
|
|
expect(() => runner.run(<String>['build', 'web', '--debug', '--no-pub']),
|
|
throwsA(isA<UsageException>()));
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('Refuses to build for web when feature is disabled', () async {
|
|
final CommandRunner<void> runner = createTestCommandRunner(BuildCommand(
|
|
artifacts: artifacts,
|
|
androidSdk: FakeAndroidSdk(),
|
|
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
|
|
fileSystem: MemoryFileSystem.test(),
|
|
logger: logger,
|
|
processUtils: processUtils,
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
));
|
|
|
|
expect(
|
|
() => runner.run(<String>['build', 'web', '--no-pub']),
|
|
throwsToolExit(message: '"build web" is not currently supported. To enable, run "flutter config --enable-web".')
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('Setup for a web build with default output directory', () async {
|
|
final BuildCommand buildCommand = BuildCommand(
|
|
artifacts: artifacts,
|
|
androidSdk: FakeAndroidSdk(),
|
|
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processUtils: processUtils,
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
);
|
|
final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
|
|
setupFileSystemForEndToEndTest(fileSystem);
|
|
await runner.run(<String>['build', 'web', '--no-pub', '--no-web-resources-cdn', '--dart-define=foo=a', '--dart2js-optimization=O3']);
|
|
|
|
final Directory buildDir = fileSystem.directory(fileSystem.path.join('build', 'web'));
|
|
|
|
expect(buildDir.existsSync(), true);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) {
|
|
expect(environment.defines, <String, String>{
|
|
'TargetFile': 'lib/main.dart',
|
|
'HasWebPlugins': 'true',
|
|
'cspMode': 'false',
|
|
'SourceMaps': 'false',
|
|
'NativeNullAssertions': 'true',
|
|
'ServiceWorkerStrategy': 'offline-first',
|
|
'Dart2jsDumpInfo': 'false',
|
|
'Dart2jsNoFrequencyBasedMinification': 'false',
|
|
'Dart2jsOptimization': 'O3',
|
|
'BuildMode': 'release',
|
|
'DartDefines': 'Zm9vPWE=,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==',
|
|
'DartObfuscation': 'false',
|
|
'TrackWidgetCreation': 'false',
|
|
'TreeShakeIcons': 'true',
|
|
});
|
|
}),
|
|
});
|
|
|
|
testUsingContext('Does not allow -O0 optimization level', () async {
|
|
final BuildCommand buildCommand = BuildCommand(
|
|
artifacts: artifacts,
|
|
androidSdk: FakeAndroidSdk(),
|
|
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
processUtils: processUtils,
|
|
);
|
|
final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
|
|
setupFileSystemForEndToEndTest(fileSystem);
|
|
await expectLater(
|
|
() => runner.run(<String>[
|
|
'build',
|
|
'web',
|
|
'--no-pub', '--no-web-resources-cdn', '--dart-define=foo=a', '--dart2js-optimization=O0']),
|
|
throwsUsageException(message: '"O0" is not an allowed value for option "dart2js-optimization"'),
|
|
);
|
|
|
|
final Directory buildDir = fileSystem.directory(fileSystem.path.join('build', 'web'));
|
|
|
|
expect(buildDir.existsSync(), isFalse);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) {
|
|
expect(environment.defines, <String, String>{
|
|
'TargetFile': 'lib/main.dart',
|
|
'HasWebPlugins': 'true',
|
|
'cspMode': 'false',
|
|
'SourceMaps': 'false',
|
|
'NativeNullAssertions': 'true',
|
|
'ServiceWorkerStrategy': 'offline-first',
|
|
'Dart2jsDumpInfo': 'false',
|
|
'Dart2jsNoFrequencyBasedMinification': 'false',
|
|
'Dart2jsOptimization': 'O3',
|
|
'BuildMode': 'release',
|
|
'DartDefines': 'Zm9vPWE=,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==',
|
|
'DartObfuscation': 'false',
|
|
'TrackWidgetCreation': 'false',
|
|
'TreeShakeIcons': 'true',
|
|
});
|
|
}),
|
|
});
|
|
|
|
testUsingContext('Setup for a web build with a user specified output directory',
|
|
() async {
|
|
final BuildCommand buildCommand = BuildCommand(
|
|
artifacts: artifacts,
|
|
androidSdk: FakeAndroidSdk(),
|
|
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processUtils: processUtils,
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
);
|
|
final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
|
|
|
|
setupFileSystemForEndToEndTest(fileSystem);
|
|
|
|
const String newBuildDir = 'new_dir';
|
|
final Directory buildDir = fileSystem.directory(fileSystem.path.join(newBuildDir));
|
|
|
|
expect(buildDir.existsSync(), false);
|
|
|
|
await runner.run(<String>[
|
|
'build',
|
|
'web',
|
|
'--no-pub',
|
|
'--no-web-resources-cdn',
|
|
'--output=$newBuildDir'
|
|
]);
|
|
|
|
expect(buildDir.existsSync(), true);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) {
|
|
expect(environment.defines, <String, String>{
|
|
'TargetFile': 'lib/main.dart',
|
|
'HasWebPlugins': 'true',
|
|
'cspMode': 'false',
|
|
'SourceMaps': 'false',
|
|
'NativeNullAssertions': 'true',
|
|
'ServiceWorkerStrategy': 'offline-first',
|
|
'Dart2jsDumpInfo': 'false',
|
|
'Dart2jsNoFrequencyBasedMinification': 'false',
|
|
'Dart2jsOptimization': 'O4',
|
|
'BuildMode': 'release',
|
|
'DartDefines': 'RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==',
|
|
'DartObfuscation': 'false',
|
|
'TrackWidgetCreation': 'false',
|
|
'TreeShakeIcons': 'true',
|
|
});
|
|
}),
|
|
});
|
|
|
|
testUsingContext('hidden if feature flag is not enabled', () async {
|
|
expect(BuildWebCommand(fileSystem: fileSystem, logger: BufferLogger.test(), verboseHelp: false).hidden, true);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('not hidden if feature flag is enabled', () async {
|
|
expect(BuildWebCommand(fileSystem: fileSystem, logger: BufferLogger.test(), verboseHelp: false).hidden, false);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('Defaults to web renderer auto mode when no option is specified', () async {
|
|
final TestWebBuildCommand buildCommand = TestWebBuildCommand(fileSystem: fileSystem);
|
|
final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
|
|
setupFileSystemForEndToEndTest(fileSystem);
|
|
await runner.run(<String>['build', 'web', '--no-pub']);
|
|
final BuildInfo buildInfo =
|
|
await buildCommand.webCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
|
|
expect(buildInfo.dartDefines, contains('FLUTTER_WEB_AUTO_DETECT=true'));
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
|
|
});
|
|
|
|
testUsingContext('Web build supports build-name and build-number', () async {
|
|
final TestWebBuildCommand buildCommand = TestWebBuildCommand(fileSystem: fileSystem);
|
|
final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
|
|
setupFileSystemForEndToEndTest(fileSystem);
|
|
|
|
await runner.run(<String>[
|
|
'build',
|
|
'web',
|
|
'--no-pub',
|
|
'--build-name=1.2.3',
|
|
'--build-number=42',
|
|
]);
|
|
|
|
final BuildInfo buildInfo = await buildCommand.webCommand
|
|
.getBuildInfo(forcedBuildMode: BuildMode.debug);
|
|
expect(buildInfo.buildNumber, '42');
|
|
expect(buildInfo.buildName, '1.2.3');
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
|
|
});
|
|
|
|
testUsingContext('Defaults to gstatic CanvasKit artifacts', () async {
|
|
final TestWebBuildCommand buildCommand = TestWebBuildCommand(fileSystem: fileSystem);
|
|
final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
|
|
setupFileSystemForEndToEndTest(fileSystem);
|
|
await runner.run(<String>['build', 'web', '--no-pub', '--web-resources-cdn']);
|
|
final BuildInfo buildInfo =
|
|
await buildCommand.webCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
|
|
expect(buildInfo.dartDefines, contains(startsWith('FLUTTER_WEB_CANVASKIT_URL=https://www.gstatic.com/flutter-canvaskit/')));
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
|
|
});
|
|
|
|
testUsingContext('Does not override custom CanvasKit URL', () async {
|
|
final TestWebBuildCommand buildCommand = TestWebBuildCommand(fileSystem: fileSystem);
|
|
final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
|
|
setupFileSystemForEndToEndTest(fileSystem);
|
|
await runner.run(<String>['build', 'web', '--no-pub', '--web-resources-cdn', '--dart-define=FLUTTER_WEB_CANVASKIT_URL=abcdefg']);
|
|
final BuildInfo buildInfo =
|
|
await buildCommand.webCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
|
|
expect(buildInfo.dartDefines, contains('FLUTTER_WEB_CANVASKIT_URL=abcdefg'));
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => fakePlatform,
|
|
FileSystem: () => fileSystem,
|
|
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
|
ProcessManager: () => processManager,
|
|
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
|
|
});
|
|
}
|
|
|
|
void setupFileSystemForEndToEndTest(FileSystem fileSystem) {
|
|
final List<String> dependencies = <String>[
|
|
fileSystem.path.join('packages', 'flutter_tools', 'lib', 'src', 'build_system', 'targets', 'web.dart'),
|
|
fileSystem.path.join('bin', 'cache', 'flutter_web_sdk'),
|
|
fileSystem.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
|
|
fileSystem.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
|
|
fileSystem.path.join('bin', 'cache', 'dart-sdk '),
|
|
];
|
|
for (final String dependency in dependencies) {
|
|
fileSystem.file(dependency).createSync(recursive: true);
|
|
}
|
|
|
|
// Project files.
|
|
fileSystem.file('.packages')
|
|
.writeAsStringSync('''
|
|
foo:lib/
|
|
fizz:bar/lib/
|
|
''');
|
|
fileSystem.file('pubspec.yaml')
|
|
.writeAsStringSync('''
|
|
name: foo
|
|
|
|
dependencies:
|
|
flutter:
|
|
sdk: flutter
|
|
fizz:
|
|
path:
|
|
bar/
|
|
''');
|
|
fileSystem.file(fileSystem.path.join('bar', 'pubspec.yaml'))
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('''
|
|
name: bar
|
|
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
web:
|
|
pluginClass: UrlLauncherPlugin
|
|
fileName: url_launcher_web.dart
|
|
''');
|
|
fileSystem.file(fileSystem.path.join('bar', 'lib', 'url_launcher_web.dart'))
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('''
|
|
class UrlLauncherPlugin {}
|
|
''');
|
|
fileSystem.file(fileSystem.path.join('lib', 'main.dart'))
|
|
.writeAsStringSync('void main() { }');
|
|
}
|
|
|
|
class TestWebBuildCommand extends FlutterCommand {
|
|
TestWebBuildCommand({ required FileSystem fileSystem, bool verboseHelp = false }) :
|
|
webCommand = BuildWebCommand(
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
verboseHelp: verboseHelp) {
|
|
addSubcommand(webCommand);
|
|
}
|
|
|
|
final BuildWebCommand webCommand;
|
|
|
|
@override
|
|
final String name = 'build';
|
|
|
|
@override
|
|
final String description = 'Build a test executable app.';
|
|
|
|
@override
|
|
Future<FlutterCommandResult> runCommand() async => FlutterCommandResult.fail();
|
|
|
|
@override
|
|
bool get shouldRunPub => false;
|
|
}
|