| // 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:flutter_tools/src/artifacts.dart'; |
| import 'package:flutter_tools/src/base/file_system.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/exceptions.dart'; |
| import 'package:flutter_tools/src/build_system/targets/dart.dart'; |
| import 'package:flutter_tools/src/build_system/targets/ios.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/compile.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| import 'package:platform/platform.dart'; |
| import 'package:process/process.dart'; |
| |
| import '../../../src/common.dart'; |
| import '../../../src/fake_process_manager.dart'; |
| import '../../../src/testbed.dart'; |
| |
| const String kBoundaryKey = '4d2d9609-c662-4571-afde-31410f96caa6'; |
| const String kElfAot = '--snapshot_kind=app-aot-elf'; |
| const String kAssemblyAot = '--snapshot_kind=app-aot-assembly'; |
| |
| void main() { |
| Testbed testbed; |
| FakeProcessManager processManager; |
| Environment androidEnvironment; |
| Environment iosEnvironment; |
| Artifacts artifacts; |
| |
| setUpAll(() { |
| Cache.disableLocking(); |
| }); |
| |
| setUp(() { |
| testbed = Testbed(setup: () { |
| androidEnvironment = Environment.test( |
| globals.fs.currentDirectory, |
| defines: <String, String>{ |
| kBuildMode: getNameForBuildMode(BuildMode.profile), |
| kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), |
| }, |
| artifacts: artifacts, |
| processManager: processManager, |
| fileSystem: globals.fs, |
| logger: globals.logger, |
| ); |
| androidEnvironment.buildDir.createSync(recursive: true); |
| iosEnvironment = Environment.test( |
| globals.fs.currentDirectory, |
| defines: <String, String>{ |
| kBuildMode: getNameForBuildMode(BuildMode.profile), |
| kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios), |
| }, |
| artifacts: artifacts, |
| processManager: processManager, |
| fileSystem: globals.fs, |
| logger: globals.logger, |
| ); |
| iosEnvironment.buildDir.createSync(recursive: true); |
| artifacts = CachedArtifacts( |
| cache: globals.cache, |
| platform: globals.platform, |
| fileSystem: globals.fs, |
| ); |
| }, overrides: <Type, Generator>{ |
| Platform: () => FakePlatform(operatingSystem: 'macos', environment: <String, String>{}), |
| FileSystem: () => MemoryFileSystem.test(style: FileSystemStyle.posix), |
| ProcessManager: () => processManager, |
| }); |
| }); |
| |
| test('KernelSnapshot throws error if missing build mode', () => testbed.run(() async { |
| androidEnvironment.defines.remove(kBuildMode); |
| expect( |
| const KernelSnapshot().build(androidEnvironment), |
| throwsA(isInstanceOf<MissingDefineException>())); |
| })); |
| |
| test('KernelSnapshot handles null result from kernel compilation', () => testbed.run(() async { |
| globals.fs.file('.packages').writeAsStringSync('\n'); |
| final String build = androidEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.engineDartBinary), |
| artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), |
| '--sdk-root', |
| artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', |
| '--target=flutter', |
| '-Ddart.developer.causal_async_stacks=false', |
| ...buildModeOptions(BuildMode.profile), |
| '--aot', |
| '--tfa', |
| '--packages', |
| '/.packages', |
| '--output-dill', |
| '$build/app.dill', |
| '--depfile', |
| '$build/kernel_snapshot.d', |
| '/lib/main.dart', |
| ], exitCode: 1), |
| ]); |
| |
| await expectLater(() => const KernelSnapshot().build(androidEnvironment), |
| throwsA(isA<Exception>())); |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('KernelSnapshot does not use track widget creation on profile builds', () => testbed.run(() async { |
| globals.fs.file('.packages').writeAsStringSync('\n'); |
| final String build = androidEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.engineDartBinary), |
| artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), |
| '--sdk-root', |
| artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', |
| '--target=flutter', |
| '-Ddart.developer.causal_async_stacks=false', |
| ...buildModeOptions(BuildMode.profile), |
| '--aot', |
| '--tfa', |
| '--packages', |
| '/.packages', |
| '--output-dill', |
| '$build/app.dill', |
| '--depfile', |
| '$build/kernel_snapshot.d', |
| '/lib/main.dart', |
| ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), |
| ]); |
| |
| await const KernelSnapshot().build(androidEnvironment); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('KernelSnapshot correctly handles an empty string in ExtraFrontEndOptions', () => testbed.run(() async { |
| globals.fs.file('.packages').writeAsStringSync('\n'); |
| final String build = androidEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.engineDartBinary), |
| artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), |
| '--sdk-root', |
| artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', |
| '--target=flutter', |
| '-Ddart.developer.causal_async_stacks=false', |
| ...buildModeOptions(BuildMode.profile), |
| '--aot', |
| '--tfa', |
| '--packages', |
| '/.packages', |
| '--output-dill', |
| '$build/app.dill', |
| '--depfile', |
| '$build/kernel_snapshot.d', |
| '/lib/main.dart', |
| ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), |
| ]); |
| |
| await const KernelSnapshot() |
| .build(androidEnvironment..defines[kExtraFrontEndOptions] = ''); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('KernelSnapshot correctly forwards ExtraFrontEndOptions', () => testbed.run(() async { |
| globals.fs.file('.packages').writeAsStringSync('\n'); |
| final String build = androidEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.engineDartBinary), |
| artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), |
| '--sdk-root', |
| artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', |
| '--target=flutter', |
| '-Ddart.developer.causal_async_stacks=false', |
| ...buildModeOptions(BuildMode.profile), |
| '--aot', |
| '--tfa', |
| '--packages', |
| '/.packages', |
| '--output-dill', |
| '$build/app.dill', |
| '--depfile', |
| '$build/kernel_snapshot.d', |
| 'foo', |
| 'bar', |
| '/lib/main.dart', |
| ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), |
| ]); |
| |
| await const KernelSnapshot() |
| .build(androidEnvironment..defines[kExtraFrontEndOptions] = 'foo,bar'); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('KernelSnapshot can disable track-widget-creation on debug builds', () => testbed.run(() async { |
| globals.fs.file('.packages').writeAsStringSync('\n'); |
| final String build = androidEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.engineDartBinary), |
| artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), |
| '--sdk-root', |
| artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', |
| '--target=flutter', |
| '-Ddart.developer.causal_async_stacks=true', |
| ...buildModeOptions(BuildMode.debug), |
| '--no-link-platform', |
| '--packages', |
| '/.packages', |
| '--output-dill', |
| '$build/app.dill', |
| '--depfile', |
| '$build/kernel_snapshot.d', |
| '/lib/main.dart', |
| ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), |
| ]); |
| |
| await const KernelSnapshot().build(androidEnvironment |
| ..defines[kBuildMode] = getNameForBuildMode(BuildMode.debug) |
| ..defines[kTrackWidgetCreation] = 'false'); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('KernelSnapshot forces platform linking on debug for darwin target platforms', () => testbed.run(() async { |
| globals.fs.file('.packages').writeAsStringSync('\n'); |
| final String build = androidEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.engineDartBinary), |
| artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), |
| '--sdk-root', |
| artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', |
| '--target=flutter', |
| '-Ddart.developer.causal_async_stacks=true', |
| ...buildModeOptions(BuildMode.debug), |
| '--packages', |
| '/.packages', |
| '--output-dill', |
| '$build/app.dill', |
| '--depfile', |
| '$build/kernel_snapshot.d', |
| '/lib/main.dart', |
| ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), |
| ]); |
| |
| await const KernelSnapshot().build(androidEnvironment |
| ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.darwin_x64) |
| ..defines[kBuildMode] = getNameForBuildMode(BuildMode.debug) |
| ..defines[kTrackWidgetCreation] = 'false' |
| ); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('KernelSnapshot does use track widget creation on debug builds', () => testbed.run(() async { |
| globals.fs.file('.packages').writeAsStringSync('\n'); |
| final Environment testEnvironment = Environment.test( |
| globals.fs.currentDirectory, |
| defines: <String, String>{ |
| kBuildMode: getNameForBuildMode(BuildMode.debug), |
| kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), |
| }, |
| processManager: processManager, |
| artifacts: artifacts, |
| fileSystem: globals.fs, |
| logger: globals.logger, |
| ); |
| final String build = testEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.engineDartBinary), |
| artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), |
| '--sdk-root', |
| artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath) + '/', |
| '--target=flutter', |
| '-Ddart.developer.causal_async_stacks=true', |
| ...buildModeOptions(BuildMode.debug), |
| '--track-widget-creation', |
| '--no-link-platform', |
| '--packages', |
| '/.packages', |
| '--output-dill', |
| '$build/app.dill', |
| '--depfile', |
| '$build/kernel_snapshot.d', |
| '/lib/main.dart', |
| ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey /build/653e11a8e6908714056a57cd6b4f602a/app.dill 0\n'), |
| ]); |
| |
| await const KernelSnapshot().build(testEnvironment); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('AotElfProfile Produces correct output directory', () => testbed.run(() async { |
| final String build = androidEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.genSnapshot, mode: BuildMode.profile), |
| '--deterministic', |
| kElfAot, |
| '--elf=$build/app.so', |
| '--strip', |
| '--no-sim-use-hardfp', |
| '--no-use-integer-division', |
| '--no-causal-async-stacks', |
| '--lazy-async-stacks', |
| '$build/app.dill', |
| ]) |
| ]); |
| androidEnvironment.buildDir.childFile('app.dill').createSync(recursive: true); |
| |
| await const AotElfProfile().build(androidEnvironment); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('AotElfProfile throws error if missing build mode', () => testbed.run(() async { |
| androidEnvironment.defines.remove(kBuildMode); |
| |
| expect(const AotElfProfile().build(androidEnvironment), |
| throwsA(isInstanceOf<MissingDefineException>())); |
| })); |
| |
| test('AotElfProfile throws error if missing target platform', () => testbed.run(() async { |
| androidEnvironment.defines.remove(kTargetPlatform); |
| |
| expect(const AotElfProfile().build(androidEnvironment), |
| throwsA(isInstanceOf<MissingDefineException>())); |
| })); |
| |
| test('AotAssemblyProfile throws error if missing build mode', () => testbed.run(() async { |
| iosEnvironment.defines.remove(kBuildMode); |
| |
| expect(const AotAssemblyProfile().build(iosEnvironment), |
| throwsA(isInstanceOf<MissingDefineException>())); |
| })); |
| |
| test('AotAssemblyProfile throws error if missing target platform', () => testbed.run(() async { |
| iosEnvironment.defines.remove(kTargetPlatform); |
| |
| expect(const AotAssemblyProfile().build(iosEnvironment), |
| throwsA(isInstanceOf<MissingDefineException>())); |
| })); |
| |
| test('AotAssemblyProfile throws error if built for non-iOS platform', () => testbed.run(() async { |
| expect(const AotAssemblyProfile().build(androidEnvironment), |
| throwsA(isInstanceOf<Exception>())); |
| })); |
| |
| test('AotAssemblyProfile generates multiple arches and lipos together', () => testbed.run(() async { |
| final String build = iosEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| // This path is not known by the cache due to the iOS gen_snapshot split. |
| 'bin/cache/artifacts/engine/ios-profile/gen_snapshot_armv7', |
| '--deterministic', |
| kAssemblyAot, |
| '--assembly=$build/armv7/snapshot_assembly.S', |
| '--strip', |
| '--no-sim-use-hardfp', |
| '--no-use-integer-division', |
| '--no-causal-async-stacks', |
| '--lazy-async-stacks', |
| '$build/app.dill', |
| ]), |
| FakeCommand(command: <String>[ |
| // This path is not known by the cache due to the iOS gen_snapshot split. |
| 'bin/cache/artifacts/engine/ios-profile/gen_snapshot_arm64', |
| '--deterministic', |
| kAssemblyAot, |
| '--assembly=$build/arm64/snapshot_assembly.S', |
| '--strip', |
| '--no-causal-async-stacks', |
| '--lazy-async-stacks', |
| '$build/app.dill', |
| ]), |
| const FakeCommand(command: <String>[ |
| 'xcrun', |
| '--sdk', |
| 'iphoneos', |
| '--show-sdk-path', |
| ]), |
| const FakeCommand(command: <String>[ |
| 'xcrun', |
| '--sdk', |
| 'iphoneos', |
| '--show-sdk-path', |
| ]), |
| FakeCommand(command: <String>[ |
| 'xcrun', |
| 'cc', |
| '-arch', |
| 'armv7', |
| '-isysroot', |
| '', |
| '-c', |
| '$build/armv7/snapshot_assembly.S', |
| '-o', |
| '$build/armv7/snapshot_assembly.o', |
| ]), |
| FakeCommand(command: <String>[ |
| 'xcrun', |
| 'cc', |
| '-arch', |
| 'arm64', |
| '-isysroot', |
| '', |
| '-c', |
| '$build/arm64/snapshot_assembly.S', |
| '-o', |
| '$build/arm64/snapshot_assembly.o', |
| ]), |
| FakeCommand(command: <String>[ |
| 'xcrun', |
| 'clang', |
| '-arch', |
| 'armv7', |
| '-miphoneos-version-min=8.0', |
| '-dynamiclib', |
| '-Xlinker', |
| '-rpath', |
| '-Xlinker', |
| '@executable_path/Frameworks', |
| '-Xlinker', |
| '-rpath', |
| '-Xlinker', |
| '@loader_path/Frameworks', |
| '-install_name', |
| '@rpath/App.framework/App', |
| '-isysroot', |
| '', |
| '-o', |
| '$build/armv7/App.framework/App', |
| '$build/armv7/snapshot_assembly.o', |
| ]), |
| FakeCommand(command: <String>[ |
| 'xcrun', |
| 'clang', |
| '-arch', |
| 'arm64', |
| '-miphoneos-version-min=8.0', |
| '-dynamiclib', |
| '-Xlinker', |
| '-rpath', |
| '-Xlinker', |
| '@executable_path/Frameworks', |
| '-Xlinker', |
| '-rpath', |
| '-Xlinker', |
| '@loader_path/Frameworks', |
| '-install_name', |
| '@rpath/App.framework/App', |
| '-isysroot', |
| '', |
| '-o', |
| '$build/arm64/App.framework/App', |
| '$build/arm64/snapshot_assembly.o', |
| ]), |
| FakeCommand(command: <String>[ |
| 'lipo', |
| '$build/armv7/App.framework/App', |
| '$build/arm64/App.framework/App', |
| '-create', |
| '-output', |
| '$build/App.framework/App', |
| ]), |
| ]); |
| iosEnvironment.defines[kIosArchs] ='armv7 arm64'; |
| |
| await const AotAssemblyProfile().build(iosEnvironment); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('AotAssemblyProfile with bitcode sends correct argument to snapshotter (one arch)', () => testbed.run(() async { |
| iosEnvironment.defines[kIosArchs] = 'arm64'; |
| iosEnvironment.defines[kBitcodeFlag] = 'true'; |
| final String build = iosEnvironment.buildDir.path; |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| // This path is not known by the cache due to the iOS gen_snapshot split. |
| 'bin/cache/artifacts/engine/ios-profile/gen_snapshot_arm64', |
| '--deterministic', |
| kAssemblyAot, |
| '--assembly=$build/arm64/snapshot_assembly.S', |
| '--strip', |
| '--no-causal-async-stacks', |
| '--lazy-async-stacks', |
| '$build/app.dill', |
| ]), |
| const FakeCommand(command: <String>[ |
| 'xcrun', |
| '--sdk', |
| 'iphoneos', |
| '--show-sdk-path', |
| ]), |
| FakeCommand(command: <String>[ |
| 'xcrun', |
| 'cc', |
| '-arch', |
| 'arm64', |
| '-isysroot', |
| '', |
| // Contains bitcode flag. |
| '-fembed-bitcode', |
| '-c', |
| '$build/arm64/snapshot_assembly.S', |
| '-o', |
| '$build/arm64/snapshot_assembly.o', |
| ]), |
| FakeCommand(command: <String>[ |
| 'xcrun', |
| 'clang', |
| '-arch', |
| 'arm64', |
| '-miphoneos-version-min=8.0', |
| '-dynamiclib', |
| '-Xlinker', |
| '-rpath', |
| '-Xlinker', |
| '@executable_path/Frameworks', |
| '-Xlinker', |
| '-rpath', |
| '-Xlinker', |
| '@loader_path/Frameworks', |
| '-install_name', |
| '@rpath/App.framework/App', |
| // Contains bitcode flag. |
| '-fembed-bitcode', |
| '-isysroot', |
| '', |
| '-o', |
| '$build/arm64/App.framework/App', |
| '$build/arm64/snapshot_assembly.o', |
| ]), |
| FakeCommand(command: <String>[ |
| 'lipo', |
| '$build/arm64/App.framework/App', |
| '-create', |
| '-output', |
| '$build/App.framework/App', |
| ]), |
| ]); |
| |
| await const AotAssemblyProfile().build(iosEnvironment); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| |
| test('kExtraGenSnapshotOptions passes values to gen_snapshot', () => testbed.run(() async { |
| androidEnvironment.defines[kExtraGenSnapshotOptions] = 'foo,bar,baz=2'; |
| androidEnvironment.defines[kBuildMode] = getNameForBuildMode(BuildMode.profile); |
| final String build = androidEnvironment.buildDir.path; |
| |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand(command: <String>[ |
| artifacts.getArtifactPath(Artifact.genSnapshot, mode: BuildMode.profile), |
| '--deterministic', |
| 'foo', |
| 'bar', |
| 'baz=2', |
| kElfAot, |
| '--elf=$build/app.so', |
| '--strip', |
| '--no-sim-use-hardfp', |
| '--no-use-integer-division', |
| '--no-causal-async-stacks', |
| '--lazy-async-stacks', |
| '$build/app.dill', |
| ]), |
| ]); |
| |
| await const AotElfRelease().build(androidEnvironment); |
| |
| expect(processManager.hasRemainingExpectations, false); |
| })); |
| } |