From 06ac0422713fe7d85dc922860e9997d5c6c3c6a0 Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Thu, 7 Mar 2024 15:38:40 -0800 Subject: [PATCH] Enable asset transformation for `flutter run -d ` and `flutter test` (#144734) Partial implementation of https://github.com/flutter/flutter/issues/143348. The title says it all. Feel free to experiment with the feature using this project: https://github.com/andrewkolos/asset_transformers_test. --- .../flutter_tools/lib/src/bundle_builder.dart | 22 ++++- .../flutter_tools/lib/src/commands/test.dart | 1 + .../lib/src/isolated/devfs_web.dart | 1 + .../test/general.shard/asset_bundle_test.dart | 4 + .../general.shard/bundle_builder_test.dart | 87 ++++++++++++++++++- 5 files changed, 113 insertions(+), 2 deletions(-) diff --git a/packages/flutter_tools/lib/src/bundle_builder.dart b/packages/flutter_tools/lib/src/bundle_builder.dart index de20fff10ae..81f3872a0f9 100644 --- a/packages/flutter_tools/lib/src/bundle_builder.dart +++ b/packages/flutter_tools/lib/src/bundle_builder.dart @@ -14,6 +14,7 @@ import 'base/logger.dart'; import 'build_info.dart'; import 'build_system/build_system.dart'; import 'build_system/depfile.dart'; +import 'build_system/tools/asset_transformer.dart'; import 'build_system/tools/scene_importer.dart'; import 'build_system/tools/shader_compiler.dart'; import 'bundle.dart'; @@ -145,6 +146,7 @@ Future writeBundle( required FileSystem fileSystem, required Artifacts artifacts, required Logger logger, + required Directory projectDir, }) async { if (bundleDir.existsSync()) { try { @@ -172,6 +174,12 @@ Future writeBundle( artifacts: artifacts, ); + final AssetTransformer assetTransformer = AssetTransformer( + processManager: processManager, + fileSystem: fileSystem, + dartBinaryPath: artifacts.getArtifactPath(Artifact.engineDartBinary), + ); + // Limit number of open files to avoid running out of file descriptors. final Pool pool = Pool(64); await Future.wait( @@ -190,8 +198,20 @@ Future writeBundle( final File input = devFSContent.file as File; bool doCopy = true; switch (entry.value.kind) { - case AssetKind.regular: + case AssetKind.regular: + if (entry.value.transformers.isEmpty) { break; + } + final AssetTransformationFailure? failure = await assetTransformer.transformAsset( + asset: input, + outputPath: file.path, + workingDirectory: projectDir.path, + transformerEntries: entry.value.transformers, + ); + doCopy = false; + if (failure != null) { + throwToolExit(failure.message); + } case AssetKind.font: break; case AssetKind.shader: diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 1dabcfe87d1..3a813e00498 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -675,6 +675,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { fileSystem: globals.fs, artifacts: globals.artifacts!, logger: globals.logger, + projectDir: globals.fs.currentDirectory, ); } } diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index 5faab41524c..9fbd859f318 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -946,6 +946,7 @@ class WebDevFS implements DevFS { fileSystem: globals.fs, artifacts: globals.artifacts!, logger: globals.logger, + projectDir: rootDirectory, ); } } diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart index 72fa0f06464..e3114aa511d 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart @@ -543,6 +543,7 @@ flutter: fileSystem: globals.fs, artifacts: globals.artifacts!, logger: testLogger, + projectDir: globals.fs.currentDirectory ); expect(testLogger.warningText, contains('Expected Error Text')); @@ -668,6 +669,7 @@ flutter: fileSystem: globals.fs, artifacts: globals.artifacts!, logger: testLogger, + projectDir: globals.fs.currentDirectory, ); }, overrides: { @@ -719,6 +721,7 @@ flutter: fileSystem: globals.fs, artifacts: globals.artifacts!, logger: testLogger, + projectDir: globals.fs.currentDirectory, ); }, overrides: { @@ -805,6 +808,7 @@ flutter: fileSystem: globals.fs, artifacts: globals.artifacts!, logger: testLogger, + projectDir: globals.fs.currentDirectory, ); expect((globals.processManager as FakeProcessManager).hasRemainingExpectations, false); }, overrides: { diff --git a/packages/flutter_tools/test/general.shard/bundle_builder_test.dart b/packages/flutter_tools/test/general.shard/bundle_builder_test.dart index fb854f24c71..687171a3e3d 100644 --- a/packages/flutter_tools/test/general.shard/bundle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/bundle_builder_test.dart @@ -2,15 +2,26 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; + +import 'package:args/args.dart'; import 'package:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/asset.dart'; import 'package:flutter_tools/src/base/config.dart'; import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; -import 'package:flutter_tools/src/bundle.dart'; +import 'package:flutter_tools/src/bundle.dart' hide defaultManifestPath; import 'package:flutter_tools/src/bundle_builder.dart'; +import 'package:flutter_tools/src/devfs.dart'; +import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; +import 'package:test/fake.dart'; import '../src/common.dart'; import '../src/context.dart'; @@ -46,6 +57,75 @@ void main() { ProcessManager: () => FakeProcessManager.any(), }); + testWithoutContext('writeBundle applies transformations to any assets that have them defined', () async { + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + final File asset = fileSystem.file('my-asset.txt') + ..createSync() + ..writeAsBytesSync([1, 2, 3]); + final Artifacts artifacts = Artifacts.test(); + + final FakeProcessManager processManager = FakeProcessManager.list( + [ + FakeCommand( + command: [ + artifacts.getArtifactPath(Artifact.engineDartBinary), + 'run', + 'increment', + '--input=/.tmp_rand0/my-asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/my-asset.txt-transformOutput1.txt' + ], + onRun: (List command) { + final ArgResults argParseResults = (ArgParser() + ..addOption('input', mandatory: true) + ..addOption('output', mandatory: true)) + .parse(command); + + final File inputFile = fileSystem.file(argParseResults['input']); + final File outputFile = fileSystem.file(argParseResults['output']); + + expect(inputFile, exists); + outputFile + ..createSync() + ..writeAsBytesSync( + Uint8List.fromList( + inputFile.readAsBytesSync().map((int b) => b + 1).toList(), + ), + ); + }, + ), + ], + ); + + final FakeAssetBundle bundle = FakeAssetBundle() + ..entries['my-asset.txt'] = AssetBundleEntry( + DevFSFileContent(asset), + kind: AssetKind.regular, + transformers: const [ + AssetTransformerEntry(package: 'increment', args: []), + ], + ); + + final Directory bundleDir = fileSystem.directory( + getAssetBuildDirectory(Config.test(), fileSystem), + ); + + await writeBundle( + bundleDir, + bundle.entries, + targetPlatform: TargetPlatform.tester, + impellerStatus: ImpellerStatus.platformDefault, + processManager: processManager, + fileSystem: fileSystem, + artifacts: artifacts, + logger: BufferLogger.test(), + projectDir: fileSystem.currentDirectory, + ); + + final File outputAssetFile = fileSystem.file('build/flutter_assets/my-asset.txt'); + expect(outputAssetFile, exists); + expect(outputAssetFile.readAsBytesSync(), orderedEquals([2, 3, 4])); + }); + testUsingContext('Handles build system failure', () { expect( () => BundleBuilder().build( @@ -157,3 +237,8 @@ void main() { ), 'build/95b595cca01caa5f0ca0a690339dd7f6.cache.dill.track.dill'); }); } + +class FakeAssetBundle extends Fake implements AssetBundle { + @override + final Map entries = {}; +}