// Copyright 2019 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. import 'package:pool/pool.dart'; import '../../asset.dart'; import '../../base/file_system.dart'; import '../../devfs.dart'; import '../../plugins.dart'; import '../../project.dart'; import '../build_system.dart'; /// The copying logic for flutter assets. class AssetBehavior extends SourceBehavior { const AssetBehavior(); @override List<File> inputs(Environment environment) { final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); assetBundle.build( manifestPath: environment.projectDir.childFile('pubspec.yaml').path, packagesPath: environment.projectDir.childFile('.packages').path, ); // Filter the file type to remove the files that are generated by this // command as inputs. final List<File> results = <File>[]; final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>(); for (DevFSFileContent devFsContent in files) { results.add(fs.file(devFsContent.file.path)); } return results; } @override List<File> outputs(Environment environment) { final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); assetBundle.build( manifestPath: environment.projectDir.childFile('pubspec.yaml').path, packagesPath: environment.projectDir.childFile('.packages').path, ); final List<File> results = <File>[]; for (String key in assetBundle.entries.keys) { final File file = fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', key)); results.add(file); } return results; } } /// A specific asset behavior for building bundles. class AssetOutputBehavior extends SourceBehavior { const AssetOutputBehavior([this._pathSuffix = '']); final String _pathSuffix; @override List<File> inputs(Environment environment) { final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); assetBundle.build( manifestPath: environment.projectDir.childFile('pubspec.yaml').path, packagesPath: environment.projectDir.childFile('.packages').path, ); // Filter the file type to remove the files that are generated by this // command as inputs. final List<File> results = <File>[]; final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>(); for (DevFSFileContent devFsContent in files) { results.add(fs.file(fs.path.join(_pathSuffix, devFsContent.file.path))); } return results; } @override List<File> outputs(Environment environment) { final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); assetBundle.build( manifestPath: environment.projectDir.childFile('pubspec.yaml').path, packagesPath: environment.projectDir.childFile('.packages').path, ); final List<File> results = <File>[]; for (String key in assetBundle.entries.keys) { final File file = fs.file(fs.path.join(environment.outputDir.path, _pathSuffix, key)); results.add(file); } return results; } } /// Copy the assets defined in the flutter manifest into a build directory. class CopyAssets extends Target { const CopyAssets(); @override String get name => 'copy_assets'; @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/assets.dart'), Source.pattern('{PROJECT_DIR}/pubspec.yaml'), Source.behavior(AssetBehavior()), ]; @override List<Source> get outputs => const <Source>[ Source.pattern('{BUILD_DIR}/flutter_assets/AssetManifest.json'), Source.pattern('{BUILD_DIR}/flutter_assets/FontManifest.json'), Source.pattern('{BUILD_DIR}/flutter_assets/LICENSE'), Source.behavior(AssetBehavior()), // <- everything in this subdirectory. ]; @override Future<void> build(Environment environment) async { final Directory output = environment .buildDir .childDirectory('flutter_assets'); if (output.existsSync()) { output.deleteSync(recursive: true); } output.createSync(recursive: true); final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); await assetBundle.build( manifestPath: environment.projectDir.childFile('pubspec.yaml').path, packagesPath: environment.projectDir.childFile('.packages').path, ); // Limit number of open files to avoid running out of file descriptors. final Pool pool = Pool(kMaxOpenFiles); await Future.wait<void>( assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async { final PoolResource resource = await pool.request(); try { final File file = fs.file(fs.path.join(output.path, entry.key)); file.parent.createSync(recursive: true); await file.writeAsBytes(await entry.value.contentsAsBytes()); } finally { resource.release(); } })); } } /// Rewrites the `.flutter-plugins` file of [project] based on the plugin /// dependencies declared in `pubspec.yaml`. // TODO(jonahwiliams): this should be per platform and located in build // outputs. class FlutterPlugins extends Target { const FlutterPlugins(); @override String get name => 'flutter_plugins'; @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/assets.dart'), Source.pattern('{PROJECT_DIR}/pubspec.yaml'), ]; @override List<Source> get outputs => const <Source>[ Source.pattern('{PROJECT_DIR}/.flutter-plugins'), ]; @override Future<void> build(Environment environment) async { // The pubspec may change for reasons other than plugins changing, so we compare // the manifest before writing. Some hosting build systems use timestamps // so we need to be careful to avoid tricking them into doing more work than // necessary. final FlutterProject project = FlutterProject.fromDirectory(environment.projectDir); final List<Plugin> plugins = findPlugins(project); final String pluginManifest = plugins .map<String>((Plugin p) => '${p.name}=${escapePath(p.path)}') .join('\n'); final File flutterPluginsFile = environment.projectDir.childFile('.flutter-plugins'); if (!flutterPluginsFile.existsSync() || flutterPluginsFile.readAsStringSync() != pluginManifest) { flutterPluginsFile.writeAsStringSync(pluginManifest); } } }