// 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:file/memory.dart'; import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/deferred_component.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/build_system/depfile.dart'; import 'package:flutter_tools/src/build_system/targets/android.dart'; import 'package:flutter_tools/src/convert.dart'; import '../../../src/common.dart'; import '../../../src/context.dart'; import '../../../src/fake_process_manager.dart'; void main() { late FakeProcessManager processManager; late FileSystem fileSystem; late Artifacts artifacts; late Logger logger; setUp(() { logger = BufferLogger.test(); fileSystem = MemoryFileSystem.test(); processManager = FakeProcessManager.empty(); artifacts = Artifacts.test(); }); testWithoutContext('Android AOT targets has analyticsName', () { expect(androidArmProfile.analyticsName, 'android_aot'); }); testUsingContext('debug bundle contains expected resources', () async { final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'debug', }, processManager: processManager, artifacts: artifacts, fileSystem: fileSystem, logger: logger, ); environment.buildDir.createSync(recursive: true); // create pre-requisites. environment.buildDir.childFile('app.dill') .writeAsStringSync('abcd'); fileSystem .file(artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug)) .createSync(recursive: true); fileSystem .file(artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug)) .createSync(recursive: true); await const DebugAndroidApplication().build(environment); expect(fileSystem.file(fileSystem.path.join('out', 'flutter_assets', 'isolate_snapshot_data')).existsSync(), true); expect(fileSystem.file(fileSystem.path.join('out', 'flutter_assets', 'vm_snapshot_data')).existsSync(), true); expect(fileSystem.file(fileSystem.path.join('out', 'flutter_assets', 'kernel_blob.bin')).existsSync(), true); }); testUsingContext('debug bundle contains expected resources with bundle SkSL', () async { final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'debug', }, inputs: <String, String>{ kBundleSkSLPath: 'bundle.sksl', }, processManager: processManager, artifacts: artifacts, fileSystem: fileSystem, logger: logger, engineVersion: '2', ); environment.buildDir.createSync(recursive: true); fileSystem.file('bundle.sksl').writeAsStringSync(json.encode( <String, Object>{ 'engineRevision': '2', 'platform': 'android', 'data': <String, Object>{ 'A': 'B', }, }, )); // create pre-requisites. environment.buildDir.childFile('app.dill') .writeAsStringSync('abcd'); fileSystem .file(artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug)) .createSync(recursive: true); fileSystem .file(artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug)) .createSync(recursive: true); await const DebugAndroidApplication().build(environment); expect(fileSystem.file(fileSystem.path.join('out', 'flutter_assets', 'isolate_snapshot_data')), exists); expect(fileSystem.file(fileSystem.path.join('out', 'flutter_assets', 'vm_snapshot_data')), exists); expect(fileSystem.file(fileSystem.path.join('out', 'flutter_assets', 'kernel_blob.bin')), exists); expect(fileSystem.file(fileSystem.path.join('out', 'flutter_assets', 'io.flutter.shaders.json')), exists); }); testWithoutContext('profile bundle contains expected resources', () async { final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'profile', }, artifacts: artifacts, processManager: processManager, fileSystem: fileSystem, logger: logger, ); environment.buildDir.createSync(recursive: true); // create pre-requisites. environment.buildDir.childFile('app.so') .writeAsStringSync('abcd'); await const ProfileAndroidApplication().build(environment); expect(fileSystem.file(fileSystem.path.join('out', 'app.so')).existsSync(), true); }); testWithoutContext('release bundle contains expected resources', () async { final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'release', }, artifacts: artifacts, processManager: processManager, fileSystem: fileSystem, logger: logger, ); environment.buildDir.createSync(recursive: true); // create pre-requisites. environment.buildDir.childFile('app.so') .writeAsStringSync('abcd'); await const ReleaseAndroidApplication().build(environment); expect(fileSystem.file(fileSystem.path.join('out', 'app.so')).existsSync(), true); }); testUsingContext('AndroidAot can build provided target platform', () async { processManager = FakeProcessManager.empty(); final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'release', }, artifacts: artifacts, processManager: processManager, fileSystem: fileSystem, logger: logger, ); processManager.addCommand(FakeCommand(command: <String>[ artifacts.getArtifactPath( Artifact.genSnapshot, platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', '--snapshot_kind=app-aot-elf', '--elf=${environment.buildDir.childDirectory('arm64-v8a').childFile('app.so').path}', '--strip', environment.buildDir.childFile('app.dill').path, ], )); environment.buildDir.createSync(recursive: true); environment.buildDir.childFile('app.dill').createSync(); environment.projectDir.childFile('.packages').writeAsStringSync('\n'); const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); await androidAot.build(environment); expect(processManager, hasNoRemainingExpectations); }); testUsingContext('AndroidAot provide code size information.', () async { processManager = FakeProcessManager.empty(); final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'release', kCodeSizeDirectory: 'code_size_1', }, artifacts: artifacts, processManager: processManager, fileSystem: fileSystem, logger: logger, ); processManager.addCommand(FakeCommand(command: <String>[ artifacts.getArtifactPath( Artifact.genSnapshot, platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', '--write-v8-snapshot-profile-to=code_size_1/snapshot.arm64-v8a.json', '--trace-precompiler-to=code_size_1/trace.arm64-v8a.json', '--snapshot_kind=app-aot-elf', '--elf=${environment.buildDir.childDirectory('arm64-v8a').childFile('app.so').path}', '--strip', environment.buildDir.childFile('app.dill').path, ], )); environment.buildDir.createSync(recursive: true); environment.buildDir.childFile('app.dill').createSync(); environment.projectDir.childFile('.packages').writeAsStringSync('\n'); const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); await androidAot.build(environment); expect(processManager, hasNoRemainingExpectations); }); testUsingContext('kExtraGenSnapshotOptions passes values to gen_snapshot', () async { processManager = FakeProcessManager.empty(); final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'release', kExtraGenSnapshotOptions: 'foo,bar,baz=2', kTargetPlatform: 'android-arm', }, processManager: processManager, artifacts: artifacts, fileSystem: fileSystem, logger: logger, ); processManager.addCommand( FakeCommand(command: <String>[ artifacts.getArtifactPath( Artifact.genSnapshot, platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', 'foo', 'bar', 'baz=2', '--snapshot_kind=app-aot-elf', '--elf=${environment.buildDir.childDirectory('arm64-v8a').childFile('app.so').path}', '--strip', environment.buildDir.childFile('app.dill').path, ], )); environment.buildDir.createSync(recursive: true); environment.buildDir.childFile('app.dill').createSync(); environment.projectDir.childFile('.packages').writeAsStringSync('\n'); await const AndroidAot(TargetPlatform.android_arm64, BuildMode.release) .build(environment); }); testUsingContext('--no-strip in kExtraGenSnapshotOptions suppresses --strip gen_snapshot flag', () async { processManager = FakeProcessManager.empty(); final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'release', kExtraGenSnapshotOptions: 'foo,--no-strip,bar', kTargetPlatform: 'android-arm', }, processManager: processManager, artifacts: artifacts, fileSystem: fileSystem, logger: logger, ); processManager.addCommand( FakeCommand(command: <String>[ artifacts.getArtifactPath( Artifact.genSnapshot, platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', 'foo', 'bar', '--snapshot_kind=app-aot-elf', '--elf=${environment.buildDir.childDirectory('arm64-v8a').childFile('app.so').path}', environment.buildDir.childFile('app.dill').path, ], )); environment.buildDir.createSync(recursive: true); environment.buildDir.childFile('app.dill').createSync(); environment.projectDir.childFile('.packages').writeAsStringSync('\n'); await const AndroidAot(TargetPlatform.android_arm64, BuildMode.release) .build(environment); }); testWithoutContext('android aot bundle copies so from abi directory', () async { final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('out')..createSync(), defines: <String, String>{ kBuildMode: 'release', }, processManager: processManager, artifacts: artifacts, fileSystem: fileSystem, logger: logger, ); environment.buildDir.createSync(recursive: true); const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot); // Create required files. environment.buildDir .childDirectory('arm64-v8a') .childFile('app.so') .createSync(recursive: true); await androidAotBundle.build(environment); expect(environment.outputDir .childDirectory('arm64-v8a') .childFile('app.so').existsSync(), true); }); test('copyDeferredComponentSoFiles copies all files to correct locations', () { final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('/out')..createSync(), defines: <String, String>{ kBuildMode: 'release', }, processManager: processManager, artifacts: artifacts, fileSystem: fileSystem, logger: logger, ); final File so1 = fileSystem.file('/unit2/abi1/part.so'); so1.createSync(recursive: true); so1.writeAsStringSync('lib1'); final File so2 = fileSystem.file('/unit3/abi1/part.so'); so2.createSync(recursive: true); so2.writeAsStringSync('lib2'); final File so3 = fileSystem.file('/unit4/abi1/part.so'); so3.createSync(recursive: true); so3.writeAsStringSync('lib3'); final File so4 = fileSystem.file('/unit2/abi2/part.so'); so4.createSync(recursive: true); so4.writeAsStringSync('lib1'); final File so5 = fileSystem.file('/unit3/abi2/part.so'); so5.createSync(recursive: true); so5.writeAsStringSync('lib2'); final File so6 = fileSystem.file('/unit4/abi2/part.so'); so6.createSync(recursive: true); so6.writeAsStringSync('lib3'); final List<DeferredComponent> components = <DeferredComponent>[ DeferredComponent(name: 'component2', libraries: <String>['lib1']), DeferredComponent(name: 'component3', libraries: <String>['lib2']), ]; final List<LoadingUnit> loadingUnits = <LoadingUnit>[ LoadingUnit(id: 2, libraries: <String>['lib1'], path: '/unit2/abi1/part.so'), LoadingUnit(id: 3, libraries: <String>['lib2'], path: '/unit3/abi1/part.so'), LoadingUnit(id: 4, libraries: <String>['lib3'], path: '/unit4/abi1/part.so'), LoadingUnit(id: 2, libraries: <String>['lib1'], path: '/unit2/abi2/part.so'), LoadingUnit(id: 3, libraries: <String>['lib2'], path: '/unit3/abi2/part.so'), LoadingUnit(id: 4, libraries: <String>['lib3'], path: '/unit4/abi2/part.so'), ]; for (final DeferredComponent component in components) { component.assignLoadingUnits(loadingUnits); } final Directory buildDir = fileSystem.directory('/build'); if (!buildDir.existsSync()) { buildDir.createSync(recursive: true); } final Depfile depfile = copyDeferredComponentSoFiles( environment, components, loadingUnits, buildDir, <String>['abi1', 'abi2'], BuildMode.release ); expect(depfile.inputs.length, 6); expect(depfile.outputs.length, 6); expect(depfile.inputs[0].path, so1.path); expect(depfile.inputs[1].path, so2.path); expect(depfile.inputs[2].path, so4.path); expect(depfile.inputs[3].path, so5.path); expect(depfile.inputs[4].path, so3.path); expect(depfile.inputs[5].path, so6.path); expect(depfile.outputs[0].readAsStringSync(), so1.readAsStringSync()); expect(depfile.outputs[1].readAsStringSync(), so2.readAsStringSync()); expect(depfile.outputs[2].readAsStringSync(), so4.readAsStringSync()); expect(depfile.outputs[3].readAsStringSync(), so5.readAsStringSync()); expect(depfile.outputs[4].readAsStringSync(), so3.readAsStringSync()); expect(depfile.outputs[5].readAsStringSync(), so6.readAsStringSync()); expect(depfile.outputs[0].path, '/build/component2/intermediates/flutter/release/deferred_libs/abi1/libapp.so-2.part.so'); expect(depfile.outputs[1].path, '/build/component3/intermediates/flutter/release/deferred_libs/abi1/libapp.so-3.part.so'); expect(depfile.outputs[2].path, '/build/component2/intermediates/flutter/release/deferred_libs/abi2/libapp.so-2.part.so'); expect(depfile.outputs[3].path, '/build/component3/intermediates/flutter/release/deferred_libs/abi2/libapp.so-3.part.so'); expect(depfile.outputs[4].path, '/out/abi1/app.so-4.part.so'); expect(depfile.outputs[5].path, '/out/abi2/app.so-4.part.so'); }); test('copyDeferredComponentSoFiles copies files for only listed abis', () { final Environment environment = Environment.test( fileSystem.currentDirectory, outputDir: fileSystem.directory('/out')..createSync(), defines: <String, String>{ kBuildMode: 'release', }, processManager: processManager, artifacts: artifacts, fileSystem: fileSystem, logger: logger, ); final File so1 = fileSystem.file('/unit2/abi1/part.so'); so1.createSync(recursive: true); so1.writeAsStringSync('lib1'); final File so2 = fileSystem.file('/unit3/abi1/part.so'); so2.createSync(recursive: true); so2.writeAsStringSync('lib2'); final File so3 = fileSystem.file('/unit4/abi1/part.so'); so3.createSync(recursive: true); so3.writeAsStringSync('lib3'); final File so4 = fileSystem.file('/unit2/abi2/part.so'); so4.createSync(recursive: true); so4.writeAsStringSync('lib1'); final File so5 = fileSystem.file('/unit3/abi2/part.so'); so5.createSync(recursive: true); so5.writeAsStringSync('lib2'); final File so6 = fileSystem.file('/unit4/abi2/part.so'); so6.createSync(recursive: true); so6.writeAsStringSync('lib3'); final List<DeferredComponent> components = <DeferredComponent>[ DeferredComponent(name: 'component2', libraries: <String>['lib1']), DeferredComponent(name: 'component3', libraries: <String>['lib2']), ]; final List<LoadingUnit> loadingUnits = <LoadingUnit>[ LoadingUnit(id: 2, libraries: <String>['lib1'], path: '/unit2/abi1/part.so'), LoadingUnit(id: 3, libraries: <String>['lib2'], path: '/unit3/abi1/part.so'), LoadingUnit(id: 4, libraries: <String>['lib3'], path: '/unit4/abi1/part.so'), LoadingUnit(id: 2, libraries: <String>['lib1'], path: '/unit2/abi2/part.so'), LoadingUnit(id: 3, libraries: <String>['lib2'], path: '/unit3/abi2/part.so'), LoadingUnit(id: 4, libraries: <String>['lib3'], path: '/unit4/abi2/part.so'), ]; for (final DeferredComponent component in components) { component.assignLoadingUnits(loadingUnits); } final Directory buildDir = fileSystem.directory('/build'); if (!buildDir.existsSync()) { buildDir.createSync(recursive: true); } final Depfile depfile = copyDeferredComponentSoFiles( environment, components, loadingUnits, buildDir, <String>['abi1'], BuildMode.release ); expect(depfile.inputs.length, 3); expect(depfile.outputs.length, 3); expect(depfile.inputs[0].path, so1.path); expect(depfile.inputs[1].path, so2.path); expect(depfile.inputs[2].path, so3.path); expect(depfile.outputs[0].readAsStringSync(), so1.readAsStringSync()); expect(depfile.outputs[1].readAsStringSync(), so2.readAsStringSync()); expect(depfile.outputs[2].readAsStringSync(), so3.readAsStringSync()); expect(depfile.outputs[0].path, '/build/component2/intermediates/flutter/release/deferred_libs/abi1/libapp.so-2.part.so'); expect(depfile.outputs[1].path, '/build/component3/intermediates/flutter/release/deferred_libs/abi1/libapp.so-3.part.so'); expect(depfile.outputs[2].path, '/out/abi1/app.so-4.part.so'); }); }