Skip to content

Instantly share code, notes, and snippets.

@soloxiao
Created May 27, 2022 03:05
Show Gist options
  • Save soloxiao/b67750c1447689796f6bad4b14bbb313 to your computer and use it in GitHub Desktop.
Save soloxiao/b67750c1447689796f6bad4b14bbb313 to your computer and use it in GitHub Desktop.
Beike_AspectD Flutter 3.0.0 flutter_tools patch gist
// @dart = 2.8
import 'package:flutter_tools/src/aop/hook_factory.dart';
import 'package:flutter_tools/src/dart/package_map.dart';
import 'package:package_config/package_config.dart';
import 'package:yaml/yaml.dart';
import 'package:file/file.dart' as f;
import '../globals.dart' as globals;
import '../artifacts.dart';
import '../build_info.dart';
import '../globals.dart';
/// 创建时间:2020-03-28
/// 作者:liujingguang
/// 描述:AopManager管理类
///
/// aop_config.yaml文件配置需要处理app.dill文件的工程, 在根项目下进行配置.
/// project_name为项目名称
/// exec_path为生成的snapshot的路径
///
/// 配置样例如下:
///
/// flutter_tools_hook:
/// - project_name: 'beike_aspectd'
/// exec_path: 'bin/starter.snapshot'
///
/// - project_name: 'flutter_aop_data_parse'
/// exec_path: 'bin/starter.snapshot'
class AopManager {
static const String sYamlConfigName = 'aop_config.yaml';
static const String key_flutter_tools_hook = 'flutter_tools_hook';
static const String key_project_name = 'project_name';
static const String key_exec_path = 'exec_path';
static Future<void> hookBuildBundleCommand(
String productDirPath,
BuildMode buildMode,
) async {
await _handleHook(productDirPath, buildMode, CommandType4Aop.Bundle);
}
static Future<void> hookBuildAotCommand(
String productDirPath,
BuildMode buildMode,
) async {
await _handleHook(productDirPath, buildMode, CommandType4Aop.Aot);
}
static Future<void> hookSnapshotCommand(
String productDirPath,
BuildMode buildMode,
) async {
await _handleHook(productDirPath, buildMode, CommandType4Aop.Snapshot);
}
static Future<List<String>> aopArgs(BuildMode buildMode) async {
final String dart_path = globals.artifacts
.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk);
final List proceduresList = await _aopProcedures();
final List<String> args = [
'--build-mode',
buildMode.name,
'--dart-path',
dart_path
];
if (proceduresList != null && proceduresList.isNotEmpty) {
final String produces = proceduresList.join(',');
args.add('--aop-packages');
args.add(produces);
}
return args;
}
static Future<List<String>> _aopProcedures() async {
List<String> procedures = List<String>();
final String configYamlPath =
fs.path.join(fs.currentDirectory.path, sYamlConfigName);
if (fs.file(configYamlPath).existsSync()) {
final dynamic yamlInfo =
loadYaml(fs.file(configYamlPath).readAsStringSync());
if (yamlInfo == null) {
return null;
}
if (yamlInfo[key_flutter_tools_hook] is! YamlList) {
return null;
}
final YamlList yamlNodes = yamlInfo[key_flutter_tools_hook] as YamlList;
for (dynamic v in yamlNodes) {
if (v == null) {
continue;
}
final String projectName = v[key_project_name] as String;
final String execPath = v[key_exec_path] as String;
if (projectName == null || execPath == null) {
continue;
}
final String packagePath = await _findAopPackagePath(projectName);
if (packagePath == null) {
continue;
}
procedures.add(fs.path.join(packagePath, execPath));
}
}
return procedures;
}
static Future<void> _handleHook(
String productDirPath, BuildMode buildMode, CommandType4Aop type) async {
// return Future.value();
try {
final String configYamlPath =
fs.path.join(fs.currentDirectory.path, sYamlConfigName);
print(configYamlPath);
if (fs.file(configYamlPath).existsSync()) {
final dynamic yamlInfo =
loadYaml(fs.file(configYamlPath).readAsStringSync());
if (yamlInfo == null) {
return;
}
if (yamlInfo[key_flutter_tools_hook] is! YamlList) {
return;
}
final YamlList yamlNodes = yamlInfo[key_flutter_tools_hook] as YamlList;
for (dynamic v in yamlNodes) {
if (v == null) {
return;
}
final String projectName = v[key_project_name] as String;
final String execPath = v[key_exec_path] as String;
if (projectName == null || execPath == null) {
return;
}
final String packagePath = await _findAopPackagePath(projectName);
if (packagePath == null) {
return;
}
await HookFactory.hook(productDirPath,
fs.path.join(packagePath, execPath), buildMode, type);
}
}
// ignore: avoid_catches_without_on_clauses
} catch (e) {
printTrace('error in _handleHook of $type : ${e.toString()}');
}
}
/// 获取项目中引用flutter_aop_data_parse的路径
static Future<String> _findAopPackagePath(String projectName) async {
Map<String, dynamic> packages;
try {
final String packagesFilePath =
fs.path.join(fs.currentDirectory.path, '.packages');
final f.File packageFile = fs.file(packagesFilePath);
final PackageConfig packageConfig =
await loadPackageConfigWithLogging(packageFile);
packages = PackageConfig.toJson(packageConfig);
for (Package package in packageConfig.packages) {
if (package.name == projectName) {
final Uri uri = package.packageUriRoot;
final String uriString = uri.path;
return uriString;
}
}
// ignore: avoid_catches_without_on_clauses
} catch (e) {
printTrace('Invalid .packages file: $e');
return null;
}
return null;
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:yaml/yaml.dart';
import 'package:package_config/package_config.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../build_info.dart';
import '../base/file_system.dart';
import '../build_system/build_system.dart';
import '../build_system/targets/common.dart';
import '../cache.dart';
import '../compile.dart';
import '../dart/package_map.dart';
import '../globals.dart' as globals;
const String aspectdImplPackageRelPath = '..';
const String frontendServerDartSnapshot = 'frontend_server.dart.snapshot';
const String sYamlConfigName = 'aop_config.yaml';
const String key_flutter_tools_hook = 'flutter_tools_hook';
const String key_project_name = 'project_name';
const String key_exec_path = 'exec_path';
const String beike_aspectd = 'beike_aspectd';
const String inner_path = 'inner';
const String globalPackagesPath = '.packages';
class AspectdHook {
static Future<Directory> getPackagePathFromConfig(
String packageConfigPath, String packageName) async {
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
globals.fs.file(packageConfigPath),
logger: globals.logger,
);
if ((packageConfig?.packages?.length ?? 0) > 0) {
final Package aspectdPackage = packageConfig.packages.toList().firstWhere(
(Package element) => element.name == packageName,
orElse: () => null);
if (aspectdPackage == null) {
return null;
}
return globals.fs.directory(aspectdPackage.root.toFilePath());
}
return null;
}
static Future<Directory> getFlutterFrontendServerDirectory(
String packagesPath) async {
final Directory directory =
await getPackagePathFromConfig(packagesPath, beike_aspectd);
if (directory == null) {
return null;
}
return globals.fs.directory(globals.fs.path
.join(directory.absolute.path, inner_path, 'flutter_frontend_server'));
}
static bool configFileExists() {
final String configYamlPath =
globals.fs.path.join(globals.fs.currentDirectory.path, sYamlConfigName);
if (globals.fs.file(configYamlPath).existsSync()) {
final dynamic yamlInfo =
loadYaml(globals.fs.file(configYamlPath).readAsStringSync());
if (yamlInfo == null) {
return false;
}
if (yamlInfo[key_flutter_tools_hook] is! YamlList) {
return false;
}
final YamlList yamlNodes = yamlInfo[key_flutter_tools_hook] as YamlList;
if (yamlNodes.nodes.isNotEmpty) {
return true;
}
}
return false;
}
static Directory getAspectdDirectory(Directory rootProjectDir) {
return globals.fs.directory(globals.fs.path.normalize(globals.fs.path
.join(rootProjectDir.path, aspectdImplPackageRelPath, beike_aspectd)));
}
static Future<void> enableAspectd() async {
final Directory currentDirectory = globals.fs.currentDirectory;
final String packagesPath = globals.fs.path
.join(currentDirectory.absolute.path, globalPackagesPath);
final Directory beikeAspectdDirectory =
await getPackagePathFromConfig(packagesPath, beike_aspectd);
final Directory flutterFrontendServerDirectory =
await getFlutterFrontendServerDirectory(packagesPath);
if (beikeAspectdDirectory == null ||
flutterFrontendServerDirectory == null) {
return;
}
final String aspectdPackagesPath = globals.fs.path.join(
beikeAspectdDirectory.absolute.path, inner_path, globalPackagesPath);
await checkAspectdFlutterFrontendServerSnapshot(
aspectdPackagesPath, flutterFrontendServerDirectory);
}
static Future<void> checkAspectdFlutterFrontendServerSnapshot(
String packagesPath, Directory flutterFrontendServerDirectory) async {
final String aspectdFlutterFrontendServerSnapshot = globals.fs.path.join(
flutterFrontendServerDirectory.absolute.path,
frontendServerDartSnapshot);
final String defaultFlutterFrontendServerSnapshot = globals.artifacts
.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk);
final File defaultServerFile =
globals.fs.file(defaultFlutterFrontendServerSnapshot);
final File aspectdServerFile =
globals.fs.file(aspectdFlutterFrontendServerSnapshot);
if (defaultServerFile.existsSync()) {
if (md5.convert(defaultServerFile.readAsBytesSync()) ==
md5.convert(aspectdServerFile.readAsBytesSync())) {
return true;
}
globals.fs.file(defaultFlutterFrontendServerSnapshot).deleteSync();
}
aspectdServerFile.copySync(defaultFlutterFrontendServerSnapshot);
}
static Future<String> getDartSdkDependency(String aspectdDir) async {
final ProcessResult processResult = await globals.processManager.run(
<String>[
globals.fs.path.join(
globals.artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk),
'bin',
'pub'),
'get',
'--verbosity=warning'
],
workingDirectory: aspectdDir,
environment: <String, String>{'FLUTTER_ROOT': Cache.flutterRoot});
if (processResult.exitCode != 0) {
throwToolExit(
'Aspectd unexpected error: ${processResult.stderr.toString()}');
}
final Directory kernelDir = await getPackagePathFromConfig(
globals.fs.path.join(aspectdDir, globalPackagesPath), 'kernel');
return kernelDir.parent.parent.path;
}
Future<void> runBuildDillCommand(Environment environment) async {
print('aop front end compiling');
final Directory mainDirectory = globals.fs.currentDirectory;
String relativeDir = environment.outputDir.absolute.path
.substring(environment.projectDir.absolute.path.length + 1);
final String outputDir =
globals.fs.path.join(mainDirectory.path, relativeDir);
final String buildDir =
globals.fs.path.join(mainDirectory.path, '.dart_tool', 'flutter_build');
final Map<String, String> defines = environment.defines;
relativeDir = defines[kTargetFile]
.substring(environment.projectDir.absolute.path.length + 1);
String targetFile = environment.defines[kTargetFile];
targetFile ??= globals.fs.path.join(mainDirectory.path, 'lib', 'main.dart');
defines[kTargetFile] = targetFile;
final Environment auxEnvironment = Environment(
projectDir: mainDirectory,
outputDir: globals.fs.directory(outputDir),
cacheDir: environment.cacheDir,
flutterRootDir: environment.flutterRootDir,
fileSystem: environment.fileSystem,
logger: environment.logger,
artifacts: environment.artifacts,
processManager: environment.processManager,
engineVersion: environment.engineVersion,
buildDir: globals.fs.directory(buildDir),
defines: defines,
inputs: environment.inputs);
const KernelSnapshot auxKernelSnapshot = KernelSnapshot();
final CompilerOutput compilerOutput =
await auxKernelSnapshot.buildImpl(auxEnvironment);
final String aspectdDill = compilerOutput.outputFilename;
print('Aspectdill path : ' + aspectdDill);
final File originalDillFile = globals.fs.file(
globals.fs.path.join(environment.buildDir.absolute.path, 'app.dill'));
print('originalDillFile path : ' + originalDillFile.path);
if (originalDillFile.existsSync()) {
await originalDillFile.copy(originalDillFile.absolute.path + '.bak');
}
globals.fs.file(aspectdDill).copySync(originalDillFile.absolute.path);
}
}
// 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.
// @dart = 2.8
import '../base/common.dart';
import '../build_info.dart';
import '../bundle.dart';
import '../bundle_builder.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../reporting/reporting.dart';
import '../runner/flutter_command.dart';
import 'build.dart';
import '../aop/aspectd.dart';
class BuildBundleCommand extends BuildSubCommand {
BuildBundleCommand({
bool verboseHelp = false,
this.bundleBuilder,
}) : super(verboseHelp: verboseHelp) {
usesTargetOption();
usesFilesystemOptions(hide: !verboseHelp);
usesBuildNumberOption();
addBuildModeFlags(verboseHelp: verboseHelp, defaultToRelease: false);
usesDartDefineOption();
usesExtraDartFlagOptions(verboseHelp: verboseHelp);
argParser
..addOption('depfile',
defaultsTo: defaultDepfilePath,
help: 'A file path where a depfile will be written. '
'This contains all build inputs and outputs in a Make-style syntax.')
..addOption(
'target-platform',
defaultsTo: 'android-arm',
allowed: const <String>[
'android-arm',
'android-arm64',
'android-x86',
'android-x64',
'ios',
'darwin',
'linux-x64',
'linux-arm64',
'windows-x64',
],
help: 'The architecture for which to build the application.',
)
..addOption(
'asset-dir',
defaultsTo: getAssetBuildDirectory(),
help:
'The output directory for the kernel_blob.bin file, the native snapshot, the assets, etc. '
'Can be used to redirect the output when driving the Flutter toolchain from another build system.',
)
..addFlag(
'tree-shake-icons',
negatable: true,
defaultsTo: false,
hide: !verboseHelp,
help:
'(deprecated) Icon font tree shaking is not supported by this command.',
);
usesPubOption();
usesTrackWidgetCreation(verboseHelp: verboseHelp);
bundleBuilder ??= BundleBuilder();
}
BundleBuilder bundleBuilder;
@override
final String name = 'bundle';
@override
final String description =
'Build the Flutter assets directory from your app.';
@override
final String usageFooter = 'The Flutter assets directory contains your '
'application code and resources; they are used by some Flutter Android and'
' iOS runtimes.';
@override
Future<CustomDimensions> get usageValues async {
final String projectDir = globals.fs.file(targetFile).parent.parent.path;
final FlutterProject flutterProject =
FlutterProject.fromDirectory(globals.fs.directory(projectDir));
if (flutterProject == null) {
return const CustomDimensions();
}
return CustomDimensions(
commandBuildBundleTargetPlatform: stringArg('target-platform'),
commandBuildBundleIsModule: flutterProject.isModule,
);
}
@override
Future<void> validateCommand() async {
if (argResults['tree-shake-icons'] as bool) {
throwToolExit(
'The "--tree-shake-icons" flag is deprecated for "build bundle" and will be removed in a future version of Flutter.');
}
return super.validateCommand();
}
@override
Future<FlutterCommandResult> runCommand() async {
await AspectdHook.enableAspectd();
final String targetPlatform = stringArg('target-platform');
final TargetPlatform platform = getTargetPlatformForName(targetPlatform);
if (platform == null) {
throwToolExit('Unknown platform: $targetPlatform');
}
// Check for target platforms that are only allowed via feature flags.
switch (platform) {
case TargetPlatform.darwin:
if (!featureFlags.isMacOSEnabled) {
throwToolExit('macOS is not a supported target platform.');
}
break;
case TargetPlatform.windows_x64:
case TargetPlatform.windows_uwp_x64:
if (!featureFlags.isWindowsEnabled) {
throwToolExit('Windows is not a supported target platform.');
}
break;
case TargetPlatform.linux_x64:
case TargetPlatform.linux_arm64:
if (!featureFlags.isLinuxEnabled) {
throwToolExit('Linux is not a supported target platform.');
}
break;
case TargetPlatform.android:
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
case TargetPlatform.fuchsia_arm64:
case TargetPlatform.fuchsia_x64:
case TargetPlatform.ios:
case TargetPlatform.tester:
case TargetPlatform.web_javascript:
break;
}
final BuildInfo buildInfo = await getBuildInfo();
displayNullSafetyMode(buildInfo);
await bundleBuilder.build(
platform: platform,
buildInfo: buildInfo,
mainPath: targetFile,
depfilePath: stringArg('depfile'),
assetDirPath: stringArg('asset-dir'),
);
return FlutterCommandResult.success();
}
}
// 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:package_config/package_config.dart';
import '../../aop/aspectd.dart';
import '../../artifacts.dart';
import '../../base/build.dart';
import '../../base/file_system.dart';
import '../../build_info.dart';
import '../../compile.dart';
import '../../dart/package_map.dart';
import '../../globals.dart' as globals show xcode;
import '../build_system.dart';
import '../depfile.dart';
import '../exceptions.dart';
import 'assets.dart';
import 'dart_plugin_registrant.dart';
import 'icon_tree_shaker.dart';
import 'localizations.dart';
/// Copies the pre-built flutter bundle.
// This is a one-off rule for implementing build bundle in terms of assemble.
class CopyFlutterBundle extends Target {
const CopyFlutterBundle();
@override
String get name => 'copy_flutter_bundle';
@override
List<Source> get inputs => const <Source>[
Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
Source.pattern('{BUILD_DIR}/app.dill'),
...IconTreeShaker.inputs,
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{OUTPUT_DIR}/vm_snapshot_data'),
Source.pattern('{OUTPUT_DIR}/isolate_snapshot_data'),
Source.pattern('{OUTPUT_DIR}/kernel_blob.bin'),
];
@override
List<String> get depfiles => <String>['flutter_assets.d'];
@override
Future<void> build(Environment environment) async {
final String? buildModeEnvironment = environment.defines[kBuildMode];
if (buildModeEnvironment == null) {
throw MissingDefineException(kBuildMode, 'copy_flutter_bundle');
}
final BuildMode buildMode = getBuildModeForName(buildModeEnvironment);
environment.outputDir.createSync(recursive: true);
// Only copy the prebuilt runtimes and kernel blob in debug mode.
if (buildMode == BuildMode.debug) {
final String vmSnapshotData = environment.artifacts
.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug);
final String isolateSnapshotData = environment.artifacts
.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug);
environment.buildDir
.childFile('app.dill')
.copySync(environment.outputDir.childFile('kernel_blob.bin').path);
environment.fileSystem
.file(vmSnapshotData)
.copySync(environment.outputDir.childFile('vm_snapshot_data').path);
environment.fileSystem.file(isolateSnapshotData).copySync(
environment.outputDir.childFile('isolate_snapshot_data').path);
}
final Depfile assetDepfile = await copyAssets(
environment,
environment.outputDir,
targetPlatform: TargetPlatform.android,
buildMode: buildMode,
);
final DepfileService depfileService = DepfileService(
fileSystem: environment.fileSystem,
logger: environment.logger,
);
depfileService.writeToFile(
assetDepfile,
environment.buildDir.childFile('flutter_assets.d'),
);
}
@override
List<Target> get dependencies => const <Target>[
KernelSnapshot(),
];
}
/// Copies the pre-built flutter bundle for release mode.
class ReleaseCopyFlutterBundle extends CopyFlutterBundle {
const ReleaseCopyFlutterBundle();
@override
String get name => 'release_flutter_bundle';
@override
List<Source> get inputs => const <Source>[];
@override
List<Source> get outputs => const <Source>[];
@override
List<String> get depfiles => const <String>[
'flutter_assets.d',
];
@override
List<Target> get dependencies => const <Target>[];
}
/// Generate a snapshot of the dart code used in the program.
///
/// Note that this target depends on the `.dart_tool/package_config.json` file
/// even though it is not listed as an input. Pub inserts a timestamp into
/// the file which causes unnecessary rebuilds, so instead a subset of the contents
/// are used an input instead.
class KernelSnapshot extends Target {
const KernelSnapshot();
@override
String get name => 'kernel_snapshot';
@override
List<Source> get inputs => const <Source>[
Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'),
Source.pattern(
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'),
Source.artifact(Artifact.platformKernelDill),
Source.hostArtifact(HostArtifact.engineDartBinary),
Source.artifact(Artifact.frontendServerSnapshotForEngineDartSdk),
];
@override
List<Source> get outputs => const <Source>[];
@override
List<String> get depfiles => <String>[
'kernel_snapshot.d',
];
@override
List<Target> get dependencies => const <Target>[
GenerateLocalizationsTarget(),
DartPluginRegistrantTarget(),
];
@override
Future<void> build(Environment environment) async {
await AspectdHook.enableAspectd();
await buildImpl(environment);
}
Future<CompilerOutput> buildImpl(Environment environment) async {
final KernelCompiler compiler = KernelCompiler(
fileSystem: environment.fileSystem,
logger: environment.logger,
processManager: environment.processManager,
artifacts: environment.artifacts,
fileSystemRoots: <String>[],
);
final String? buildModeEnvironment = environment.defines[kBuildMode];
if (buildModeEnvironment == null) {
throw MissingDefineException(kBuildMode, 'kernel_snapshot');
}
final String? targetPlatformEnvironment =
environment.defines[kTargetPlatform];
if (targetPlatformEnvironment == null) {
throw MissingDefineException(kTargetPlatform, 'kernel_snapshot');
}
final BuildMode buildMode = getBuildModeForName(buildModeEnvironment);
final String targetFile = environment.defines[kTargetFile] ??
environment.fileSystem.path.join('lib', 'main.dart');
final File packagesFile = environment.projectDir
.childDirectory('.dart_tool')
.childFile('package_config.json');
final String targetFileAbsolute =
environment.fileSystem.file(targetFile).absolute.path;
// everything besides 'false' is considered to be enabled.
final bool trackWidgetCreation =
environment.defines[kTrackWidgetCreation] != 'false';
final TargetPlatform targetPlatform =
getTargetPlatformForName(targetPlatformEnvironment);
// This configuration is all optional.
final List<String> extraFrontEndOptions =
decodeCommaSeparated(environment.defines, kExtraFrontEndOptions);
final List<String>? fileSystemRoots =
environment.defines[kFileSystemRoots]?.split(',');
final String? fileSystemScheme = environment.defines[kFileSystemScheme];
TargetModel targetModel = TargetModel.flutter;
if (targetPlatform == TargetPlatform.fuchsia_x64 ||
targetPlatform == TargetPlatform.fuchsia_arm64) {
targetModel = TargetModel.flutterRunner;
}
// Force linking of the platform for desktop embedder targets since these
// do not correctly load the core snapshots in debug mode.
// See https://github.com/flutter/flutter/issues/44724
bool forceLinkPlatform;
switch (targetPlatform) {
case TargetPlatform.darwin:
case TargetPlatform.windows_x64:
case TargetPlatform.linux_x64:
forceLinkPlatform = true;
break;
case TargetPlatform.android:
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
case TargetPlatform.fuchsia_arm64:
case TargetPlatform.fuchsia_x64:
case TargetPlatform.ios:
case TargetPlatform.linux_arm64:
case TargetPlatform.tester:
case TargetPlatform.web_javascript:
case TargetPlatform.windows_uwp_x64:
forceLinkPlatform = false;
break;
}
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
packagesFile,
logger: environment.logger,
);
final CompilerOutput? output = await compiler.compile(
sdkRoot: environment.artifacts.getArtifactPath(
Artifact.flutterPatchedSdkPath,
platform: targetPlatform,
mode: buildMode,
),
aot: buildMode.isPrecompiled,
buildMode: buildMode,
trackWidgetCreation: trackWidgetCreation && buildMode == BuildMode.debug,
targetModel: targetModel,
outputFilePath: environment.buildDir.childFile('app.dill').path,
packagesPath: packagesFile.path,
linkPlatformKernelIn: forceLinkPlatform || buildMode.isPrecompiled,
mainPath: targetFileAbsolute,
depFilePath: environment.buildDir.childFile('kernel_snapshot.d').path,
extraFrontEndOptions: extraFrontEndOptions,
fileSystemRoots: fileSystemRoots,
fileSystemScheme: fileSystemScheme,
dartDefines: decodeDartDefines(environment.defines, kDartDefines),
packageConfig: packageConfig,
buildDir: environment.buildDir,
checkDartPluginRegistry: environment.generateDartPluginRegistry,
);
if (output == null || output.errorCount != 0) {
throw Exception();
}
return output;
}
}
/// Supports compiling a dart kernel file to an ELF binary.
abstract class AotElfBase extends Target {
const AotElfBase();
@override
String get analyticsName => 'android_aot';
@override
Future<void> build(Environment environment) async {
final AOTSnapshotter snapshotter = AOTSnapshotter(
fileSystem: environment.fileSystem,
logger: environment.logger,
xcode: globals.xcode!,
processManager: environment.processManager,
artifacts: environment.artifacts,
);
final String outputPath = environment.buildDir.path;
final String? buildModeEnvironment = environment.defines[kBuildMode];
if (buildModeEnvironment == null) {
throw MissingDefineException(kBuildMode, 'aot_elf');
}
final String? targetPlatformEnvironment =
environment.defines[kTargetPlatform];
if (targetPlatformEnvironment == null) {
throw MissingDefineException(kTargetPlatform, 'aot_elf');
}
final List<String> extraGenSnapshotOptions =
decodeCommaSeparated(environment.defines, kExtraGenSnapshotOptions);
final BuildMode buildMode = getBuildModeForName(buildModeEnvironment);
final TargetPlatform targetPlatform =
getTargetPlatformForName(targetPlatformEnvironment);
final String? splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation =
environment.defines[kDartObfuscation] == 'true';
final String? codeSizeDirectory = environment.defines[kCodeSizeDirectory];
if (codeSizeDirectory != null) {
final File codeSizeFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('snapshot.${environment.defines[kTargetPlatform]}.json');
final File precompilerTraceFile = environment.fileSystem
.directory(codeSizeDirectory)
.childFile('trace.${environment.defines[kTargetPlatform]}.json');
extraGenSnapshotOptions
.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
extraGenSnapshotOptions
.add('--trace-precompiler-to=${precompilerTraceFile.path}');
}
final int snapshotExitCode = await snapshotter.build(
platform: targetPlatform,
buildMode: buildMode,
mainPath: environment.buildDir.childFile('app.dill').path,
outputPath: outputPath,
bitcode: false,
extraGenSnapshotOptions: extraGenSnapshotOptions,
splitDebugInfo: splitDebugInfo,
dartObfuscation: dartObfuscation,
);
if (snapshotExitCode != 0) {
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
}
}
}
/// Generate an ELF binary from a dart kernel file in profile mode.
class AotElfProfile extends AotElfBase {
const AotElfProfile(this.targetPlatform);
@override
String get name => 'aot_elf_profile';
@override
List<Source> get inputs => <Source>[
const Source.pattern(
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'),
const Source.pattern('{BUILD_DIR}/app.dill'),
const Source.hostArtifact(HostArtifact.engineDartBinary),
const Source.artifact(Artifact.skyEnginePath),
Source.artifact(
Artifact.genSnapshot,
platform: targetPlatform,
mode: BuildMode.profile,
),
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{BUILD_DIR}/app.so'),
];
@override
List<Target> get dependencies => const <Target>[
KernelSnapshot(),
];
final TargetPlatform targetPlatform;
}
/// Generate an ELF binary from a dart kernel file in release mode.
class AotElfRelease extends AotElfBase {
const AotElfRelease(this.targetPlatform);
@override
String get name => 'aot_elf_release';
@override
List<Source> get inputs => <Source>[
const Source.pattern(
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'),
const Source.pattern('{BUILD_DIR}/app.dill'),
const Source.hostArtifact(HostArtifact.engineDartBinary),
const Source.artifact(Artifact.skyEnginePath),
Source.artifact(
Artifact.genSnapshot,
platform: targetPlatform,
mode: BuildMode.release,
),
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{BUILD_DIR}/app.so'),
];
@override
List<Target> get dependencies => const <Target>[
KernelSnapshot(),
];
final TargetPlatform targetPlatform;
}
/// Copies the pre-built flutter aot bundle.
// This is a one-off rule for implementing build aot in terms of assemble.
abstract class CopyFlutterAotBundle extends Target {
const CopyFlutterAotBundle();
@override
List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/app.so'),
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{OUTPUT_DIR}/app.so'),
];
@override
Future<void> build(Environment environment) async {
final File outputFile = environment.outputDir.childFile('app.so');
if (!outputFile.parent.existsSync()) {
outputFile.parent.createSync(recursive: true);
}
environment.buildDir.childFile('app.so').copySync(outputFile.path);
}
}
// 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 'dart:async';
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:process/process.dart';
import 'package:usage/uuid/uuid.dart';
import 'artifacts.dart';
import 'base/common.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/platform.dart';
import 'build_info.dart';
import 'convert.dart';
import 'aop/aspectd.dart';
/// The target model describes the set of core libraries that are available within
/// the SDK.
class TargetModel {
/// Parse a [TargetModel] from a raw string.
///
/// Throws an exception if passed a value other than 'flutter',
/// 'flutter_runner', 'vm', or 'dartdevc'.
factory TargetModel(String rawValue) {
switch (rawValue) {
case 'flutter':
return flutter;
case 'flutter_runner':
return flutterRunner;
case 'vm':
return vm;
case 'dartdevc':
return dartdevc;
}
throw Exception('Unexpected target model $rawValue');
}
const TargetModel._(this._value);
/// The Flutter patched Dart SDK.
static const TargetModel flutter = TargetModel._('flutter');
/// The Fuchsia patched SDK.
static const TargetModel flutterRunner = TargetModel._('flutter_runner');
/// The Dart VM.
static const TargetModel vm = TargetModel._('vm');
/// The development compiler for JavaScript.
static const TargetModel dartdevc = TargetModel._('dartdevc');
final String _value;
@override
String toString() => _value;
}
class CompilerOutput {
const CompilerOutput(this.outputFilename, this.errorCount, this.sources,
{this.expressionData});
final String outputFilename;
final int errorCount;
final List<Uri> sources;
/// This field is only non-null for expression compilation requests.
final Uint8List? expressionData;
}
enum StdoutState { CollectDiagnostic, CollectDependencies }
/// Handles stdin/stdout communication with the frontend server.
class StdoutHandler {
StdoutHandler({
required Logger logger,
required FileSystem fileSystem,
}) : _logger = logger,
_fileSystem = fileSystem {
reset();
}
final Logger _logger;
final FileSystem _fileSystem;
String? boundaryKey;
StdoutState state = StdoutState.CollectDiagnostic;
Completer<CompilerOutput?>? compilerOutput;
final List<Uri> sources = <Uri>[];
bool _suppressCompilerMessages = false;
bool _expectSources = true;
bool _readFile = false;
void handler(String message) {
const String kResultPrefix = 'result ';
if (boundaryKey == null && message.startsWith(kResultPrefix)) {
boundaryKey = message.substring(kResultPrefix.length);
return;
}
final String? messageBoundaryKey = boundaryKey;
if (messageBoundaryKey != null && message.startsWith(messageBoundaryKey)) {
if (_expectSources) {
if (state == StdoutState.CollectDiagnostic) {
state = StdoutState.CollectDependencies;
return;
}
}
if (message.length <= messageBoundaryKey.length) {
compilerOutput?.complete(null);
return;
}
final int spaceDelimiter = message.lastIndexOf(' ');
final String fileName =
message.substring(messageBoundaryKey.length + 1, spaceDelimiter);
final int errorCount =
int.parse(message.substring(spaceDelimiter + 1).trim());
Uint8List? expressionData;
if (_readFile) {
expressionData = _fileSystem.file(fileName).readAsBytesSync();
}
final CompilerOutput output = CompilerOutput(
fileName,
errorCount,
sources,
expressionData: expressionData,
);
compilerOutput?.complete(output);
return;
}
if (state == StdoutState.CollectDiagnostic) {
if (!_suppressCompilerMessages) {
_logger.printError(message);
} else {
_logger.printTrace(message);
}
} else {
assert(state == StdoutState.CollectDependencies);
switch (message[0]) {
case '+':
sources.add(Uri.parse(message.substring(1)));
break;
case '-':
sources.remove(Uri.parse(message.substring(1)));
break;
default:
_logger.printTrace('Unexpected prefix for $message uri - ignoring');
}
}
}
// This is needed to get ready to process next compilation result output,
// with its own boundary key and new completer.
void reset(
{bool suppressCompilerMessages = false,
bool expectSources = true,
bool readFile = false}) {
boundaryKey = null;
compilerOutput = Completer<CompilerOutput?>();
_suppressCompilerMessages = suppressCompilerMessages;
_expectSources = expectSources;
_readFile = readFile;
state = StdoutState.CollectDiagnostic;
}
}
/// List the preconfigured build options for a given build mode.
List<String> buildModeOptions(BuildMode mode, List<String> dartDefines) {
switch (mode) {
case BuildMode.debug:
return <String>[
'-Ddart.vm.profile=false',
// This allows the CLI to override the value of this define for unit
// testing the framework.
if (!dartDefines
.any((String define) => define.startsWith('dart.vm.product')))
'-Ddart.vm.product=false',
'--enable-asserts',
];
case BuildMode.profile:
return <String>[
'-Ddart.vm.profile=true',
'-Ddart.vm.product=false',
];
case BuildMode.release:
return <String>[
'-Ddart.vm.profile=false',
'-Ddart.vm.product=true',
];
}
throw Exception('Unknown BuildMode: $mode');
}
/// A compiler interface for producing single (non-incremental) kernel files.
class KernelCompiler {
KernelCompiler({
required FileSystem fileSystem,
required Logger logger,
required ProcessManager processManager,
required Artifacts artifacts,
required List<String> fileSystemRoots,
String? fileSystemScheme,
@visibleForTesting StdoutHandler? stdoutHandler,
}) : _logger = logger,
_fileSystem = fileSystem,
_artifacts = artifacts,
_processManager = processManager,
_fileSystemScheme = fileSystemScheme,
_fileSystemRoots = fileSystemRoots,
_stdoutHandler = stdoutHandler ??
StdoutHandler(logger: logger, fileSystem: fileSystem);
final FileSystem _fileSystem;
final Artifacts _artifacts;
final ProcessManager _processManager;
final Logger _logger;
final String? _fileSystemScheme;
final List<String> _fileSystemRoots;
final StdoutHandler _stdoutHandler;
Future<CompilerOutput?> compile({
required String sdkRoot,
String? mainPath,
String? outputFilePath,
String? depFilePath,
TargetModel targetModel = TargetModel.flutter,
bool linkPlatformKernelIn = false,
bool aot = false,
List<String>? extraFrontEndOptions,
List<String>? fileSystemRoots,
String? fileSystemScheme,
String? initializeFromDill,
String? platformDill,
Directory? buildDir,
bool checkDartPluginRegistry = false,
required String? packagesPath,
required BuildMode buildMode,
required bool trackWidgetCreation,
required List<String> dartDefines,
required PackageConfig packageConfig,
}) async {
final String frontendServer = _artifacts
.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk);
// This is a URI, not a file path, so the forward slash is correct even on Windows.
if (!sdkRoot.endsWith('/')) {
sdkRoot = '$sdkRoot/';
}
final String engineDartPath =
_artifacts.getHostArtifact(HostArtifact.engineDartBinary).path;
if (!_processManager.canRun(engineDartPath)) {
throwToolExit('Unable to find Dart binary at $engineDartPath');
}
String? mainUri;
final File mainFile = _fileSystem.file(mainPath);
final Uri mainFileUri = mainFile.uri;
if (packagesPath != null) {
mainUri = packageConfig.toPackageUri(mainFileUri)?.toString();
}
mainUri ??= toMultiRootPath(mainFileUri, _fileSystemScheme,
_fileSystemRoots, _fileSystem.path.separator == r'\');
if (outputFilePath != null && !_fileSystem.isFileSync(outputFilePath)) {
_fileSystem.file(outputFilePath).createSync(recursive: true);
}
if (buildDir != null && checkDartPluginRegistry) {
// Check if there's a Dart plugin registrant.
// This is contained in the file `generated_main.dart` under `.dart_tools/flutter_build/`.
final File newMainDart = buildDir.parent.childFile('generated_main.dart');
if (newMainDart.existsSync()) {
mainUri = newMainDart.path;
}
}
final List<String> command = <String>[
engineDartPath,
'--disable-dart-dev',
frontendServer,
'--sdk-root',
sdkRoot,
'--target=$targetModel',
'--no-print-incremental-dependencies',
for (final Object dartDefine in dartDefines) '-D$dartDefine',
...buildModeOptions(buildMode, dartDefines),
if (trackWidgetCreation) '--track-widget-creation',
if (!linkPlatformKernelIn) '--no-link-platform',
if (aot) ...<String>[
'--aot',
'--tfa',
],
if (packagesPath != null) ...<String>[
'--packages',
packagesPath,
],
if (outputFilePath != null) ...<String>[
'--output-dill',
outputFilePath,
],
if (depFilePath != null &&
(fileSystemRoots == null || fileSystemRoots.isEmpty)) ...<String>[
'--depfile',
depFilePath,
],
if (fileSystemRoots != null)
for (final String root in fileSystemRoots) ...<String>[
'--filesystem-root',
root,
],
if (fileSystemScheme != null) ...<String>[
'--filesystem-scheme',
fileSystemScheme,
],
if (initializeFromDill != null) ...<String>[
'--initialize-from-dill',
initializeFromDill,
],
if (platformDill != null) ...<String>[
'--platform',
platformDill,
],
...?extraFrontEndOptions,
mainUri,
];
_logger.printTrace(command.join(' '));
final Process server = await _processManager.start(command);
server.stderr.transform<String>(utf8.decoder).listen(_logger.printError);
server.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(_stdoutHandler.handler);
final int exitCode = await server.exitCode;
if (exitCode == 0) {
return _stdoutHandler.compilerOutput?.future;
}
return null;
}
}
/// Class that allows to serialize compilation requests to the compiler.
abstract class _CompilationRequest {
_CompilationRequest(this.completer);
Completer<CompilerOutput?> completer;
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler);
Future<void> run(DefaultResidentCompiler compiler) async {
completer.complete(await _run(compiler));
}
}
class _RecompileRequest extends _CompilationRequest {
_RecompileRequest(
Completer<CompilerOutput?> completer,
this.mainUri,
this.invalidatedFiles,
this.outputPath,
this.packageConfig,
this.suppressErrors,
) : super(completer);
Uri mainUri;
List<Uri>? invalidatedFiles;
String outputPath;
PackageConfig packageConfig;
bool suppressErrors;
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
compiler._recompile(this);
}
class _CompileExpressionRequest extends _CompilationRequest {
_CompileExpressionRequest(
Completer<CompilerOutput?> completer,
this.expression,
this.definitions,
this.typeDefinitions,
this.libraryUri,
this.klass,
this.isStatic,
) : super(completer);
String expression;
List<String>? definitions;
List<String>? typeDefinitions;
String? libraryUri;
String? klass;
bool isStatic;
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
compiler._compileExpression(this);
}
class _CompileExpressionToJsRequest extends _CompilationRequest {
_CompileExpressionToJsRequest(
Completer<CompilerOutput?> completer,
this.libraryUri,
this.line,
this.column,
this.jsModules,
this.jsFrameValues,
this.moduleName,
this.expression,
) : super(completer);
final String? libraryUri;
final int line;
final int column;
final Map<String, String>? jsModules;
final Map<String, String>? jsFrameValues;
final String? moduleName;
final String? expression;
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
compiler._compileExpressionToJs(this);
}
class _RejectRequest extends _CompilationRequest {
_RejectRequest(Completer<CompilerOutput?> completer) : super(completer);
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
compiler._reject();
}
/// Wrapper around incremental frontend server compiler, that communicates with
/// server via stdin/stdout.
///
/// The wrapper is intended to stay resident in memory as user changes, reloads,
/// restarts the Flutter app.
abstract class ResidentCompiler {
factory ResidentCompiler(
String sdkRoot, {
required BuildMode buildMode,
required Logger logger,
required ProcessManager processManager,
required Artifacts artifacts,
required Platform platform,
required FileSystem fileSystem,
bool testCompilation,
bool trackWidgetCreation,
String packagesPath,
List<String> fileSystemRoots,
String? fileSystemScheme,
String initializeFromDill,
TargetModel targetModel,
bool unsafePackageSerialization,
List<String> extraFrontEndOptions,
String platformDill,
List<String>? dartDefines,
String librariesSpec,
}) = DefaultResidentCompiler;
// TODO(zanderso): find a better way to configure additional file system
// roots from the runner.
// See: https://github.com/flutter/flutter/issues/50494
void addFileSystemRoot(String root);
/// If invoked for the first time, it compiles Dart script identified by
/// [mainPath], [invalidatedFiles] list is ignored.
/// On successive runs [invalidatedFiles] indicates which files need to be
/// recompiled. If [mainPath] is [null], previously used [mainPath] entry
/// point that is used for recompilation.
/// Binary file name is returned if compilation was successful, otherwise
/// null is returned.
///
/// If [checkDartPluginRegistry] is true, it is the caller's responsibility
/// to ensure that the generated registrant file has been updated such that
/// it is wrapping [mainUri].
Future<CompilerOutput?> recompile(
Uri mainUri,
List<Uri>? invalidatedFiles, {
required String outputPath,
required PackageConfig packageConfig,
required FileSystem fs,
String? projectRootPath,
bool suppressErrors = false,
bool checkDartPluginRegistry = false,
});
Future<CompilerOutput?> compileExpression(
String expression,
List<String>? definitions,
List<String>? typeDefinitions,
String? libraryUri,
String? klass,
bool isStatic,
);
/// Compiles [expression] in [libraryUri] at [line]:[column] to JavaScript
/// in [moduleName].
///
/// Values listed in [jsFrameValues] are substituted for their names in the
/// [expression].
///
/// Ensures that all [jsModules] are loaded and accessible inside the
/// expression.
///
/// Example values of parameters:
/// [moduleName] is of the form '/packages/hello_world_main.dart'
/// [jsFrameValues] is a map from js variable name to its primitive value
/// or another variable name, for example
/// { 'x': '1', 'y': 'y', 'o': 'null' }
/// [jsModules] is a map from variable name to the module name, where
/// variable name is the name originally used in JavaScript to contain the
/// module object, for example:
/// { 'dart':'dart_sdk', 'main': '/packages/hello_world_main.dart' }
/// Returns a [CompilerOutput] including the name of the file containing the
/// compilation result and a number of errors.
Future<CompilerOutput?> compileExpressionToJs(
String libraryUri,
int line,
int column,
Map<String, String> jsModules,
Map<String, String> jsFrameValues,
String moduleName,
String expression,
);
/// Should be invoked when results of compilation are accepted by the client.
///
/// Either [accept] or [reject] should be called after every [recompile] call.
void accept();
/// Should be invoked when results of compilation are rejected by the client.
///
/// Either [accept] or [reject] should be called after every [recompile] call.
Future<CompilerOutput?> reject();
/// Should be invoked when frontend server compiler should forget what was
/// accepted previously so that next call to [recompile] produces complete
/// kernel file.
void reset();
Future<Object> shutdown();
}
@visibleForTesting
class DefaultResidentCompiler implements ResidentCompiler {
DefaultResidentCompiler(
String sdkRoot, {
required this.buildMode,
required Logger logger,
required ProcessManager processManager,
required Artifacts artifacts,
required Platform platform,
required FileSystem fileSystem,
this.testCompilation = false,
this.trackWidgetCreation = true,
this.packagesPath,
List<String> fileSystemRoots = const <String>[],
this.fileSystemScheme,
this.initializeFromDill,
this.targetModel = TargetModel.flutter,
this.unsafePackageSerialization = false,
this.extraFrontEndOptions,
this.platformDill,
List<String>? dartDefines,
this.librariesSpec,
@visibleForTesting StdoutHandler? stdoutHandler,
}) : assert(sdkRoot != null),
_logger = logger,
_processManager = processManager,
_artifacts = artifacts,
_stdoutHandler = stdoutHandler ??
StdoutHandler(logger: logger, fileSystem: fileSystem),
_platform = platform,
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/',
// Make a copy, we might need to modify it later.
fileSystemRoots = List<String>.from(fileSystemRoots);
final Logger _logger;
final ProcessManager _processManager;
final Artifacts _artifacts;
final Platform _platform;
final bool testCompilation;
final BuildMode buildMode;
final bool trackWidgetCreation;
final String? packagesPath;
final TargetModel targetModel;
final List<String> fileSystemRoots;
final String? fileSystemScheme;
final String? initializeFromDill;
final bool unsafePackageSerialization;
final List<String>? extraFrontEndOptions;
final List<String> dartDefines;
final String? librariesSpec;
@override
void addFileSystemRoot(String root) {
fileSystemRoots.add(root);
}
/// The path to the root of the Dart SDK used to compile.
///
/// This is used to resolve the [platformDill].
final String sdkRoot;
/// The path to the platform dill file.
///
/// This does not need to be provided for the normal Flutter workflow.
final String? platformDill;
Process? _server;
final StdoutHandler _stdoutHandler;
bool _compileRequestNeedsConfirmation = false;
final StreamController<_CompilationRequest> _controller =
StreamController<_CompilationRequest>();
@override
Future<CompilerOutput?> recompile(
Uri mainUri,
List<Uri>? invalidatedFiles, {
required String outputPath,
required PackageConfig packageConfig,
bool suppressErrors = false,
bool checkDartPluginRegistry = false,
String? projectRootPath,
FileSystem? fs,
}) async {
assert(outputPath != null);
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
// `generated_main.dart` contains the Dart plugin registry.
if (checkDartPluginRegistry && projectRootPath != null && fs != null) {
final File generatedMainDart = fs.file(
fs.path.join(
projectRootPath,
'.dart_tool',
'flutter_build',
'generated_main.dart',
),
);
if (generatedMainDart != null && generatedMainDart.existsSync()) {
mainUri = generatedMainDart.uri;
}
}
final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
_controller.add(_RecompileRequest(completer, mainUri, invalidatedFiles,
outputPath, packageConfig, suppressErrors));
return completer.future;
}
Future<CompilerOutput?> _recompile(_RecompileRequest request) async {
_stdoutHandler.reset();
_compileRequestNeedsConfirmation = true;
_stdoutHandler._suppressCompilerMessages = request.suppressErrors;
final String mainUri =
request.packageConfig.toPackageUri(request.mainUri)?.toString() ??
toMultiRootPath(request.mainUri, fileSystemScheme, fileSystemRoots,
_platform.isWindows);
final Process? server = _server;
if (server == null) {
return _compile(mainUri, request.outputPath);
}
final String inputKey = Uuid().generateV4();
server.stdin.writeln('recompile $mainUri $inputKey');
_logger.printTrace('<- recompile $mainUri $inputKey');
final List<Uri>? invalidatedFiles = request.invalidatedFiles;
if (invalidatedFiles != null) {
for (final Uri fileUri in invalidatedFiles) {
String message;
if (fileUri.scheme == 'package') {
message = fileUri.toString();
} else {
message = request.packageConfig.toPackageUri(fileUri)?.toString() ??
toMultiRootPath(fileUri, fileSystemScheme, fileSystemRoots,
_platform.isWindows);
}
server.stdin.writeln(message);
_logger.printTrace(message);
}
}
server.stdin.writeln(inputKey);
_logger.printTrace('<- $inputKey');
return _stdoutHandler.compilerOutput?.future;
}
final List<_CompilationRequest> _compilationQueue = <_CompilationRequest>[];
Future<void> _handleCompilationRequest(_CompilationRequest request) async {
final bool isEmpty = _compilationQueue.isEmpty;
_compilationQueue.add(request);
// Only trigger processing if queue was empty - i.e. no other requests
// are currently being processed. This effectively enforces "one
// compilation request at a time".
if (isEmpty) {
while (_compilationQueue.isNotEmpty) {
final _CompilationRequest request = _compilationQueue.first;
await request.run(this);
_compilationQueue.removeAt(0);
}
}
}
Future<CompilerOutput?> _compile(
String scriptUri,
String? outputPath,
) async {
await AspectdHook.enableAspectd();
final String frontendServer = _artifacts
.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk);
final List<String> command = <String>[
_artifacts.getHostArtifact(HostArtifact.engineDartBinary).path,
'--disable-dart-dev',
frontendServer,
'--sdk-root',
sdkRoot,
'--incremental',
if (testCompilation) '--no-print-incremental-dependencies',
'--target=$targetModel',
// TODO(zanderso): remove once this becomes the default behavior
// in the frontend_server.
// https://github.com/flutter/flutter/issues/52693
'--debugger-module-names',
// TODO(annagrin): remove once this becomes the default behavior
// in the frontend_server.
// https://github.com/flutter/flutter/issues/59902
'--experimental-emit-debug-metadata',
for (final Object dartDefine in dartDefines) '-D$dartDefine',
if (outputPath != null) ...<String>[
'--output-dill',
outputPath,
],
if (librariesSpec != null) ...<String>[
'--libraries-spec',
librariesSpec!,
],
if (packagesPath != null) ...<String>[
'--packages',
packagesPath!,
],
...buildModeOptions(buildMode, dartDefines),
if (trackWidgetCreation) '--track-widget-creation',
if (fileSystemRoots != null)
for (final String root in fileSystemRoots) ...<String>[
'--filesystem-root',
root,
],
if (fileSystemScheme != null) ...<String>[
'--filesystem-scheme',
fileSystemScheme!,
],
if (initializeFromDill != null) ...<String>[
'--initialize-from-dill',
initializeFromDill!,
],
if (platformDill != null) ...<String>[
'--platform',
platformDill!,
],
if (unsafePackageSerialization == true) '--unsafe-package-serialization',
...?extraFrontEndOptions,
];
_logger.printTrace(command.join(' '));
_server = await _processManager.start(command);
_server?.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(_stdoutHandler.handler, onDone: () {
// when outputFilename future is not completed, but stdout is closed
// process has died unexpectedly.
if (_stdoutHandler.compilerOutput?.isCompleted == false) {
_stdoutHandler.compilerOutput?.complete(null);
throwToolExit('the Dart compiler exited unexpectedly.');
}
});
_server?.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(_logger.printError);
unawaited(_server?.exitCode.then((int code) {
if (code != 0) {
throwToolExit('the Dart compiler exited unexpectedly.');
}
}));
_server?.stdin.writeln('compile $scriptUri');
_logger.printTrace('<- compile $scriptUri');
return _stdoutHandler.compilerOutput?.future;
}
@override
Future<CompilerOutput?> compileExpression(
String expression,
List<String>? definitions,
List<String>? typeDefinitions,
String? libraryUri,
String? klass,
bool isStatic,
) async {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
final _CompileExpressionRequest request = _CompileExpressionRequest(
completer,
expression,
definitions,
typeDefinitions,
libraryUri,
klass,
isStatic);
_controller.add(request);
return completer.future;
}
Future<CompilerOutput?> _compileExpression(
_CompileExpressionRequest request) async {
_stdoutHandler.reset(
suppressCompilerMessages: true, expectSources: false, readFile: true);
// 'compile-expression' should be invoked after compiler has been started,
// program was compiled.
final Process? server = _server;
if (server == null) {
return null;
}
final String inputKey = Uuid().generateV4();
server.stdin
..writeln('compile-expression $inputKey')
..writeln(request.expression);
request.definitions?.forEach(server.stdin.writeln);
server.stdin.writeln(inputKey);
request.typeDefinitions?.forEach(server.stdin.writeln);
server.stdin
..writeln(inputKey)
..writeln(request.libraryUri ?? '')
..writeln(request.klass ?? '')
..writeln(request.isStatic);
return _stdoutHandler.compilerOutput?.future;
}
@override
Future<CompilerOutput?> compileExpressionToJs(
String libraryUri,
int line,
int column,
Map<String, String> jsModules,
Map<String, String> jsFrameValues,
String moduleName,
String expression,
) {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
_controller.add(_CompileExpressionToJsRequest(completer, libraryUri, line,
column, jsModules, jsFrameValues, moduleName, expression));
return completer.future;
}
Future<CompilerOutput?> _compileExpressionToJs(
_CompileExpressionToJsRequest request) async {
_stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false);
// 'compile-expression-to-js' should be invoked after compiler has been started,
// program was compiled.
final Process? server = _server;
if (server == null) {
return null;
}
final String inputKey = Uuid().generateV4();
server.stdin
..writeln('compile-expression-to-js $inputKey')
..writeln(request.libraryUri ?? '')
..writeln(request.line)
..writeln(request.column);
request.jsModules?.forEach((String k, String v) {
server.stdin.writeln('$k:$v');
});
server.stdin.writeln(inputKey);
request.jsFrameValues?.forEach((String k, String v) {
server.stdin.writeln('$k:$v');
});
server.stdin
..writeln(inputKey)
..writeln(request.moduleName ?? '')
..writeln(request.expression ?? '');
return _stdoutHandler.compilerOutput?.future;
}
@override
void accept() {
if (_compileRequestNeedsConfirmation) {
_server?.stdin.writeln('accept');
_logger.printTrace('<- accept');
}
_compileRequestNeedsConfirmation = false;
}
@override
Future<CompilerOutput?> reject() {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
_controller.add(_RejectRequest(completer));
return completer.future;
}
Future<CompilerOutput?> _reject() async {
if (!_compileRequestNeedsConfirmation) {
return Future<CompilerOutput?>.value();
}
_stdoutHandler.reset(expectSources: false);
_server?.stdin.writeln('reject');
_logger.printTrace('<- reject');
_compileRequestNeedsConfirmation = false;
return _stdoutHandler.compilerOutput?.future;
}
@override
void reset() {
_server?.stdin.writeln('reset');
_logger.printTrace('<- reset');
}
@override
Future<Object> shutdown() async {
// Server was never successfully created.
final Process? server = _server;
if (server == null) {
return 0;
}
_logger.printTrace('killing pid ${server.pid}');
server.kill();
return server.exitCode;
}
}
/// Convert a file URI into a multi-root scheme URI if provided, otherwise
/// return unmodified.
@visibleForTesting
String toMultiRootPath(
Uri fileUri, String? scheme, List<String> fileSystemRoots, bool windows) {
if (scheme == null || fileSystemRoots.isEmpty || fileUri.scheme != 'file') {
return fileUri.toString();
}
final String filePath = fileUri.toFilePath(windows: windows);
for (final String fileSystemRoot in fileSystemRoots) {
if (filePath.startsWith(fileSystemRoot)) {
return '$scheme://${filePath.substring(fileSystemRoot.length)}';
}
}
return fileUri.toString();
}
// @dart = 2.8
import 'dart:io';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/globals.dart';
/// 创建时间:2020-03-28
/// 作者:liujingguang
/// 描述:Hook处理工厂
enum CommandType4Aop { Bundle, Aot, Snapshot }
class HookFactory {
static Future<void> hook(String productDirPath, String executorPath,
BuildMode buildMode, CommandType4Aop type) async {
if (productDirPath == null ||
executorPath == null ||
!fs.file(executorPath).existsSync()) {
return;
}
String inputPath;
switch (type) {
case CommandType4Aop.Bundle:
inputPath = _getBundleInputPath(productDirPath);
break;
case CommandType4Aop.Aot:
inputPath = _getAotInputPath(productDirPath);
break;
case CommandType4Aop.Snapshot:
inputPath = _getSnapShotInputPath(productDirPath);
break;
}
if (!inputPath.startsWith(fs.currentDirectory.path)) {
inputPath = fs.path.join(fs.currentDirectory.path, inputPath);
}
if (!fs.file(inputPath).existsSync()) {
return;
}
final String outputPath =
inputPath + '.${type.toString().toLowerCase()}.result.dill';
/// 执行hook命令
final List<String> command = <String>[
artifacts
.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk),
executorPath,
'--input',
inputPath,
'--output',
outputPath,
if (buildMode != BuildMode.release) ...<String>[
'--sdk-root',
fs
.file(artifacts.getArtifactPath(Artifact.platformKernelDill))
.parent
.path +
fs.path.separator
],
];
print(command.toString());
final ProcessResult result = await processManager.run(command);
if (result.exitCode != 0) {
print(result.stderr);
throwToolExit(
'hook by aop terminated unexpectedly in ${type.toString()}.');
return;
}
print('aop hook succeed');
/// 删除input输入文件
final File inputFile = fs.file(inputPath);
if (inputFile.existsSync()) {
// inputFile.copySync(inputPath + '.old'); //调试时可以打开查看信息
inputFile.deleteSync();
}
/// 将Aop处理生成后的output文件重命名为input文件名
fs.file(outputPath).renameSync(inputPath);
}
static String _getBundleInputPath(String assetsDir) =>
fs.path.join(assetsDir, 'kernel_blob.bin');
static String _getAotInputPath(String path) =>
fs.path.join(path ?? getAotBuildDirectory(), 'app.dill');
static String _getSnapShotInputPath(String path) => path;
}
// 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 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:package_config/package_config.dart';
import '../../artifacts.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../build_info.dart';
import '../../cache.dart';
import '../../convert.dart';
import '../../dart/language_version.dart';
import '../../dart/package_map.dart';
import '../../globals.dart' as globals;
import '../../project.dart';
import '../build_system.dart';
import '../depfile.dart';
import '../exceptions.dart';
import 'assets.dart';
import 'localizations.dart';
import '../../aop/aop_manager.dart';
/// Whether the application has web plugins.
const String kHasWebPlugins = 'HasWebPlugins';
/// An override for the dart2js build mode.
///
/// Valid values are O1 (lowest, profile default) to O4 (highest, release default).
const String kDart2jsOptimization = 'Dart2jsOptimization';
/// Whether to disable dynamic generation code to satisfy csp policies.
const String kCspMode = 'cspMode';
/// Base href to set in index.html in flutter build command
const String kBaseHref = 'baseHref';
/// Placeholder for base href
const String kBaseHrefPlaceholder = r'$FLUTTER_BASE_HREF';
/// The caching strategy to use for service worker generation.
const String kServiceWorkerStrategy = 'ServiceWorkerStrategy';
/// Whether the dart2js build should output source maps.
const String kSourceMapsEnabled = 'SourceMaps';
/// Whether the dart2js native null assertions are enabled.
const String kNativeNullAssertions = 'NativeNullAssertions';
/// The caching strategy for the generated service worker.
enum ServiceWorkerStrategy {
/// Download the app shell eagerly and all other assets lazily.
/// Prefer the offline cached version.
offlineFirst,
/// Do not generate a service worker,
none,
}
const String kOfflineFirst = 'offline-first';
const String kNoneWorker = 'none';
/// Convert a [value] into a [ServiceWorkerStrategy].
ServiceWorkerStrategy _serviceWorkerStrategyFromString(String? value) {
switch (value) {
case kNoneWorker:
return ServiceWorkerStrategy.none;
// offline-first is the default value for any invalid requests.
default:
return ServiceWorkerStrategy.offlineFirst;
}
}
/// Generates an entry point for a web target.
// Keep this in sync with build_runner/resident_web_runner.dart
class WebEntrypointTarget extends Target {
const WebEntrypointTarget();
@override
String get name => 'web_entrypoint';
@override
List<Target> get dependencies => const <Target>[];
@override
List<Source> get inputs => const <Source>[
Source.pattern(
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/web.dart'),
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{BUILD_DIR}/main.dart'),
];
@override
Future<void> build(Environment environment) async {
final String? targetFile = environment.defines[kTargetFile];
final bool hasPlugins = environment.defines[kHasWebPlugins] == 'true';
final Uri importUri = environment.fileSystem.file(targetFile).absolute.uri;
// TODO(zanderso): support configuration of this file.
const String packageFile = '.packages';
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
environment.fileSystem.file(packageFile),
logger: environment.logger,
);
final FlutterProject flutterProject = FlutterProject.current();
final LanguageVersion languageVersion = determineLanguageVersion(
environment.fileSystem.file(targetFile),
packageConfig[flutterProject.manifest.appName],
Cache.flutterRoot!,
);
// Use the PackageConfig to find the correct package-scheme import path
// for the user application. If the application has a mix of package-scheme
// and relative imports for a library, then importing the entrypoint as a
// file-scheme will cause said library to be recognized as two distinct
// libraries. This can cause surprising behavior as types from that library
// will be considered distinct from each other.
// By construction, this will only be null if the .packages file does not
// have an entry for the user's application or if the main file is
// outside of the lib/ directory.
final String mainImport =
packageConfig.toPackageUri(importUri)?.toString() ??
importUri.toString();
String contents;
if (hasPlugins) {
final Uri generatedUri = environment.projectDir
.childDirectory('lib')
.childFile('generated_plugin_registrant.dart')
.absolute
.uri;
final String generatedImport =
packageConfig.toPackageUri(generatedUri)?.toString() ??
generatedUri.toString();
contents = '''
// @dart=${languageVersion.major}.${languageVersion.minor}
import 'dart:ui' as ui;
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import '$generatedImport';
import '$mainImport' as entrypoint;
Future<void> main() async {
registerPlugins(webPluginRegistrar);
await ui.webOnlyInitializePlatform();
entrypoint.main();
}
''';
} else {
contents = '''
// @dart=${languageVersion.major}.${languageVersion.minor}
import 'dart:ui' as ui;
import '$mainImport' as entrypoint;
Future<void> main() async {
await ui.webOnlyInitializePlatform();
entrypoint.main();
}
''';
}
environment.buildDir.childFile('main.dart').writeAsStringSync(contents);
}
}
/// Compiles a web entry point with dart2js.
class Dart2JSTarget extends Target {
const Dart2JSTarget();
@override
String get name => 'dart2js';
@override
List<Target> get dependencies => const <Target>[
WebEntrypointTarget(),
GenerateLocalizationsTarget(),
];
@override
List<Source> get inputs => const <Source>[
Source.hostArtifact(HostArtifact.flutterWebSdk),
Source.hostArtifact(HostArtifact.dart2jsSnapshot),
Source.hostArtifact(HostArtifact.engineDartBinary),
Source.pattern('{BUILD_DIR}/main.dart'),
Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'),
];
@override
List<Source> get outputs => const <Source>[];
@override
List<String> get depfiles => const <String>[
'dart2js.d',
];
String _collectOutput(ProcessResult result) {
final String stdout = result.stdout is List<int>
? utf8.decode(result.stdout as List<int>)
: result.stdout as String;
final String stderr = result.stderr is List<int>
? utf8.decode(result.stderr as List<int>)
: result.stderr as String;
return stdout + stderr;
}
@override
Future<void> build(Environment environment) async {
final String? buildModeEnvironment = environment.defines[kBuildMode];
if (buildModeEnvironment == null) {
throw MissingDefineException(kBuildMode, name);
}
final BuildMode buildMode = getBuildModeForName(buildModeEnvironment);
final bool sourceMapsEnabled =
environment.defines[kSourceMapsEnabled] == 'true';
final bool nativeNullAssertions =
environment.defines[kNativeNullAssertions] == 'true';
final Artifacts artifacts = globals.artifacts!;
final String librariesSpec =
(artifacts.getHostArtifact(HostArtifact.flutterWebSdk) as Directory)
.childFile('libraries.json')
.path;
final List<String> sharedCommandOptions = <String>[
artifacts.getHostArtifact(HostArtifact.engineDartBinary).path,
'--disable-dart-dev',
artifacts.getHostArtifact(HostArtifact.dart2jsSnapshot).path,
'--libraries-spec=$librariesSpec',
...decodeCommaSeparated(environment.defines, kExtraFrontEndOptions),
if (nativeNullAssertions) '--native-null-assertions',
if (buildMode == BuildMode.profile)
'-Ddart.vm.profile=true'
else
'-Ddart.vm.product=true',
for (final String dartDefine
in decodeDartDefines(environment.defines, kDartDefines))
'-D$dartDefine',
if (!sourceMapsEnabled) '--no-source-maps',
];
// Run the dart2js compilation in two stages, so that icon tree shaking can
// parse the kernel file for web builds.
final ProcessResult kernelResult =
await globals.processManager.run(<String>[
...sharedCommandOptions,
'-o',
environment.buildDir.childFile('app.dill').path,
'--packages=.packages',
'--cfe-only',
environment.buildDir.childFile('main.dart').path, // dartfile
]);
if (kernelResult.exitCode != 0) {
throw Exception(_collectOutput(kernelResult));
}
await AopManager.hookSnapshotCommand(
environment.buildDir.childFile('app.dill').path, buildMode);
final String? dart2jsOptimization =
environment.defines[kDart2jsOptimization];
final File outputJSFile = environment.buildDir.childFile('main.dart.js');
final bool csp = environment.defines[kCspMode] == 'true';
final ProcessResult javaScriptResult =
await environment.processManager.run(<String>[
...sharedCommandOptions,
if (dart2jsOptimization != null) '-$dart2jsOptimization' else '-O4',
if (buildMode == BuildMode.profile) '--no-minify',
if (csp) '--csp',
'-o',
outputJSFile.path,
environment.buildDir.childFile('app.dill').path, // dartfile
]);
if (javaScriptResult.exitCode != 0) {
throw Exception(_collectOutput(javaScriptResult));
}
final File dart2jsDeps = environment.buildDir.childFile('app.dill.deps');
if (!dart2jsDeps.existsSync()) {
globals.printWarning(
'Warning: dart2js did not produced expected deps list at '
'${dart2jsDeps.path}');
return;
}
final DepfileService depfileService = DepfileService(
fileSystem: globals.fs,
logger: globals.logger,
);
final Depfile depfile = depfileService.parseDart2js(
environment.buildDir.childFile('app.dill.deps'),
outputJSFile,
);
depfileService.writeToFile(
depfile,
environment.buildDir.childFile('dart2js.d'),
);
}
}
/// Unpacks the dart2js compilation and resources to a given output directory.
class WebReleaseBundle extends Target {
const WebReleaseBundle();
@override
String get name => 'web_release_bundle';
@override
List<Target> get dependencies => const <Target>[
Dart2JSTarget(),
];
@override
List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/main.dart.js'),
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{OUTPUT_DIR}/main.dart.js'),
];
@override
List<String> get depfiles => const <String>[
'dart2js.d',
'flutter_assets.d',
'web_resources.d',
];
@override
Future<void> build(Environment environment) async {
for (final File outputFile
in environment.buildDir.listSync(recursive: true).whereType<File>()) {
final String basename = globals.fs.path.basename(outputFile.path);
if (!basename.contains('main.dart.js')) {
continue;
}
// Do not copy the deps file.
if (basename.endsWith('.deps')) {
continue;
}
outputFile.copySync(environment.outputDir
.childFile(globals.fs.path.basename(outputFile.path))
.path);
}
final String versionInfo = FlutterProject.current().getVersionInfo();
environment.outputDir
.childFile('version.json')
.writeAsStringSync(versionInfo);
final Directory outputDirectory =
environment.outputDir.childDirectory('assets');
outputDirectory.createSync(recursive: true);
final Depfile depfile = await copyAssets(
environment,
environment.outputDir.childDirectory('assets'),
targetPlatform: TargetPlatform.web_javascript,
);
final DepfileService depfileService = DepfileService(
fileSystem: globals.fs,
logger: globals.logger,
);
depfileService.writeToFile(
depfile,
environment.buildDir.childFile('flutter_assets.d'),
);
final Directory webResources = environment.projectDir.childDirectory('web');
final List<File> inputResourceFiles =
webResources.listSync(recursive: true).whereType<File>().toList();
// Copy other resource files out of web/ directory.
final List<File> outputResourcesFiles = <File>[];
for (final File inputFile in inputResourceFiles) {
final File outputFile = globals.fs.file(globals.fs.path.join(
environment.outputDir.path,
globals.fs.path.relative(inputFile.path, from: webResources.path)));
if (!outputFile.parent.existsSync()) {
outputFile.parent.createSync(recursive: true);
}
outputResourcesFiles.add(outputFile);
// insert a random hash into the requests for service_worker.js. This is not a content hash,
// because it would need to be the hash for the entire bundle and not just the resource
// in question.
if (environment.fileSystem.path.basename(inputFile.path) ==
'index.html') {
final String randomHash = Random().nextInt(4294967296).toString();
String resultString = inputFile
.readAsStringSync()
.replaceFirst(
'var serviceWorkerVersion = null',
"var serviceWorkerVersion = '$randomHash'",
)
// This is for legacy index.html that still use the old service
// worker loading mechanism.
.replaceFirst(
"navigator.serviceWorker.register('flutter_service_worker.js')",
"navigator.serviceWorker.register('flutter_service_worker.js?v=$randomHash')",
);
final String? baseHref = environment.defines[kBaseHref];
if (resultString.contains(kBaseHrefPlaceholder) && baseHref == null) {
resultString = resultString.replaceAll(kBaseHrefPlaceholder, '/');
} else if (resultString.contains(kBaseHrefPlaceholder) &&
baseHref != null) {
resultString =
resultString.replaceAll(kBaseHrefPlaceholder, baseHref);
}
outputFile.writeAsStringSync(resultString);
continue;
}
inputFile.copySync(outputFile.path);
}
final Depfile resourceFile =
Depfile(inputResourceFiles, outputResourcesFiles);
depfileService.writeToFile(
resourceFile,
environment.buildDir.childFile('web_resources.d'),
);
}
}
/// Static assets provided by the Flutter SDK that do not change, such as
/// CanvasKit.
///
/// These assets can be cached forever and are only invalidated when the
/// Flutter SDK is upgraded to a new version.
class WebBuiltInAssets extends Target {
const WebBuiltInAssets(this.fileSystem, this.cache);
final FileSystem fileSystem;
final Cache cache;
@override
String get name => 'web_static_assets';
@override
List<Target> get dependencies => const <Target>[];
@override
List<String> get depfiles => const <String>[];
@override
List<Source> get inputs => const <Source>[];
@override
List<Source> get outputs => const <Source>[];
@override
Future<void> build(Environment environment) async {
// TODO(yjbanov): https://github.com/flutter/flutter/issues/52588
//
// Update this when we start building CanvasKit from sources. In the
// meantime, get the Web SDK directory from cache rather than through
// Artifacts. The latter is sensitive to `--local-engine`, which changes
// the directory to point to ENGINE/src/out. However, CanvasKit is not yet
// built as part of the engine, but fetched from CIPD, and so it won't be
// found in ENGINE/src/out.
final Directory flutterWebSdk = cache.getWebSdkDirectory();
final Directory canvasKitDirectory =
flutterWebSdk.childDirectory('canvaskit');
for (final File file
in canvasKitDirectory.listSync(recursive: true).whereType<File>()) {
final String relativePath =
fileSystem.path.relative(file.path, from: canvasKitDirectory.path);
final String targetPath = fileSystem.path
.join(environment.outputDir.path, 'canvaskit', relativePath);
file.copySync(targetPath);
}
}
}
/// Generate a service worker for a web target.
class WebServiceWorker extends Target {
const WebServiceWorker(this.fileSystem, this.cache);
final FileSystem fileSystem;
final Cache cache;
@override
String get name => 'web_service_worker';
@override
List<Target> get dependencies => <Target>[
const Dart2JSTarget(),
const WebReleaseBundle(),
WebBuiltInAssets(fileSystem, cache),
];
@override
List<String> get depfiles => const <String>[
'service_worker.d',
];
@override
List<Source> get inputs => const <Source>[];
@override
List<Source> get outputs => const <Source>[];
@override
Future<void> build(Environment environment) async {
final List<File> contents = environment.outputDir
.listSync(recursive: true)
.whereType<File>()
.where((File file) =>
!file.path.endsWith('flutter_service_worker.js') &&
!globals.fs.path.basename(file.path).startsWith('.'))
.toList();
final Map<String, String> urlToHash = <String, String>{};
for (final File file in contents) {
// Do not force caching of source maps.
if (file.path.endsWith('main.dart.js.map') ||
file.path.endsWith('.part.js.map')) {
continue;
}
final String url = globals.fs.path
.toUri(
globals.fs.path
.relative(file.path, from: environment.outputDir.path),
)
.toString();
final String hash = md5.convert(await file.readAsBytes()).toString();
urlToHash[url] = hash;
// Add an additional entry for the base URL.
if (globals.fs.path.basename(url) == 'index.html') {
urlToHash['/'] = hash;
}
}
final File serviceWorkerFile =
environment.outputDir.childFile('flutter_service_worker.js');
final Depfile depfile = Depfile(contents, <File>[serviceWorkerFile]);
final ServiceWorkerStrategy serviceWorkerStrategy =
_serviceWorkerStrategyFromString(
environment.defines[kServiceWorkerStrategy],
);
final String serviceWorker = generateServiceWorker(
urlToHash,
<String>[
'/',
'main.dart.js',
'index.html',
'assets/NOTICES',
if (urlToHash.containsKey('assets/AssetManifest.json'))
'assets/AssetManifest.json',
if (urlToHash.containsKey('assets/FontManifest.json'))
'assets/FontManifest.json',
],
serviceWorkerStrategy: serviceWorkerStrategy,
);
serviceWorkerFile.writeAsStringSync(serviceWorker);
final DepfileService depfileService = DepfileService(
fileSystem: globals.fs,
logger: globals.logger,
);
depfileService.writeToFile(
depfile,
environment.buildDir.childFile('service_worker.d'),
);
}
}
/// Generate a service worker with an app-specific cache name a map of
/// resource files.
///
/// The tool embeds file hashes directly into the worker so that the byte for byte
/// invalidation will automatically reactivate workers whenever a new
/// version is deployed.
String generateServiceWorker(
Map<String, String> resources,
List<String> coreBundle, {
required ServiceWorkerStrategy serviceWorkerStrategy,
}) {
if (serviceWorkerStrategy == ServiceWorkerStrategy.none) {
return '';
}
return '''
'use strict';
const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
${resources.entries.map((MapEntry<String, String> entry) => '"${entry.key}": "${entry.value}"').join(",\n")}
};
// The application shell files that are downloaded before a service worker can
// start.
const CORE = [
${coreBundle.map((String file) => '"$file"').join(',\n')}];
// During install, the TEMP cache is populated with the application shell files.
self.addEventListener("install", (event) => {
self.skipWaiting();
return event.waitUntil(
caches.open(TEMP).then((cache) => {
return cache.addAll(
CORE.map((value) => new Request(value, {'cache': 'reload'})));
})
);
});
// During activate, the cache is populated with the temp files downloaded in
// install. If this service worker is upgrading from one with a saved
// MANIFEST, then use this to retain unchanged resource files.
self.addEventListener("activate", function(event) {
return event.waitUntil(async function() {
try {
var contentCache = await caches.open(CACHE_NAME);
var tempCache = await caches.open(TEMP);
var manifestCache = await caches.open(MANIFEST);
var manifest = await manifestCache.match('manifest');
// When there is no prior manifest, clear the entire cache.
if (!manifest) {
await caches.delete(CACHE_NAME);
contentCache = await caches.open(CACHE_NAME);
for (var request of await tempCache.keys()) {
var response = await tempCache.match(request);
await contentCache.put(request, response);
}
await caches.delete(TEMP);
// Save the manifest to make future upgrades efficient.
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
return;
}
var oldManifest = await manifest.json();
var origin = self.location.origin;
for (var request of await contentCache.keys()) {
var key = request.url.substring(origin.length + 1);
if (key == "") {
key = "/";
}
// If a resource from the old manifest is not in the new cache, or if
// the MD5 sum has changed, delete it. Otherwise the resource is left
// in the cache and can be reused by the new service worker.
if (!RESOURCES[key] || RESOURCES[key] != oldManifest[key]) {
await contentCache.delete(request);
}
}
// Populate the cache with the app shell TEMP files, potentially overwriting
// cache files preserved above.
for (var request of await tempCache.keys()) {
var response = await tempCache.match(request);
await contentCache.put(request, response);
}
await caches.delete(TEMP);
// Save the manifest to make future upgrades efficient.
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
return;
} catch (err) {
// On an unhandled exception the state of the cache cannot be guaranteed.
console.error('Failed to upgrade service worker: ' + err);
await caches.delete(CACHE_NAME);
await caches.delete(TEMP);
await caches.delete(MANIFEST);
}
}());
});
// The fetch handler redirects requests for RESOURCE files to the service
// worker cache.
self.addEventListener("fetch", (event) => {
if (event.request.method !== 'GET') {
return;
}
var origin = self.location.origin;
var key = event.request.url.substring(origin.length + 1);
// Redirect URLs to the index.html
if (key.indexOf('?v=') != -1) {
key = key.split('?v=')[0];
}
if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') {
key = '/';
}
// If the URL is not the RESOURCE list then return to signal that the
// browser should take over.
if (!RESOURCES[key]) {
return;
}
// If the URL is the index.html, perform an online-first request.
if (key == '/') {
return onlineFirst(event);
}
event.respondWith(caches.open(CACHE_NAME)
.then((cache) => {
return cache.match(event.request).then((response) => {
// Either respond with the cached resource, or perform a fetch and
// lazily populate the cache.
return response || fetch(event.request).then((response) => {
cache.put(event.request, response.clone());
return response;
});
})
})
);
});
self.addEventListener('message', (event) => {
// SkipWaiting can be used to immediately activate a waiting service worker.
// This will also require a page refresh triggered by the main worker.
if (event.data === 'skipWaiting') {
self.skipWaiting();
return;
}
if (event.data === 'downloadOffline') {
downloadOffline();
return;
}
});
// Download offline will check the RESOURCES for all files not in the cache
// and populate them.
async function downloadOffline() {
var resources = [];
var contentCache = await caches.open(CACHE_NAME);
var currentContent = {};
for (var request of await contentCache.keys()) {
var key = request.url.substring(origin.length + 1);
if (key == "") {
key = "/";
}
currentContent[key] = true;
}
for (var resourceKey of Object.keys(RESOURCES)) {
if (!currentContent[resourceKey]) {
resources.push(resourceKey);
}
}
return contentCache.addAll(resources);
}
// Attempt to download the resource online before falling back to
// the offline cache.
function onlineFirst(event) {
return event.respondWith(
fetch(event.request).then((response) => {
return caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, response.clone());
return response;
});
}).catch((error) => {
return caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((response) => {
if (response != null) {
return response;
}
throw error;
});
});
})
);
}
''';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment