// 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:archive/archive.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/application_package.dart'; import 'package:flutter_tools/src/android/gradle.dart'; import 'package:flutter_tools/src/android/gradle_errors.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fake_process_manager.dart'; import '../../src/fakes.dart'; const minimalV2EmbeddingManifest = r''' '''; void main() { group('gradle build', () { late BufferLogger logger; late FakeAnalytics fakeAnalytics; late MemoryFileSystem fileSystem; late FakeProcessManager processManager; setUp(() { processManager = FakeProcessManager.empty(); logger = BufferLogger.test(); fileSystem = MemoryFileSystem.test(); Cache.flutterRoot = ''; fakeAnalytics = getInitializedFakeAnalyticsInstance( fs: fileSystem, fakeFlutterVersion: FakeFlutterVersion(), ); }); testUsingContext( 'Can immediately tool exit on recognized exit code/stderr', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], exitCode: 1, stderr: '\nSome gradle message\n', ), ); fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); var handlerCalled = false; await expectLater(() async { await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: [ GradleHandledError( test: (String line) { return line.contains('Some gradle message'); }, handler: ({String? line, FlutterProject? project, bool? usesAndroidX}) async { handlerCalled = true; return GradleBuildStatus.exit; }, eventLabel: 'random-event-label', ), ], ); }, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1')); expect(handlerCalled, isTrue); expect( fakeAnalytics.sentEvents, containsAll([ Event.flutterBuildInfo( label: 'app-not-using-android-x', buildType: 'gradle', settings: 'androidGradlePluginVersion: null', ), Event.flutterBuildInfo( label: 'gradle-random-event-label-failure', buildType: 'gradle', settings: 'androidGradlePluginVersion: null', ), ]), ); expect( analyticsTimingEventExists( sentEvents: fakeAnalytics.sentEvents, workflow: 'build', variableName: 'gradle', ), true, ); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'Verbose mode for APKs includes Gradle stacktrace and sets debug log level', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: BufferLogger.test(verbose: true), processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '--full-stacktrace', '--info', '-Pverbose=true', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], ), ); fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); fileSystem .directory('build') .childDirectory('app') .childDirectory('outputs') .childDirectory('flutter-apk') .childFile('app-release.apk') .createSync(recursive: true); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: [], ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'Can retry build on recognized exit code/stderr', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); const fakeCmd = FakeCommand( command: [ 'gradlew', '-q', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], exitCode: 1, stderr: '\nSome gradle message\n', ); processManager.addCommand(fakeCmd); const maxRetries = 2; for (var i = 0; i < maxRetries; i++) { processManager.addCommand(fakeCmd); } fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); var testFnCalled = 0; await expectLater(() async { await builder.buildGradleApp( maxRetries: maxRetries, project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: [ GradleHandledError( test: (String line) { if (line.contains('Some gradle message')) { testFnCalled++; return true; } return false; }, handler: ({String? line, FlutterProject? project, bool? usesAndroidX}) async { return GradleBuildStatus.retry; }, eventLabel: 'random-event-label', ), ], ); }, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1')); expect(logger.statusText, contains('Retrying Gradle Build: #1, wait time: 100ms')); expect(logger.statusText, contains('Retrying Gradle Build: #2, wait time: 200ms')); expect(testFnCalled, equals(maxRetries + 1)); expect(fakeAnalytics.sentEvents, hasLength(9)); expect( fakeAnalytics.sentEvents, contains( Event.flutterBuildInfo( label: 'gradle-random-event-label-failure', buildType: 'gradle', settings: 'androidGradlePluginVersion: null', ), ), ); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'Converts recognized ProcessExceptions into tools exits', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], exitCode: 1, stderr: '\nSome gradle message\n', ), ); fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); var handlerCalled = false; await expectLater(() async { await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: [ GradleHandledError( test: (String line) { return line.contains('Some gradle message'); }, handler: ({String? line, FlutterProject? project, bool? usesAndroidX}) async { handlerCalled = true; return GradleBuildStatus.exit; }, eventLabel: 'random-event-label', ), ], ); }, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1')); expect(handlerCalled, isTrue); expect(fakeAnalytics.sentEvents, hasLength(3)); expect( fakeAnalytics.sentEvents, contains( Event.flutterBuildInfo( label: 'gradle-random-event-label-failure', buildType: 'gradle', settings: 'androidGradlePluginVersion: null', ), ), ); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'rethrows unrecognized ProcessException', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( FakeCommand( command: const [ 'gradlew', '-q', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], exitCode: 1, onRun: (_) { throw const ProcessException('', [], 'Unrecognized'); }, ), ); fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await expectLater(() async { await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: const [], ); }, throwsProcessException()); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'logs success event after a successful retry', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], exitCode: 1, stderr: '\nnSome gradle message\n', ), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], ), ); fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); fileSystem .directory('build') .childDirectory('app') .childDirectory('outputs') .childDirectory('flutter-apk') .childFile('app-release.apk') .createSync(recursive: true); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: [ GradleHandledError( test: (String line) { return line.contains('Some gradle message'); }, handler: ({String? line, FlutterProject? project, bool? usesAndroidX}) async { return GradleBuildStatus.retry; }, eventLabel: 'random-event-label', ), ], ); expect( fakeAnalytics.sentEvents, contains( Event.flutterBuildInfo( label: 'gradle-random-event-label-success', buildType: 'gradle', settings: 'androidGradlePluginVersion: null', ), ), ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'performs code size analysis and sends analytics', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(environment: {'HOME': '/home'}), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Ptarget-platform=android-arm64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Pcode-size-directory=foo', 'assembleRelease', ], ), ); fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final archive = Archive() ..addFile(ArchiveFile('AndroidManifest.xml', 100, List.filled(100, 0))) ..addFile(ArchiveFile('META-INF/CERT.RSA', 10, List.filled(10, 0))) ..addFile(ArchiveFile('META-INF/CERT.SF', 10, List.filled(10, 0))) ..addFile(ArchiveFile('lib/arm64-v8a/libapp.so', 50, List.filled(50, 0))) ..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List.filled(50, 0))); fileSystem .directory('build') .childDirectory('app') .childDirectory('outputs') .childDirectory('flutter-apk') .childFile('app-release.apk') ..createSync(recursive: true) ..writeAsBytesSync(ZipEncoder().encode(archive)!); fileSystem.file('foo/snapshot.arm64-v8a.json') ..createSync(recursive: true) ..writeAsStringSync(r''' [ { "l": "dart:_internal", "c": "SubListIterable", "n": "[Optimized] skip", "s": 2400 } ]'''); fileSystem.file('foo/trace.arm64-v8a.json') ..createSync(recursive: true) ..writeAsStringSync('{}'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, codeSizeDirectory: 'foo', packageConfigPath: '.dart_tool/package_config.json', ), targetArchs: [AndroidArch.arm64_v8a], ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: [], ); expect(fakeAnalytics.sentEvents, contains(Event.codeSizeAnalysis(platform: 'apk'))); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); group('Appbundle debug symbol tests', () { final commonCommandPortion = [ 'gradlew', '-q', '-Ptarget-platform=android-arm64,android-arm,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', ]; // Output from `/tools/bin/apkanalyzer files list ` // on an aab not containing debug symbols. const apkanalyzerOutputWithoutSymFiles = r''' / /META-INF/ /META-INF/MANIFEST.MF /META-INF/ANDROIDD.RSA /META-INF/ANDROIDD.SF /base/ /base/root/ /base/root/kotlin/ /base/root/kotlin/reflect/ /base/root/kotlin/reflect/reflect.kotlin_builtins /base/root/kotlin/ranges/ /base/root/kotlin/ranges/ranges.kotlin_builtins /base/root/kotlin/kotlin.kotlin_builtins /base/root/kotlin/internal/ /base/root/kotlin/internal/internal.kotlin_builtins /base/root/kotlin/coroutines/ /base/root/kotlin/coroutines/coroutines.kotlin_builtins /base/root/kotlin/collections/ /base/root/kotlin/collections/collections.kotlin_builtins /base/root/kotlin/annotation/ /base/root/kotlin/annotation/annotation.kotlin_builtins /base/root/kotlin-tooling-metadata.json /base/root/META-INF/ /base/root/META-INF/version-control-info.textproto /base/root/META-INF/services/ /base/root/META-INF/services/i0.b /base/root/META-INF/services/i0.a /base/root/META-INF/kotlinx_coroutines_core.version /base/root/META-INF/kotlinx_coroutines_android.version /base/root/META-INF/com/ /base/root/META-INF/com/android/ /base/root/META-INF/com/android/build/ /base/root/META-INF/com/android/build/gradle/ /base/root/META-INF/com/android/build/gradle/app-metadata.properties /base/root/META-INF/androidx.window_window.version /base/root/META-INF/androidx.window_window-java.version /base/root/META-INF/androidx.window.extensions.core_core.version /base/root/META-INF/androidx.viewpager_viewpager.version /base/root/META-INF/androidx.versionedparcelable_versionedparcelable.version /base/root/META-INF/androidx.tracing_tracing.version /base/root/META-INF/androidx.startup_startup-runtime.version /base/root/META-INF/androidx.savedstate_savedstate.version /base/root/META-INF/androidx.profileinstaller_profileinstaller.version /base/root/META-INF/androidx.loader_loader.version /base/root/META-INF/androidx.lifecycle_lifecycle-viewmodel.version /base/root/META-INF/androidx.lifecycle_lifecycle-viewmodel-savedstate.version /base/root/META-INF/androidx.lifecycle_lifecycle-runtime.version /base/root/META-INF/androidx.lifecycle_lifecycle-process.version /base/root/META-INF/androidx.lifecycle_lifecycle-livedata.version /base/root/META-INF/androidx.lifecycle_lifecycle-livedata-core.version /base/root/META-INF/androidx.lifecycle_lifecycle-livedata-core-ktx.version /base/root/META-INF/androidx.interpolator_interpolator.version /base/root/META-INF/androidx.fragment_fragment.version /base/root/META-INF/androidx.customview_customview.version /base/root/META-INF/androidx.core_core.version /base/root/META-INF/androidx.core_core-ktx.version /base/root/META-INF/androidx.arch.core_core-runtime.version /base/root/META-INF/androidx.annotation_annotation-experimental.version /base/root/META-INF/androidx.activity_activity.version /base/root/DebugProbesKt.bin /base/resources.pb /base/res/ /base/res/mipmap-xxxhdpi-v4/ /base/res/mipmap-xxxhdpi-v4/ic_launcher.png /base/res/mipmap-xxhdpi-v4/ /base/res/mipmap-xxhdpi-v4/ic_launcher.png /base/res/mipmap-xhdpi-v4/ /base/res/mipmap-xhdpi-v4/ic_launcher.png /base/res/mipmap-mdpi-v4/ /base/res/mipmap-mdpi-v4/ic_launcher.png /base/res/mipmap-hdpi-v4/ /base/res/mipmap-hdpi-v4/ic_launcher.png /base/res/drawable-v21/ /base/res/drawable-v21/launch_background.xml /base/native.pb /base/manifest/ /base/manifest/AndroidManifest.xml /base/lib/ /base/lib/x86_64/ /base/lib/x86_64/libflutter.so /base/lib/x86_64/libapp.so /base/lib/armeabi-v7a/ /base/lib/armeabi-v7a/libflutter.so /base/lib/armeabi-v7a/libapp.so /base/lib/arm64-v8a/ /base/lib/arm64-v8a/libflutter.so /base/lib/arm64-v8a/libapp.so /base/dex/ /base/dex/classes.dex /base/assets/ /base/assets/flutter_assets/ /base/assets/flutter_assets/shaders/ /base/assets/flutter_assets/shaders/ink_sparkle.frag /base/assets/flutter_assets/packages/ /base/assets/flutter_assets/packages/cupertino_icons/ /base/assets/flutter_assets/packages/cupertino_icons/assets/ /base/assets/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf /base/assets/flutter_assets/fonts/ /base/assets/flutter_assets/fonts/MaterialIcons-Regular.otf /base/assets/flutter_assets/NativeAssetsManifest.json /base/assets/flutter_assets/NOTICES.Z /base/assets/flutter_assets/FontManifest.json /base/assets/flutter_assets/AssetManifest.bin /base/assets.pb /BundleConfig.pb /BUNDLE-METADATA/ /BUNDLE-METADATA/com.android.tools.build.profiles/ /BUNDLE-METADATA/com.android.tools.build.profiles/baseline.profm /BUNDLE-METADATA/com.android.tools.build.profiles/baseline.prof /BUNDLE-METADATA/com.android.tools.build.obfuscation/ /BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map /BUNDLE-METADATA/com.android.tools.build.libraries/ /BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb /BUNDLE-METADATA/com.android.tools.build.gradle/ /BUNDLE-METADATA/com.android.tools.build.gradle/app-metadata.properties '''; // Output from `/tools/bin/apkanalyzer files list ` // on an aab containing debug symbols. const String apkanalyzerOutputWithSymFiles = apkanalyzerOutputWithoutSymFiles + r''' /BUNDLE-METADATA/com.android.tools.build.debugsymbols/ /BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/ /BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libflutter.so.sym '''; // Output from `/tools/bin/apkanalyzer files list ` // on an aab containing the debug info and symbol tables. const String apkanalyzerOutputWithDebugInfoAndSymFiles = apkanalyzerOutputWithoutSymFiles + r''' /BUNDLE-METADATA/com.android.tools.build.debugsymbols/ /BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/ /BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libflutter.so.dbg '''; void createSharedGradleFiles() { fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); } File createAabFile(BuildMode buildMode) { final File aabFile = fileSystem .directory('/build') .childDirectory('app') .childDirectory('outputs') .childDirectory('bundle') .childDirectory('$buildMode') .childFile('app-$buildMode.aab'); aabFile.createSync(recursive: true); return aabFile; } testUsingContext( 'build succeeds when debug symbols present for at least one architecture', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(environment: {'HOME': '/home'}), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( FakeCommand(command: List.of(commonCommandPortion)..add('bundleRelease')), ); createSharedGradleFiles(); final File aabFile = createAabFile(BuildMode.release); final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!; processManager.addCommand( FakeCommand( command: [ sdk.getCmdlineToolsPath(apkAnalyzerBinaryName)!, 'files', 'list', aabFile.path, ], stdout: apkanalyzerOutputWithSymFiles, ), ); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), targetArchs: [ AndroidArch.arm64_v8a, AndroidArch.armeabi_v7a, AndroidArch.x86_64, ], ), target: 'lib/main.dart', isBuildingBundle: true, configOnly: false, localGradleErrors: [], ); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'build succeeds when debug info and symbol tables present for at least one architecture', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(environment: {'HOME': '/home'}), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( FakeCommand(command: List.of(commonCommandPortion)..add('bundleRelease')), ); createSharedGradleFiles(); final File aabFile = createAabFile(BuildMode.release); final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!; processManager.addCommand( FakeCommand( command: [ sdk.getCmdlineToolsPath(apkAnalyzerBinaryName)!, 'files', 'list', aabFile.path, ], stdout: apkanalyzerOutputWithDebugInfoAndSymFiles, ), ); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), targetArchs: [ AndroidArch.arm64_v8a, AndroidArch.armeabi_v7a, AndroidArch.x86_64, ], ), target: 'lib/main.dart', isBuildingBundle: true, configOnly: false, localGradleErrors: [], ); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'building a debug aab does not invoke apkanalyzer', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(environment: {'HOME': '/home'}), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( FakeCommand(command: List.of(commonCommandPortion)..add('bundleDebug')), ); createSharedGradleFiles(); createAabFile(BuildMode.debug); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.debug, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), targetArchs: [ AndroidArch.arm64_v8a, AndroidArch.armeabi_v7a, AndroidArch.x86_64, ], ), target: 'lib/main.dart', isBuildingBundle: true, configOnly: false, localGradleErrors: [], ); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'throws tool exit for missing debug symbols when building release app bundle', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(environment: {'HOME': '/home'}), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( FakeCommand(command: List.of(commonCommandPortion)..add('bundleRelease')), ); createSharedGradleFiles(); final File aabFile = createAabFile(BuildMode.release); final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!; processManager.addCommand( FakeCommand( command: [ sdk.getCmdlineToolsPath(apkAnalyzerBinaryName)!, 'files', 'list', aabFile.path, ], stdout: apkanalyzerOutputWithoutSymFiles, ), ); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await expectLater( () async => builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), targetArchs: [ AndroidArch.arm64_v8a, AndroidArch.armeabi_v7a, AndroidArch.x86_64, ], ), target: 'lib/main.dart', isBuildingBundle: true, configOnly: false, localGradleErrors: [], ), throwsToolExit(message: failedToStripDebugSymbolsErrorMessage), ); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'build aab in release mode fails when apkanalyzer exit code is non zero', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(environment: {'HOME': '/home'}), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( FakeCommand(command: List.of(commonCommandPortion)..add('bundleRelease')), ); createSharedGradleFiles(); final File aabFile = createAabFile(BuildMode.release); final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!; processManager.addCommand( FakeCommand( command: [ sdk.getCmdlineToolsPath(apkAnalyzerBinaryName)!, 'files', 'list', aabFile.path, ], exitCode: 1, stdout: apkanalyzerOutputWithSymFiles, ), ); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await expectLater( () async => builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), targetArchs: [ AndroidArch.arm64_v8a, AndroidArch.armeabi_v7a, AndroidArch.x86_64, ], ), target: 'lib/main.dart', isBuildingBundle: true, configOnly: false, localGradleErrors: [], ), throwsToolExit(message: failedToStripDebugSymbolsErrorMessage), ); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); }); testUsingContext( 'indicates that an APK has been built successfully', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], ), ); fileSystem.directory('android').childFile('build.gradle').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); fileSystem .directory('build') .childDirectory('app') .childDirectory('outputs') .childDirectory('flutter-apk') .childFile('app-release.apk') .createSync(recursive: true); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: const [], ); expect( logger.statusText, contains('Built build/app/outputs/flutter-apk/app-release.apk (0.0MB)'), ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext('Uses namespace attribute if manifest lacks a package attribute', () async { final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory); final AndroidSdk sdk = FakeAndroidSdk(); fileSystem .directory(project.android.hostAppGradleRoot) .childFile('build.gradle') .createSync(recursive: true); fileSystem .directory(project.android.hostAppGradleRoot) .childDirectory('app') .childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync(''' apply from: irrelevant/flutter.gradle android { namespace 'com.example.foo' } '''); fileSystem .directory(project.android.hostAppGradleRoot) .childDirectory('app') .childDirectory('src') .childDirectory('main') .childFile('AndroidManifest.xml') ..createSync(recursive: true) ..writeAsStringSync(r''' '''); final AndroidApk? androidApk = await AndroidApk.fromAndroidProject( project.android, androidSdk: sdk, fileSystem: fileSystem, logger: logger, processManager: processManager, processUtils: ProcessUtils(processManager: processManager, logger: logger), userMessages: UserMessages(), buildInfo: const BuildInfo( BuildMode.debug, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ); expect(androidApk?.id, 'com.example.foo'); }); testUsingContext( 'can call custom gradle task getBuildOptions and parse the result', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: ['gradlew', '-q', 'printBuildVariants'], stdout: ''' BuildVariant: freeDebug BuildVariant: paidDebug BuildVariant: freeRelease BuildVariant: paidRelease BuildVariant: freeProfile BuildVariant: paidProfile ''', ), ); final List actual = await builder.getBuildVariants( project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); expect(actual, [ 'freeDebug', 'paidDebug', 'freeRelease', 'paidRelease', 'freeProfile', 'paidProfile', ]); expect( analyticsTimingEventExists( sentEvents: fakeAnalytics.sentEvents, workflow: 'print', variableName: 'android build variants', ), true, ); }, overrides: { AndroidStudio: () => FakeAndroidStudio(), Analytics: () => fakeAnalytics, }, ); testUsingContext( 'getBuildOptions returns empty list if gradle returns error', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: ['gradlew', '-q', 'printBuildVariants'], stderr: ''' Gradle Crashed ''', exitCode: 1, ), ); final List actual = await builder.getBuildVariants( project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); expect(actual, const []); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'can call custom gradle task outputFreeDebugAppLinkSettings and parse the result', () async { final String expectedOutputPath; expectedOutputPath = fileSystem.path.join( '/build/deeplink_data', 'app-link-settings-freeDebug.json', ); final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( FakeCommand( command: [ 'gradlew', '-q', '-PoutputPath=$expectedOutputPath', 'outputFreeDebugAppLinkSettings', ], ), ); await builder.outputsAppLinkSettings( 'freeDebug', project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); expect( analyticsTimingEventExists( sentEvents: fakeAnalytics.sentEvents, workflow: 'outputs', variableName: 'app link settings', ), true, ); }, overrides: { AndroidStudio: () => FakeAndroidStudio(), FileSystem: () => fileSystem, ProcessManager: () => processManager, Analytics: () => fakeAnalytics, }, ); testUsingContext( "doesn't indicate how to consume an AAR when printHowToConsumeAar is false", () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=build/', '-Pis-plugin=false', '-PbuildNumber=1.0', '-q', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Ptarget-platform=android-arm,android-arm64,android-x64', 'assembleAarRelease', ], ), ); final File manifestFile = fileSystem.file('pubspec.yaml'); manifestFile.createSync(recursive: true); manifestFile.writeAsStringSync(''' flutter: module: androidPackage: com.example.test '''); fileSystem.file('.android/gradlew').createSync(recursive: true); fileSystem.file('.android/gradle.properties').writeAsStringSync('irrelevant'); fileSystem.file('.android/build.gradle').createSync(recursive: true); fileSystem.directory('build/outputs/repo').createSync(recursive: true); await builder.buildGradleAar( androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), outputDirectory: fileSystem.directory('build/'), target: '', buildNumber: '1.0', ); expect(logger.statusText, contains('Built build/outputs/repo')); expect(logger.statusText.contains('Consuming the Module'), isFalse); expect(processManager, hasNoRemainingExpectations); expect( analyticsTimingEventExists( sentEvents: fakeAnalytics.sentEvents, workflow: 'build', variableName: 'gradle-aar', ), true, ); }, overrides: { AndroidStudio: () => FakeAndroidStudio(), Analytics: () => fakeAnalytics, }, ); // Regression test for https://github.com/flutter/flutter/issues/162649. testUsingContext('buildAar generates tooling for each sub-build for AARs', () async { addTearDown(() { printOnFailure(logger.statusText); printOnFailure(logger.errorText); }); final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommands(const [ FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=/build/host', '-Pis-plugin=false', '-PbuildNumber=1.0', '-q', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Ptarget-platform=android-arm,android-arm64,android-x64', 'assembleAarDebug', ], ), FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=/build/host', '-Pis-plugin=false', '-PbuildNumber=1.0', '-q', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Ptarget-platform=android-arm,android-arm64,android-x64', 'assembleAarProfile', ], ), FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=/build/host', '-Pis-plugin=false', '-PbuildNumber=1.0', '-q', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Ptarget-platform=android-arm,android-arm64,android-x64', 'assembleAarRelease', ], ), ]); final File manifestFile = fileSystem.file('pubspec.yaml'); manifestFile.createSync(recursive: true); manifestFile.writeAsStringSync(''' flutter: module: androidPackage: com.example.test '''); fileSystem.file('.android/gradlew').createSync(recursive: true); fileSystem.file('.android/gradle.properties').writeAsStringSync('irrelevant'); fileSystem.file('.android/build.gradle').createSync(recursive: true); fileSystem.directory('build/host/outputs/repo').createSync(recursive: true); final generateToolingCalls = <(FlutterProject, bool)>[]; await builder.buildAar( project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), androidBuildInfo: const { AndroidBuildInfo( BuildInfo( BuildMode.debug, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), AndroidBuildInfo( BuildInfo( BuildMode.profile, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), }, target: '', buildNumber: '1.0', generateTooling: (FlutterProject project, {required bool releaseMode}) async { generateToolingCalls.add((project, releaseMode)); }, ); expect(processManager, hasNoRemainingExpectations); // Ideally, this should be checked before each invocation to the process, // but instead we'll assume it was invoked in the same order as the calls // to gradle to keep the scope of this test light. expect(generateToolingCalls, hasLength(3)); expect( generateToolingCalls.map(((FlutterProject, bool) call) { return call.$2; }), [false, false, true], reason: 'generateTooling should omit debug metadata for release builds', ); }); testUsingContext( 'Verbose mode for AARs includes Gradle stacktrace and sets debug log level', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: BufferLogger.test(verbose: true), processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=build/', '-Pis-plugin=false', '-PbuildNumber=1.0', '--full-stacktrace', '--info', '-Pverbose=true', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Ptarget-platform=android-arm,android-arm64,android-x64', 'assembleAarRelease', ], ), ); final File manifestFile = fileSystem.file('pubspec.yaml'); manifestFile.createSync(recursive: true); manifestFile.writeAsStringSync(''' flutter: module: androidPackage: com.example.test '''); fileSystem.file('.android/gradlew').createSync(recursive: true); fileSystem.file('.android/gradle.properties').writeAsStringSync('irrelevant'); fileSystem.file('.android/build.gradle').createSync(recursive: true); fileSystem.directory('build/outputs/repo').createSync(recursive: true); await builder.buildGradleAar( androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), outputDirectory: fileSystem.directory('build/'), target: '', buildNumber: '1.0', ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'gradle exit code and stderr is forwarded to tool exit', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=build/', '-Pis-plugin=false', '-PbuildNumber=1.0', '-q', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Ptarget-platform=android-arm,android-arm64,android-x64', 'assembleAarRelease', ], exitCode: 108, stderr: 'Gradle task assembleAarRelease failed with exit code 108.', ), ); final File manifestFile = fileSystem.file('pubspec.yaml'); manifestFile.createSync(recursive: true); manifestFile.writeAsStringSync(''' flutter: module: androidPackage: com.example.test '''); fileSystem.file('.android/gradlew').createSync(recursive: true); fileSystem.file('.android/gradle.properties').writeAsStringSync('irrelevant'); fileSystem.file('.android/build.gradle').createSync(recursive: true); fileSystem.directory('build/outputs/repo').createSync(recursive: true); await expectLater( () async => builder.buildGradleAar( androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), outputDirectory: fileSystem.directory('build/'), target: '', buildNumber: '1.0', ), throwsToolExit( exitCode: 108, message: 'Gradle task assembleAarRelease failed with exit code 108.', ), ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'build apk uses selected local engine with arm32 ABI', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.testLocalEngine( localEngine: 'out/android_arm', localEngineHost: 'out/host_release', ), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0', '-Plocal-engine-build-mode=release', '-Plocal-engine-out=out/android_arm', '-Plocal-engine-host-out=out/host_release', '-Ptarget-platform=android-arm', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], ), ); fileSystem.file('out/android_arm/flutter_embedding_release.pom') ..createSync(recursive: true) ..writeAsStringSync(''' 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b '''); fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true); fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true); fileSystem .file('out/android_arm/armeabi_v7a_release.maven-metadata.xml') .createSync(recursive: true); fileSystem .file('out/android_arm/flutter_embedding_release.jar') .createSync(recursive: true); fileSystem .file('out/android_arm/flutter_embedding_release.pom') .createSync(recursive: true); fileSystem .file('out/android_arm/flutter_embedding_release.maven-metadata.xml') .createSync(recursive: true); fileSystem.file('android/gradlew').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.file('android/build.gradle').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await expectLater(() async { await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: const [], ); }, throwsToolExit()); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'build apk uses selected local engine with arm64 ABI', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.testLocalEngine( localEngine: 'out/android_arm64', localEngineHost: 'out/host_release', ), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0', '-Plocal-engine-build-mode=release', '-Plocal-engine-out=out/android_arm64', '-Plocal-engine-host-out=out/host_release', '-Ptarget-platform=android-arm64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], ), ); fileSystem.file('out/android_arm64/flutter_embedding_release.pom') ..createSync(recursive: true) ..writeAsStringSync(''' 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b '''); fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true); fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true); fileSystem .file('out/android_arm64/arm64_v8a_release.maven-metadata.xml') .createSync(recursive: true); fileSystem .file('out/android_arm64/flutter_embedding_release.jar') .createSync(recursive: true); fileSystem .file('out/android_arm64/flutter_embedding_release.pom') .createSync(recursive: true); fileSystem .file('out/android_arm64/flutter_embedding_release.maven-metadata.xml') .createSync(recursive: true); fileSystem.file('android/gradlew').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.file('android/build.gradle').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await expectLater(() async { await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: const [], ); }, throwsToolExit()); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'build apk uses selected local engine with x64 ABI', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.testLocalEngine( localEngine: 'out/android_x64', localEngineHost: 'out/host_release', ), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0', '-Plocal-engine-build-mode=release', '-Plocal-engine-out=out/android_x64', '-Plocal-engine-host-out=out/host_release', '-Ptarget-platform=android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], exitCode: 1, ), ); fileSystem.file('out/android_x64/flutter_embedding_release.pom') ..createSync(recursive: true) ..writeAsStringSync(''' 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b '''); fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true); fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true); fileSystem .file('out/android_x64/x86_64_release.maven-metadata.xml') .createSync(recursive: true); fileSystem .file('out/android_x64/flutter_embedding_release.jar') .createSync(recursive: true); fileSystem .file('out/android_x64/flutter_embedding_release.pom') .createSync(recursive: true); fileSystem .file('out/android_x64/flutter_embedding_release.maven-metadata.xml') .createSync(recursive: true); fileSystem.file('android/gradlew').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.file('android/build.gradle').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await expectLater(() async { await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: const [], ); }, throwsToolExit()); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'honors --no-android-gradle-daemon setting', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '--no-daemon', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', 'assembleRelease', ], ), ); fileSystem.file('android/gradlew').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.file('android/build.gradle').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await expectLater(() async { await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, androidGradleDaemon: false, packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: const [], ); }, throwsToolExit()); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'honors --android-project-cache-dir setting', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.test(), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-q', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', '-Pbase-application-name=android.app.Application', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '--project-cache-dir=/made/up/dir', 'assembleRelease', ], ), ); fileSystem.file('android/gradlew').createSync(recursive: true); fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); fileSystem.file('android/build.gradle').createSync(recursive: true); fileSystem.directory('android').childDirectory('app').childFile('build.gradle') ..createSync(recursive: true) ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); final FlutterProject project = FlutterProject.fromDirectoryTest( fileSystem.currentDirectory, ); project.android.appManifestFile ..createSync(recursive: true) ..writeAsStringSync(minimalV2EmbeddingManifest); await expectLater(() async { await builder.buildGradleApp( project: project, androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, androidGradleProjectCacheDir: '/made/up/dir', packageConfigPath: '.dart_tool/package_config.json', ), ), target: 'lib/main.dart', isBuildingBundle: false, configOnly: false, localGradleErrors: const [], ); }, throwsToolExit()); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'build aar uses selected local engine with arm32 ABI', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.testLocalEngine( localEngine: 'out/android_arm', localEngineHost: 'out/host_release', ), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=build/', '-Pis-plugin=false', '-PbuildNumber=2.0', '-q', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0', '-Plocal-engine-build-mode=release', '-Plocal-engine-out=out/android_arm', '-Plocal-engine-host-out=out/host_release', '-Ptarget-platform=android-arm', 'assembleAarRelease', ], ), ); fileSystem.file('out/android_arm/flutter_embedding_release.pom') ..createSync(recursive: true) ..writeAsStringSync(''' 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b '''); fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true); fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true); fileSystem .file('out/android_arm/armeabi_v7a_release.maven-metadata.xml') .createSync(recursive: true); fileSystem .file('out/android_arm/flutter_embedding_release.jar') .createSync(recursive: true); fileSystem .file('out/android_arm/flutter_embedding_release.pom') .createSync(recursive: true); fileSystem .file('out/android_arm/flutter_embedding_release.maven-metadata.xml') .createSync(recursive: true); final File manifestFile = fileSystem.file('pubspec.yaml'); manifestFile.createSync(recursive: true); manifestFile.writeAsStringSync(''' flutter: module: androidPackage: com.example.test '''); fileSystem.directory('.android/gradle').createSync(recursive: true); fileSystem.directory('.android/gradle/wrapper').createSync(recursive: true); fileSystem.file('.android/gradlew').createSync(recursive: true); fileSystem.file('.android/gradle.properties').writeAsStringSync('irrelevant'); fileSystem.file('.android/build.gradle').createSync(recursive: true); fileSystem.directory('build/outputs/repo').createSync(recursive: true); await builder.buildGradleAar( androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), outputDirectory: fileSystem.directory('build/'), target: '', buildNumber: '2.0', ); expect( fileSystem.link( 'build/outputs/repo/io/flutter/flutter_embedding_release/' '1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/' 'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom', ), exists, ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'build aar uses selected local engine with x64 ABI', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.testLocalEngine( localEngine: 'out/android_arm64', localEngineHost: 'out/host_release', ), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=build/', '-Pis-plugin=false', '-PbuildNumber=2.0', '-q', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0', '-Plocal-engine-build-mode=release', '-Plocal-engine-out=out/android_arm64', '-Plocal-engine-host-out=out/host_release', '-Ptarget-platform=android-arm64', 'assembleAarRelease', ], ), ); fileSystem.file('out/android_arm64/flutter_embedding_release.pom') ..createSync(recursive: true) ..writeAsStringSync(''' 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b '''); fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true); fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true); fileSystem .file('out/android_arm64/arm64_v8a_release.maven-metadata.xml') .createSync(recursive: true); fileSystem .file('out/android_arm64/flutter_embedding_release.jar') .createSync(recursive: true); fileSystem .file('out/android_arm64/flutter_embedding_release.pom') .createSync(recursive: true); fileSystem .file('out/android_arm64/flutter_embedding_release.maven-metadata.xml') .createSync(recursive: true); final File manifestFile = fileSystem.file('pubspec.yaml'); manifestFile.createSync(recursive: true); manifestFile.writeAsStringSync(''' flutter: module: androidPackage: com.example.test '''); fileSystem.directory('.android/gradle').createSync(recursive: true); fileSystem.directory('.android/gradle/wrapper').createSync(recursive: true); fileSystem.file('.android/gradlew').createSync(recursive: true); fileSystem.file('.android/gradle.properties').writeAsStringSync('irrelevant'); fileSystem.file('.android/build.gradle').createSync(recursive: true); fileSystem.directory('build/outputs/repo').createSync(recursive: true); await builder.buildGradleAar( androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), outputDirectory: fileSystem.directory('build/'), target: '', buildNumber: '2.0', ); expect( fileSystem.link( 'build/outputs/repo/io/flutter/flutter_embedding_release/' '1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/' 'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom', ), exists, ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); testUsingContext( 'build aar uses selected local engine on x64 ABI', () async { final builder = AndroidGradleBuilder( java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, artifacts: Artifacts.testLocalEngine( localEngine: 'out/android_x64', localEngineHost: 'out/host_release', ), analytics: fakeAnalytics, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand( command: [ 'gradlew', '-I=/packages/flutter_tools/gradle/aar_init_script.gradle', '-Pflutter-root=/', '-Poutput-dir=build/', '-Pis-plugin=false', '-PbuildNumber=2.0', '-q', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', '-Ptree-shake-icons=false', '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0', '-Plocal-engine-build-mode=release', '-Plocal-engine-out=out/android_x64', '-Plocal-engine-host-out=out/host_release', '-Ptarget-platform=android-x64', 'assembleAarRelease', ], ), ); fileSystem.file('out/android_x64/flutter_embedding_release.pom') ..createSync(recursive: true) ..writeAsStringSync(''' 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b '''); fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true); fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true); fileSystem .file('out/android_x64/x86_64_release.maven-metadata.xml') .createSync(recursive: true); fileSystem .file('out/android_x64/flutter_embedding_release.jar') .createSync(recursive: true); fileSystem .file('out/android_x64/flutter_embedding_release.pom') .createSync(recursive: true); fileSystem .file('out/android_x64/flutter_embedding_release.maven-metadata.xml') .createSync(recursive: true); final File manifestFile = fileSystem.file('pubspec.yaml'); manifestFile.createSync(recursive: true); manifestFile.writeAsStringSync(''' flutter: module: androidPackage: com.example.test '''); fileSystem.directory('.android/gradle').createSync(recursive: true); fileSystem.directory('.android/gradle/wrapper').createSync(recursive: true); fileSystem.file('.android/gradlew').createSync(recursive: true); fileSystem.file('.android/gradle.properties').writeAsStringSync('irrelevant'); fileSystem.file('.android/build.gradle').createSync(recursive: true); fileSystem.directory('build/outputs/repo').createSync(recursive: true); await builder.buildGradleAar( androidBuildInfo: const AndroidBuildInfo( BuildInfo( BuildMode.release, null, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), ), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), outputDirectory: fileSystem.directory('build/'), target: '', buildNumber: '2.0', ); expect( fileSystem.link( 'build/outputs/repo/io/flutter/flutter_embedding_release/' '1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/' 'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom', ), exists, ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); }); } class FakeGradleUtils extends Fake implements GradleUtils { @override String getExecutable(FlutterProject project) { return 'gradlew'; } } class FakeAndroidStudio extends Fake implements AndroidStudio { @override String get javaPath => '/android-studio/jbr'; }