From 29207e4f787a093039dedb413c99f77c99a78280 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Wed, 31 Mar 2021 15:11:58 -0700 Subject: [PATCH] [flutter_tools] split application package factory dependencies (#79461) --- .../lib/src/android/android_device.dart | 1 + .../lib/src/android/application_package.dart | 415 +++++++++++ .../lib/src/application_package.dart | 659 +----------------- .../lib/src/commands/build_ios.dart | 2 +- .../flutter_tools/lib/src/context_runner.dart | 3 +- .../lib/src/flutter_application_package.dart | 116 +++ .../lib/src/ios/application_package.dart | 180 +++++ .../flutter_tools/lib/src/ios/devices.dart | 2 +- packages/flutter_tools/lib/src/ios/mac.dart | 2 +- .../flutter_tools/lib/src/ios/simulators.dart | 1 + .../commands.shard/hermetic/install_test.dart | 2 + .../android/android_device_start_test.dart | 2 +- .../android/android_install_test.dart | 2 +- .../application_package_test.dart | 2 + .../test/general.shard/ios/devices_test.dart | 2 +- .../ios/ios_device_install_test.dart | 2 +- .../ios_device_start_nonprebuilt_test.dart | 2 +- .../ios/ios_device_start_prebuilt_test.dart | 2 +- .../general.shard/ios/simulators_test.dart | 2 +- 19 files changed, 732 insertions(+), 667 deletions(-) create mode 100644 packages/flutter_tools/lib/src/android/application_package.dart create mode 100644 packages/flutter_tools/lib/src/flutter_application_package.dart create mode 100644 packages/flutter_tools/lib/src/ios/application_package.dart diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index a808fb94bb0..334cbebd94b 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -28,6 +28,7 @@ import '../protocol_discovery.dart'; import 'android.dart'; import 'android_console.dart'; import 'android_sdk.dart'; +import 'application_package.dart'; /// Whether the [AndroidDevice] is believed to be a physical device or an emulator. enum HardwareType { emulator, physical } diff --git a/packages/flutter_tools/lib/src/android/application_package.dart b/packages/flutter_tools/lib/src/android/application_package.dart new file mode 100644 index 00000000000..d410fcb4e90 --- /dev/null +++ b/packages/flutter_tools/lib/src/android/application_package.dart @@ -0,0 +1,415 @@ +// 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. + +// @dart = 2.8 + +import 'dart:collection'; + +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; +import 'package:xml/xml.dart'; + +import '../application_package.dart'; +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/process.dart'; +import '../base/user_messages.dart'; +import '../build_info.dart'; +import '../globals.dart' as globals; +import '../project.dart'; +import 'android_sdk.dart'; +import 'gradle.dart'; + +/// An application package created from an already built Android APK. +class AndroidApk extends ApplicationPackage { + AndroidApk({ + String id, + @required this.file, + @required this.versionCode, + @required this.launchActivity, + }) : assert(file != null), + assert(launchActivity != null), + super(id: id); + + /// Creates a new AndroidApk from an existing APK. + /// + /// Returns `null` if the APK was invalid or any required tooling was missing. + factory AndroidApk.fromApk(File apk, { + @required AndroidSdk androidSdk, + @required ProcessManager processManager, + @required UserMessages userMessages, + @required Logger logger, + }) { + final String aaptPath = androidSdk?.latestVersion?.aaptPath; + if (aaptPath == null || !processManager.canRun(aaptPath)) { + logger.printError(userMessages.aaptNotFound); + return null; + } + + String apptStdout; + try { + apptStdout = globals.processUtils.runSync( + [ + aaptPath, + 'dump', + 'xmltree', + apk.path, + 'AndroidManifest.xml', + ], + throwOnError: true, + ).stdout.trim(); + } on ProcessException catch (error) { + globals.printError('Failed to extract manifest from APK: $error.'); + return null; + } + + final ApkManifestData data = ApkManifestData.parseFromXmlDump(apptStdout, logger); + + if (data == null) { + logger.printError('Unable to read manifest info from ${apk.path}.'); + return null; + } + + if (data.packageName == null || data.launchableActivityName == null) { + logger.printError('Unable to read manifest info from ${apk.path}.'); + return null; + } + + return AndroidApk( + id: data.packageName, + file: apk, + versionCode: int.tryParse(data.versionCode), + launchActivity: '${data.packageName}/${data.launchableActivityName}', + ); + } + + /// Path to the actual apk file. + final File file; + + /// The path to the activity that should be launched. + final String launchActivity; + + /// The version code of the APK. + final int versionCode; + + /// Creates a new AndroidApk based on the information in the Android manifest. + static Future fromAndroidProject(AndroidProject androidProject, { + @required AndroidSdk androidSdk, + @required ProcessManager processManager, + @required UserMessages userMessages, + @required ProcessUtils processUtils, + @required Logger logger, + @required FileSystem fileSystem, + }) async { + File apkFile; + + if (androidProject.isUsingGradle && androidProject.isSupportedVersion) { + apkFile = getApkDirectory(androidProject.parent).childFile('app.apk'); + if (apkFile.existsSync()) { + // Grab information from the .apk. The gradle build script might alter + // the application Id, so we need to look at what was actually built. + return AndroidApk.fromApk( + apkFile, + androidSdk: androidSdk, + processManager: processManager, + logger: logger, + userMessages: userMessages, + ); + } + // The .apk hasn't been built yet, so we work with what we have. The run + // command will grab a new AndroidApk after building, to get the updated + // IDs. + } else { + apkFile = fileSystem.file(fileSystem.path.join(getAndroidBuildDirectory(), 'app.apk')); + } + + final File manifest = androidProject.appManifestFile; + + if (!manifest.existsSync()) { + logger.printError('AndroidManifest.xml could not be found.'); + logger.printError('Please check ${manifest.path} for errors.'); + return null; + } + + final String manifestString = manifest.readAsStringSync(); + XmlDocument document; + try { + document = XmlDocument.parse(manifestString); + } on XmlParserException catch (exception) { + String manifestLocation; + if (androidProject.isUsingGradle) { + manifestLocation = fileSystem.path.join(androidProject.hostAppGradleRoot.path, 'app', 'src', 'main', 'AndroidManifest.xml'); + } else { + manifestLocation = fileSystem.path.join(androidProject.hostAppGradleRoot.path, 'AndroidManifest.xml'); + } + logger.printError('AndroidManifest.xml is not a valid XML document.'); + logger.printError('Please check $manifestLocation for errors.'); + throwToolExit('XML Parser error message: ${exception.toString()}'); + } + + final Iterable manifests = document.findElements('manifest'); + if (manifests.isEmpty) { + logger.printError('AndroidManifest.xml has no manifest element.'); + logger.printError('Please check ${manifest.path} for errors.'); + return null; + } + final String packageId = manifests.first.getAttribute('package'); + + String launchActivity; + for (final XmlElement activity in document.findAllElements('activity')) { + final String enabled = activity.getAttribute('android:enabled'); + if (enabled != null && enabled == 'false') { + continue; + } + + for (final XmlElement element in activity.findElements('intent-filter')) { + String actionName = ''; + String categoryName = ''; + for (final XmlNode node in element.children) { + if (node is! XmlElement) { + continue; + } + final XmlElement xmlElement = node as XmlElement; + final String name = xmlElement.getAttribute('android:name'); + if (name == 'android.intent.action.MAIN') { + actionName = name; + } else if (name == 'android.intent.category.LAUNCHER') { + categoryName = name; + } + } + if (actionName.isNotEmpty && categoryName.isNotEmpty) { + final String activityName = activity.getAttribute('android:name'); + launchActivity = '$packageId/$activityName'; + break; + } + } + } + + if (packageId == null || launchActivity == null) { + logger.printError('package identifier or launch activity not found.'); + logger.printError('Please check ${manifest.path} for errors.'); + return null; + } + + return AndroidApk( + id: packageId, + file: apkFile, + versionCode: null, + launchActivity: launchActivity, + ); + } + + @override + File get packagesFile => file; + + @override + String get name => file.basename; +} + + +class _Entry { + _Element parent; + int level; +} + +class _Element extends _Entry { + _Element.fromLine(String line, _Element parent) { + // E: application (line=29) + final List parts = line.trimLeft().split(' '); + name = parts[1]; + level = line.length - line.trimLeft().length; + this.parent = parent; + children = <_Entry>[]; + } + + List<_Entry> children; + String name; + + void addChild(_Entry child) { + children.add(child); + } + + _Attribute firstAttribute(String name) { + return children.whereType<_Attribute>().firstWhere( + (_Attribute e) => e.key.startsWith(name), + orElse: () => null, + ); + } + + _Element firstElement(String name) { + return children.whereType<_Element>().firstWhere( + (_Element e) => e.name.startsWith(name), + orElse: () => null, + ); + } + + Iterable<_Element> allElements(String name) { + return children.whereType<_Element>().where((_Element e) => e.name.startsWith(name)); + } +} + +class _Attribute extends _Entry { + _Attribute.fromLine(String line, _Element parent) { + // A: android:label(0x01010001)="hello_world" (Raw: "hello_world") + const String attributePrefix = 'A: '; + final List keyVal = line + .substring(line.indexOf(attributePrefix) + attributePrefix.length) + .split('='); + key = keyVal[0]; + value = keyVal[1]; + level = line.length - line.trimLeft().length; + this.parent = parent; + } + + String key; + String value; +} + +class ApkManifestData { + ApkManifestData._(this._data); + + static bool isAttributeWithValuePresent(_Element baseElement, + String childElement, String attributeName, String attributeValue) { + final Iterable<_Element> allElements = baseElement.allElements(childElement); + for (final _Element oneElement in allElements) { + final String elementAttributeValue = oneElement + ?.firstAttribute(attributeName) + ?.value; + if (elementAttributeValue != null && + elementAttributeValue.startsWith(attributeValue)) { + return true; + } + } + return false; + } + + static ApkManifestData parseFromXmlDump(String data, Logger logger) { + if (data == null || data.trim().isEmpty) { + return null; + } + + final List lines = data.split('\n'); + assert(lines.length > 3); + + final int manifestLine = lines.indexWhere((String line) => line.contains('E: manifest')); + final _Element manifest = _Element.fromLine(lines[manifestLine], null); + _Element currentElement = manifest; + + for (final String line in lines.skip(manifestLine)) { + final String trimLine = line.trimLeft(); + final int level = line.length - trimLine.length; + + // Handle level out + while (currentElement.parent != null && level <= currentElement.level) { + currentElement = currentElement.parent; + } + + if (level > currentElement.level) { + switch (trimLine[0]) { + case 'A': + currentElement + .addChild(_Attribute.fromLine(line, currentElement)); + break; + case 'E': + final _Element element = _Element.fromLine(line, currentElement); + currentElement.addChild(element); + currentElement = element; + } + } + } + + final _Element application = manifest.firstElement('application'); + if (application == null) { + return null; + } + + final Iterable<_Element> activities = application.allElements('activity'); + + _Element launchActivity; + for (final _Element activity in activities) { + final _Attribute enabled = activity.firstAttribute('android:enabled'); + final Iterable<_Element> intentFilters = activity.allElements('intent-filter'); + final bool isEnabledByDefault = enabled == null; + final bool isExplicitlyEnabled = enabled != null && enabled.value.contains('0xffffffff'); + if (!(isEnabledByDefault || isExplicitlyEnabled)) { + continue; + } + + for (final _Element element in intentFilters) { + final bool isMainAction = isAttributeWithValuePresent( + element, 'action', 'android:name', '"android.intent.action.MAIN"'); + if (!isMainAction) { + continue; + } + final bool isLauncherCategory = isAttributeWithValuePresent( + element, 'category', 'android:name', + '"android.intent.category.LAUNCHER"'); + if (!isLauncherCategory) { + continue; + } + launchActivity = activity; + break; + } + if (launchActivity != null) { + break; + } + } + + final _Attribute package = manifest.firstAttribute('package'); + // "io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world") + final String packageName = package.value.substring(1, package.value.indexOf('" ')); + + if (launchActivity == null) { + logger.printError('Error running $packageName. Default activity not found'); + return null; + } + + final _Attribute nameAttribute = launchActivity.firstAttribute('android:name'); + // "io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity") + final String activityName = nameAttribute + .value.substring(1, nameAttribute.value.indexOf('" ')); + + // Example format: (type 0x10)0x1 + final _Attribute versionCodeAttr = manifest.firstAttribute('android:versionCode'); + if (versionCodeAttr == null) { + logger.printError('Error running $packageName. Manifest versionCode not found'); + return null; + } + if (!versionCodeAttr.value.startsWith('(type 0x10)')) { + logger.printError('Error running $packageName. Manifest versionCode invalid'); + return null; + } + final int versionCode = int.tryParse(versionCodeAttr.value.substring(11)); + if (versionCode == null) { + logger.printError('Error running $packageName. Manifest versionCode invalid'); + return null; + } + + final Map> map = >{}; + map['package'] = {'name': packageName}; + map['version-code'] = {'name': versionCode.toString()}; + map['launchable-activity'] = {'name': activityName}; + + return ApkManifestData._(map); + } + + final Map> _data; + + @visibleForTesting + Map> get data => + UnmodifiableMapView>(_data); + + String get packageName => _data['package'] == null ? null : _data['package']['name']; + + String get versionCode => _data['version-code'] == null ? null : _data['version-code']['name']; + + String get launchableActivityName { + return _data['launchable-activity'] == null ? null : _data['launchable-activity']['name']; + } + + @override + String toString() => _data.toString(); +} diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index 37a8076975d..662a2b62ec6 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart @@ -4,119 +4,21 @@ // @dart = 2.8 -import 'dart:collection'; - import 'package:meta/meta.dart'; -import 'package:process/process.dart'; -import 'package:xml/xml.dart'; -import 'android/android_sdk.dart'; -import 'android/gradle.dart'; -import 'base/common.dart'; import 'base/context.dart'; import 'base/file_system.dart'; -import 'base/io.dart'; -import 'base/logger.dart'; -import 'base/process.dart'; -import 'base/user_messages.dart'; import 'build_info.dart'; -import 'fuchsia/application_package.dart'; -import 'globals.dart' as globals; -import 'ios/plist_parser.dart'; -import 'linux/application_package.dart'; -import 'macos/application_package.dart'; -import 'project.dart'; -import 'tester/flutter_tester.dart'; -import 'web/web_device.dart'; -import 'windows/application_package.dart'; - -class ApplicationPackageFactory { - ApplicationPackageFactory({ - @required AndroidSdk androidSdk, - @required ProcessManager processManager, - @required Logger logger, - @required UserMessages userMessages, - @required FileSystem fileSystem, - }) : _androidSdk = androidSdk, - _processManager = processManager, - _logger = logger, - _userMessages = userMessages, - _fileSystem = fileSystem, - _processUtils = ProcessUtils(logger: logger, processManager: processManager); +abstract class ApplicationPackageFactory { static ApplicationPackageFactory get instance => context.get(); - final AndroidSdk _androidSdk; - final ProcessManager _processManager; - final Logger _logger; - final ProcessUtils _processUtils; - final UserMessages _userMessages; - final FileSystem _fileSystem; - + /// Create an [ApplicationPackage] for the given platform. Future getPackageForPlatform( TargetPlatform platform, { BuildInfo buildInfo, File applicationBinary, - }) async { - switch (platform) { - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.android_x86: - if (applicationBinary == null) { - return AndroidApk.fromAndroidProject( - FlutterProject.current().android, - processManager: _processManager, - processUtils: _processUtils, - logger: _logger, - androidSdk: _androidSdk, - userMessages: _userMessages, - fileSystem: _fileSystem, - ); - } - return AndroidApk.fromApk( - applicationBinary, - processManager: _processManager, - logger: _logger, - androidSdk: _androidSdk, - userMessages: _userMessages, - ); - case TargetPlatform.ios: - return applicationBinary == null - ? await IOSApp.fromIosProject(FlutterProject.current().ios, buildInfo) - : IOSApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.tester: - return FlutterTesterApp.fromCurrentDirectory(globals.fs); - case TargetPlatform.darwin_x64: - return applicationBinary == null - ? MacOSApp.fromMacOSProject(FlutterProject.current().macos) - : MacOSApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.web_javascript: - if (!FlutterProject.current().web.existsSync()) { - return null; - } - return WebApplicationPackage(FlutterProject.current()); - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - return applicationBinary == null - ? LinuxApp.fromLinuxProject(FlutterProject.current().linux) - : LinuxApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.windows_x64: - return applicationBinary == null - ? WindowsApp.fromWindowsProject(FlutterProject.current().windows) - : WindowsApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - return applicationBinary == null - ? FuchsiaApp.fromFuchsiaProject(FlutterProject.current().fuchsia) - : FuchsiaApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.windows_uwp_x64: - throw UnsupportedError('Cannot build for windows_uwp_x64'); - } - assert(platform != null); - return null; - } + }); } abstract class ApplicationPackage { @@ -135,558 +37,3 @@ abstract class ApplicationPackage { @override String toString() => displayName ?? id; } - -/// An application package created from an already built Android APK. -class AndroidApk extends ApplicationPackage { - AndroidApk({ - String id, - @required this.file, - @required this.versionCode, - @required this.launchActivity, - }) : assert(file != null), - assert(launchActivity != null), - super(id: id); - - /// Creates a new AndroidApk from an existing APK. - /// - /// Returns `null` if the APK was invalid or any required tooling was missing. - factory AndroidApk.fromApk(File apk, { - @required AndroidSdk androidSdk, - @required ProcessManager processManager, - @required UserMessages userMessages, - @required Logger logger, - }) { - final String aaptPath = androidSdk?.latestVersion?.aaptPath; - if (aaptPath == null || !processManager.canRun(aaptPath)) { - logger.printError(userMessages.aaptNotFound); - return null; - } - - String apptStdout; - try { - apptStdout = globals.processUtils.runSync( - [ - aaptPath, - 'dump', - 'xmltree', - apk.path, - 'AndroidManifest.xml', - ], - throwOnError: true, - ).stdout.trim(); - } on ProcessException catch (error) { - globals.printError('Failed to extract manifest from APK: $error.'); - return null; - } - - final ApkManifestData data = ApkManifestData.parseFromXmlDump(apptStdout, logger); - - if (data == null) { - logger.printError('Unable to read manifest info from ${apk.path}.'); - return null; - } - - if (data.packageName == null || data.launchableActivityName == null) { - logger.printError('Unable to read manifest info from ${apk.path}.'); - return null; - } - - return AndroidApk( - id: data.packageName, - file: apk, - versionCode: int.tryParse(data.versionCode), - launchActivity: '${data.packageName}/${data.launchableActivityName}', - ); - } - - /// Path to the actual apk file. - final File file; - - /// The path to the activity that should be launched. - final String launchActivity; - - /// The version code of the APK. - final int versionCode; - - /// Creates a new AndroidApk based on the information in the Android manifest. - static Future fromAndroidProject(AndroidProject androidProject, { - @required AndroidSdk androidSdk, - @required ProcessManager processManager, - @required UserMessages userMessages, - @required ProcessUtils processUtils, - @required Logger logger, - @required FileSystem fileSystem, - }) async { - File apkFile; - - if (androidProject.isUsingGradle && androidProject.isSupportedVersion) { - apkFile = getApkDirectory(androidProject.parent).childFile('app.apk'); - if (apkFile.existsSync()) { - // Grab information from the .apk. The gradle build script might alter - // the application Id, so we need to look at what was actually built. - return AndroidApk.fromApk( - apkFile, - androidSdk: androidSdk, - processManager: processManager, - logger: logger, - userMessages: userMessages, - ); - } - // The .apk hasn't been built yet, so we work with what we have. The run - // command will grab a new AndroidApk after building, to get the updated - // IDs. - } else { - apkFile = fileSystem.file(fileSystem.path.join(getAndroidBuildDirectory(), 'app.apk')); - } - - final File manifest = androidProject.appManifestFile; - - if (!manifest.existsSync()) { - logger.printError('AndroidManifest.xml could not be found.'); - logger.printError('Please check ${manifest.path} for errors.'); - return null; - } - - final String manifestString = manifest.readAsStringSync(); - XmlDocument document; - try { - document = XmlDocument.parse(manifestString); - } on XmlParserException catch (exception) { - String manifestLocation; - if (androidProject.isUsingGradle) { - manifestLocation = fileSystem.path.join(androidProject.hostAppGradleRoot.path, 'app', 'src', 'main', 'AndroidManifest.xml'); - } else { - manifestLocation = fileSystem.path.join(androidProject.hostAppGradleRoot.path, 'AndroidManifest.xml'); - } - logger.printError('AndroidManifest.xml is not a valid XML document.'); - logger.printError('Please check $manifestLocation for errors.'); - throwToolExit('XML Parser error message: ${exception.toString()}'); - } - - final Iterable manifests = document.findElements('manifest'); - if (manifests.isEmpty) { - logger.printError('AndroidManifest.xml has no manifest element.'); - logger.printError('Please check ${manifest.path} for errors.'); - return null; - } - final String packageId = manifests.first.getAttribute('package'); - - String launchActivity; - for (final XmlElement activity in document.findAllElements('activity')) { - final String enabled = activity.getAttribute('android:enabled'); - if (enabled != null && enabled == 'false') { - continue; - } - - for (final XmlElement element in activity.findElements('intent-filter')) { - String actionName = ''; - String categoryName = ''; - for (final XmlNode node in element.children) { - if (node is! XmlElement) { - continue; - } - final XmlElement xmlElement = node as XmlElement; - final String name = xmlElement.getAttribute('android:name'); - if (name == 'android.intent.action.MAIN') { - actionName = name; - } else if (name == 'android.intent.category.LAUNCHER') { - categoryName = name; - } - } - if (actionName.isNotEmpty && categoryName.isNotEmpty) { - final String activityName = activity.getAttribute('android:name'); - launchActivity = '$packageId/$activityName'; - break; - } - } - } - - if (packageId == null || launchActivity == null) { - logger.printError('package identifier or launch activity not found.'); - logger.printError('Please check ${manifest.path} for errors.'); - return null; - } - - return AndroidApk( - id: packageId, - file: apkFile, - versionCode: null, - launchActivity: launchActivity, - ); - } - - @override - File get packagesFile => file; - - @override - String get name => file.basename; -} - -/// Tests whether a [Directory] is an iOS bundle directory. -bool _isBundleDirectory(Directory dir) => dir.path.endsWith('.app'); - -abstract class IOSApp extends ApplicationPackage { - IOSApp({@required String projectBundleId}) : super(id: projectBundleId); - - /// Creates a new IOSApp from an existing app bundle or IPA. - factory IOSApp.fromPrebuiltApp(FileSystemEntity applicationBinary) { - final FileSystemEntityType entityType = globals.fs.typeSync(applicationBinary.path); - if (entityType == FileSystemEntityType.notFound) { - globals.printError( - 'File "${applicationBinary.path}" does not exist. Use an app bundle or an ipa.'); - return null; - } - Directory bundleDir; - if (entityType == FileSystemEntityType.directory) { - final Directory directory = globals.fs.directory(applicationBinary); - if (!_isBundleDirectory(directory)) { - globals.printError('Folder "${applicationBinary.path}" is not an app bundle.'); - return null; - } - bundleDir = globals.fs.directory(applicationBinary); - } else { - // Try to unpack as an ipa. - final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_app.'); - globals.os.unzip(globals.fs.file(applicationBinary), tempDir); - final Directory payloadDir = globals.fs.directory( - globals.fs.path.join(tempDir.path, 'Payload'), - ); - if (!payloadDir.existsSync()) { - globals.printError( - 'Invalid prebuilt iOS ipa. Does not contain a "Payload" directory.'); - return null; - } - try { - bundleDir = payloadDir.listSync().whereType().singleWhere(_isBundleDirectory); - } on StateError { - globals.printError( - 'Invalid prebuilt iOS ipa. Does not contain a single app bundle.'); - return null; - } - } - final String plistPath = globals.fs.path.join(bundleDir.path, 'Info.plist'); - if (!globals.fs.file(plistPath).existsSync()) { - globals.printError('Invalid prebuilt iOS app. Does not contain Info.plist.'); - return null; - } - final String id = globals.plistParser.getValueFromFile( - plistPath, - PlistParser.kCFBundleIdentifierKey, - ); - if (id == null) { - globals.printError('Invalid prebuilt iOS app. Info.plist does not contain bundle identifier'); - return null; - } - - return PrebuiltIOSApp( - bundleDir: bundleDir, - bundleName: globals.fs.path.basename(bundleDir.path), - projectBundleId: id, - ); - } - - static Future fromIosProject(IosProject project, BuildInfo buildInfo) { - if (!globals.platform.isMacOS) { - return null; - } - if (!project.exists) { - // If the project doesn't exist at all the current hint to run flutter - // create is accurate. - return null; - } - if (!project.xcodeProject.existsSync()) { - globals.printError('Expected ios/Runner.xcodeproj but this file is missing.'); - return null; - } - if (!project.xcodeProjectInfoFile.existsSync()) { - globals.printError('Expected ios/Runner.xcodeproj/project.pbxproj but this file is missing.'); - return null; - } - return BuildableIOSApp.fromProject(project, buildInfo); - } - - @override - String get displayName => id; - - String get simulatorBundlePath; - - String get deviceBundlePath; - - /// Directory used by ios-deploy to store incremental installation metadata for - /// faster second installs. - Directory get appDeltaDirectory; -} - -class BuildableIOSApp extends IOSApp { - BuildableIOSApp(this.project, String projectBundleId, String hostAppBundleName) - : _hostAppBundleName = hostAppBundleName, - super(projectBundleId: projectBundleId); - - static Future fromProject(IosProject project, BuildInfo buildInfo) async { - final String projectBundleId = await project.productBundleIdentifier(buildInfo); - final String hostAppBundleName = await project.hostAppBundleName(buildInfo); - return BuildableIOSApp(project, projectBundleId, hostAppBundleName); - } - - final IosProject project; - - final String _hostAppBundleName; - - @override - String get name => _hostAppBundleName; - - @override - String get simulatorBundlePath => _buildAppPath('iphonesimulator'); - - @override - String get deviceBundlePath => _buildAppPath('iphoneos'); - - @override - Directory get appDeltaDirectory => globals.fs.directory(globals.fs.path.join(getIosBuildDirectory(), 'app-delta')); - - // Xcode uses this path for the final archive bundle location, - // not a top-level output directory. - // Specifying `build/ios/archive/Runner` will result in `build/ios/archive/Runner.xcarchive`. - String get archiveBundlePath - => globals.fs.path.join(getIosBuildDirectory(), 'archive', globals.fs.path.withoutExtension(_hostAppBundleName)); - - // The output xcarchive bundle path `build/ios/archive/Runner.xcarchive`. - String get archiveBundleOutputPath => - globals.fs.path.setExtension(archiveBundlePath, '.xcarchive'); - - String get ipaOutputPath => - globals.fs.path.join(getIosBuildDirectory(), 'ipa'); - - String _buildAppPath(String type) { - return globals.fs.path.join(getIosBuildDirectory(), type, _hostAppBundleName); - } -} - -class PrebuiltIOSApp extends IOSApp { - PrebuiltIOSApp({ - this.bundleDir, - this.bundleName, - @required String projectBundleId, - }) : super(projectBundleId: projectBundleId); - - final Directory bundleDir; - final String bundleName; - - @override - final Directory appDeltaDirectory = null; - - @override - String get name => bundleName; - - @override - String get simulatorBundlePath => _bundlePath; - - @override - String get deviceBundlePath => _bundlePath; - - String get _bundlePath => bundleDir.path; -} - -class _Entry { - _Element parent; - int level; -} - -class _Element extends _Entry { - _Element.fromLine(String line, _Element parent) { - // E: application (line=29) - final List parts = line.trimLeft().split(' '); - name = parts[1]; - level = line.length - line.trimLeft().length; - this.parent = parent; - children = <_Entry>[]; - } - - List<_Entry> children; - String name; - - void addChild(_Entry child) { - children.add(child); - } - - _Attribute firstAttribute(String name) { - return children.whereType<_Attribute>().firstWhere( - (_Attribute e) => e.key.startsWith(name), - orElse: () => null, - ); - } - - _Element firstElement(String name) { - return children.whereType<_Element>().firstWhere( - (_Element e) => e.name.startsWith(name), - orElse: () => null, - ); - } - - Iterable<_Element> allElements(String name) { - return children.whereType<_Element>().where((_Element e) => e.name.startsWith(name)); - } -} - -class _Attribute extends _Entry { - _Attribute.fromLine(String line, _Element parent) { - // A: android:label(0x01010001)="hello_world" (Raw: "hello_world") - const String attributePrefix = 'A: '; - final List keyVal = line - .substring(line.indexOf(attributePrefix) + attributePrefix.length) - .split('='); - key = keyVal[0]; - value = keyVal[1]; - level = line.length - line.trimLeft().length; - this.parent = parent; - } - - String key; - String value; -} - -class ApkManifestData { - ApkManifestData._(this._data); - - static bool isAttributeWithValuePresent(_Element baseElement, - String childElement, String attributeName, String attributeValue) { - final Iterable<_Element> allElements = baseElement.allElements(childElement); - for (final _Element oneElement in allElements) { - final String elementAttributeValue = oneElement - ?.firstAttribute(attributeName) - ?.value; - if (elementAttributeValue != null && - elementAttributeValue.startsWith(attributeValue)) { - return true; - } - } - return false; - } - - static ApkManifestData parseFromXmlDump(String data, Logger logger) { - if (data == null || data.trim().isEmpty) { - return null; - } - - final List lines = data.split('\n'); - assert(lines.length > 3); - - final int manifestLine = lines.indexWhere((String line) => line.contains('E: manifest')); - final _Element manifest = _Element.fromLine(lines[manifestLine], null); - _Element currentElement = manifest; - - for (final String line in lines.skip(manifestLine)) { - final String trimLine = line.trimLeft(); - final int level = line.length - trimLine.length; - - // Handle level out - while (currentElement.parent != null && level <= currentElement.level) { - currentElement = currentElement.parent; - } - - if (level > currentElement.level) { - switch (trimLine[0]) { - case 'A': - currentElement - .addChild(_Attribute.fromLine(line, currentElement)); - break; - case 'E': - final _Element element = _Element.fromLine(line, currentElement); - currentElement.addChild(element); - currentElement = element; - } - } - } - - final _Element application = manifest.firstElement('application'); - if (application == null) { - return null; - } - - final Iterable<_Element> activities = application.allElements('activity'); - - _Element launchActivity; - for (final _Element activity in activities) { - final _Attribute enabled = activity.firstAttribute('android:enabled'); - final Iterable<_Element> intentFilters = activity.allElements('intent-filter'); - final bool isEnabledByDefault = enabled == null; - final bool isExplicitlyEnabled = enabled != null && enabled.value.contains('0xffffffff'); - if (!(isEnabledByDefault || isExplicitlyEnabled)) { - continue; - } - - for (final _Element element in intentFilters) { - final bool isMainAction = isAttributeWithValuePresent( - element, 'action', 'android:name', '"android.intent.action.MAIN"'); - if (!isMainAction) { - continue; - } - final bool isLauncherCategory = isAttributeWithValuePresent( - element, 'category', 'android:name', - '"android.intent.category.LAUNCHER"'); - if (!isLauncherCategory) { - continue; - } - launchActivity = activity; - break; - } - if (launchActivity != null) { - break; - } - } - - final _Attribute package = manifest.firstAttribute('package'); - // "io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world") - final String packageName = package.value.substring(1, package.value.indexOf('" ')); - - if (launchActivity == null) { - logger.printError('Error running $packageName. Default activity not found'); - return null; - } - - final _Attribute nameAttribute = launchActivity.firstAttribute('android:name'); - // "io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity") - final String activityName = nameAttribute - .value.substring(1, nameAttribute.value.indexOf('" ')); - - // Example format: (type 0x10)0x1 - final _Attribute versionCodeAttr = manifest.firstAttribute('android:versionCode'); - if (versionCodeAttr == null) { - logger.printError('Error running $packageName. Manifest versionCode not found'); - return null; - } - if (!versionCodeAttr.value.startsWith('(type 0x10)')) { - logger.printError('Error running $packageName. Manifest versionCode invalid'); - return null; - } - final int versionCode = int.tryParse(versionCodeAttr.value.substring(11)); - if (versionCode == null) { - logger.printError('Error running $packageName. Manifest versionCode invalid'); - return null; - } - - final Map> map = >{}; - map['package'] = {'name': packageName}; - map['version-code'] = {'name': versionCode.toString()}; - map['launchable-activity'] = {'name': activityName}; - - return ApkManifestData._(map); - } - - final Map> _data; - - @visibleForTesting - Map> get data => - UnmodifiableMapView>(_data); - - String get packageName => _data['package'] == null ? null : _data['package']['name']; - - String get versionCode => _data['version-code'] == null ? null : _data['version-code']['name']; - - String get launchableActivityName { - return _data['launchable-activity'] == null ? null : _data['launchable-activity']['name']; - } - - @override - String toString() => _data.toString(); -} diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index d3fd27c0e02..5cd4adeafba 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -7,7 +7,6 @@ import 'package:file/file.dart'; import 'package:meta/meta.dart'; -import '../application_package.dart'; import '../base/analyze_size.dart'; import '../base/common.dart'; import '../base/logger.dart'; @@ -16,6 +15,7 @@ import '../base/utils.dart'; import '../build_info.dart'; import '../convert.dart'; import '../globals.dart' as globals; +import '../ios/application_package.dart'; import '../ios/mac.dart'; import '../runner/flutter_command.dart'; import 'build.dart'; diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 371406534da..232523646c5 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -37,6 +37,7 @@ import 'devtools_launcher.dart'; import 'doctor.dart'; import 'emulator.dart'; import 'features.dart'; +import 'flutter_application_package.dart'; import 'flutter_device_manager.dart'; import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools; import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts; @@ -117,7 +118,7 @@ Future runInContext( featureFlags: featureFlags, operatingSystemUtils: globals.os, ), - ApplicationPackageFactory: () => ApplicationPackageFactory( + ApplicationPackageFactory: () => FlutterApplicationPackageFactory( userMessages: globals.userMessages, processManager: globals.processManager, logger: globals.logger, diff --git a/packages/flutter_tools/lib/src/flutter_application_package.dart b/packages/flutter_tools/lib/src/flutter_application_package.dart new file mode 100644 index 00000000000..84d61959de3 --- /dev/null +++ b/packages/flutter_tools/lib/src/flutter_application_package.dart @@ -0,0 +1,116 @@ +// 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. + +// @dart = 2.8 + +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; + +import 'android/android_sdk.dart'; +import 'android/application_package.dart'; +import 'application_package.dart'; +import 'base/file_system.dart'; +import 'base/logger.dart'; +import 'base/process.dart'; +import 'base/user_messages.dart'; +import 'build_info.dart'; +import 'fuchsia/application_package.dart'; +import 'globals.dart' as globals; +import 'ios/application_package.dart'; +import 'linux/application_package.dart'; +import 'macos/application_package.dart'; +import 'project.dart'; +import 'tester/flutter_tester.dart'; +import 'web/web_device.dart'; +import 'windows/application_package.dart'; + +/// A package factory that supports all Flutter target platforms. +class FlutterApplicationPackageFactory extends ApplicationPackageFactory { + FlutterApplicationPackageFactory({ + @required AndroidSdk androidSdk, + @required ProcessManager processManager, + @required Logger logger, + @required UserMessages userMessages, + @required FileSystem fileSystem, + }) : _androidSdk = androidSdk, + _processManager = processManager, + _logger = logger, + _userMessages = userMessages, + _fileSystem = fileSystem, + _processUtils = ProcessUtils(logger: logger, processManager: processManager); + + + final AndroidSdk _androidSdk; + final ProcessManager _processManager; + final Logger _logger; + final ProcessUtils _processUtils; + final UserMessages _userMessages; + final FileSystem _fileSystem; + + @override + Future getPackageForPlatform( + TargetPlatform platform, { + BuildInfo buildInfo, + File applicationBinary, + }) async { + switch (platform) { + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.android_x86: + if (applicationBinary == null) { + return AndroidApk.fromAndroidProject( + FlutterProject.current().android, + processManager: _processManager, + processUtils: _processUtils, + logger: _logger, + androidSdk: _androidSdk, + userMessages: _userMessages, + fileSystem: _fileSystem, + ); + } + return AndroidApk.fromApk( + applicationBinary, + processManager: _processManager, + logger: _logger, + androidSdk: _androidSdk, + userMessages: _userMessages, + ); + case TargetPlatform.ios: + return applicationBinary == null + ? await IOSApp.fromIosProject(FlutterProject.current().ios, buildInfo) + : IOSApp.fromPrebuiltApp(applicationBinary); + case TargetPlatform.tester: + return FlutterTesterApp.fromCurrentDirectory(globals.fs); + case TargetPlatform.darwin_x64: + return applicationBinary == null + ? MacOSApp.fromMacOSProject(FlutterProject.current().macos) + : MacOSApp.fromPrebuiltApp(applicationBinary); + case TargetPlatform.web_javascript: + if (!FlutterProject.current().web.existsSync()) { + return null; + } + return WebApplicationPackage(FlutterProject.current()); + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + return applicationBinary == null + ? LinuxApp.fromLinuxProject(FlutterProject.current().linux) + : LinuxApp.fromPrebuiltApp(applicationBinary); + case TargetPlatform.windows_x64: + return applicationBinary == null + ? WindowsApp.fromWindowsProject(FlutterProject.current().windows) + : WindowsApp.fromPrebuiltApp(applicationBinary); + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + return applicationBinary == null + ? FuchsiaApp.fromFuchsiaProject(FlutterProject.current().fuchsia) + : FuchsiaApp.fromPrebuiltApp(applicationBinary); + case TargetPlatform.windows_uwp_x64: + throw UnsupportedError('Cannot build for windows_uwp_x64'); + } + assert(platform != null); + return null; + } +} diff --git a/packages/flutter_tools/lib/src/ios/application_package.dart b/packages/flutter_tools/lib/src/ios/application_package.dart new file mode 100644 index 00000000000..fef82b69011 --- /dev/null +++ b/packages/flutter_tools/lib/src/ios/application_package.dart @@ -0,0 +1,180 @@ +// 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. + +// @dart = 2.8 + +import 'package:meta/meta.dart'; + +import '../application_package.dart'; +import '../base/file_system.dart'; +import '../build_info.dart'; +import '../globals.dart' as globals; +import '../project.dart'; +import 'plist_parser.dart'; + + +/// Tests whether a [Directory] is an iOS bundle directory. +bool _isBundleDirectory(Directory dir) => dir.path.endsWith('.app'); + +abstract class IOSApp extends ApplicationPackage { + IOSApp({@required String projectBundleId}) : super(id: projectBundleId); + + /// Creates a new IOSApp from an existing app bundle or IPA. + factory IOSApp.fromPrebuiltApp(FileSystemEntity applicationBinary) { + final FileSystemEntityType entityType = globals.fs.typeSync(applicationBinary.path); + if (entityType == FileSystemEntityType.notFound) { + globals.printError( + 'File "${applicationBinary.path}" does not exist. Use an app bundle or an ipa.'); + return null; + } + Directory bundleDir; + if (entityType == FileSystemEntityType.directory) { + final Directory directory = globals.fs.directory(applicationBinary); + if (!_isBundleDirectory(directory)) { + globals.printError('Folder "${applicationBinary.path}" is not an app bundle.'); + return null; + } + bundleDir = globals.fs.directory(applicationBinary); + } else { + // Try to unpack as an ipa. + final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_app.'); + globals.os.unzip(globals.fs.file(applicationBinary), tempDir); + final Directory payloadDir = globals.fs.directory( + globals.fs.path.join(tempDir.path, 'Payload'), + ); + if (!payloadDir.existsSync()) { + globals.printError( + 'Invalid prebuilt iOS ipa. Does not contain a "Payload" directory.'); + return null; + } + try { + bundleDir = payloadDir.listSync().whereType().singleWhere(_isBundleDirectory); + } on StateError { + globals.printError( + 'Invalid prebuilt iOS ipa. Does not contain a single app bundle.'); + return null; + } + } + final String plistPath = globals.fs.path.join(bundleDir.path, 'Info.plist'); + if (!globals.fs.file(plistPath).existsSync()) { + globals.printError('Invalid prebuilt iOS app. Does not contain Info.plist.'); + return null; + } + final String id = globals.plistParser.getValueFromFile( + plistPath, + PlistParser.kCFBundleIdentifierKey, + ); + if (id == null) { + globals.printError('Invalid prebuilt iOS app. Info.plist does not contain bundle identifier'); + return null; + } + + return PrebuiltIOSApp( + bundleDir: bundleDir, + bundleName: globals.fs.path.basename(bundleDir.path), + projectBundleId: id, + ); + } + + static Future fromIosProject(IosProject project, BuildInfo buildInfo) { + if (!globals.platform.isMacOS) { + return null; + } + if (!project.exists) { + // If the project doesn't exist at all the current hint to run flutter + // create is accurate. + return null; + } + if (!project.xcodeProject.existsSync()) { + globals.printError('Expected ios/Runner.xcodeproj but this file is missing.'); + return null; + } + if (!project.xcodeProjectInfoFile.existsSync()) { + globals.printError('Expected ios/Runner.xcodeproj/project.pbxproj but this file is missing.'); + return null; + } + return BuildableIOSApp.fromProject(project, buildInfo); + } + + @override + String get displayName => id; + + String get simulatorBundlePath; + + String get deviceBundlePath; + + /// Directory used by ios-deploy to store incremental installation metadata for + /// faster second installs. + Directory get appDeltaDirectory; +} + +class BuildableIOSApp extends IOSApp { + BuildableIOSApp(this.project, String projectBundleId, String hostAppBundleName) + : _hostAppBundleName = hostAppBundleName, + super(projectBundleId: projectBundleId); + + static Future fromProject(IosProject project, BuildInfo buildInfo) async { + final String projectBundleId = await project.productBundleIdentifier(buildInfo); + final String hostAppBundleName = await project.hostAppBundleName(buildInfo); + return BuildableIOSApp(project, projectBundleId, hostAppBundleName); + } + + final IosProject project; + + final String _hostAppBundleName; + + @override + String get name => _hostAppBundleName; + + @override + String get simulatorBundlePath => _buildAppPath('iphonesimulator'); + + @override + String get deviceBundlePath => _buildAppPath('iphoneos'); + + @override + Directory get appDeltaDirectory => globals.fs.directory(globals.fs.path.join(getIosBuildDirectory(), 'app-delta')); + + // Xcode uses this path for the final archive bundle location, + // not a top-level output directory. + // Specifying `build/ios/archive/Runner` will result in `build/ios/archive/Runner.xcarchive`. + String get archiveBundlePath + => globals.fs.path.join(getIosBuildDirectory(), 'archive', globals.fs.path.withoutExtension(_hostAppBundleName)); + + // The output xcarchive bundle path `build/ios/archive/Runner.xcarchive`. + String get archiveBundleOutputPath => + globals.fs.path.setExtension(archiveBundlePath, '.xcarchive'); + + String get ipaOutputPath => + globals.fs.path.join(getIosBuildDirectory(), 'ipa'); + + String _buildAppPath(String type) { + return globals.fs.path.join(getIosBuildDirectory(), type, _hostAppBundleName); + } +} + +class PrebuiltIOSApp extends IOSApp { + PrebuiltIOSApp({ + this.bundleDir, + this.bundleName, + @required String projectBundleId, + }) : super(projectBundleId: projectBundleId); + + final Directory bundleDir; + final String bundleName; + + @override + final Directory appDeltaDirectory = null; + + @override + String get name => bundleName; + + @override + String get simulatorBundlePath => _bundlePath; + + @override + String get deviceBundlePath => _bundlePath; + + String get _bundlePath => bundleDir.path; +} diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 20ee1d59caf..50f7dab2461 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -10,7 +10,6 @@ import 'package:meta/meta.dart'; import 'package:process/process.dart'; import 'package:vm_service/vm_service.dart' as vm_service; -import '../application_package.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; @@ -27,6 +26,7 @@ import '../macos/xcode.dart'; import '../project.dart'; import '../protocol_discovery.dart'; import '../vmservice.dart'; +import 'application_package.dart'; import 'ios_deploy.dart'; import 'ios_workflow.dart'; import 'iproxy.dart'; diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 88773fbca1d..2008da8b6c7 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -7,7 +7,6 @@ import 'package:meta/meta.dart'; import 'package:process/process.dart'; -import '../application_package.dart'; import '../artifacts.dart'; import '../base/common.dart'; import '../base/file_system.dart'; @@ -24,6 +23,7 @@ import '../macos/cocoapod_utils.dart'; import '../macos/xcode.dart'; import '../project.dart'; import '../reporting/reporting.dart'; +import 'application_package.dart'; import 'code_signing.dart'; import 'devices.dart'; import 'migrations/project_base_configuration_migration.dart'; diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index f3f0273f6ed..e9d12f5ebd3 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -26,6 +26,7 @@ import '../globals.dart' as globals; import '../macos/xcode.dart'; import '../project.dart'; import '../protocol_discovery.dart'; +import 'application_package.dart'; import 'mac.dart'; import 'plist_parser.dart'; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart index f847a6fae7e..6ff695b622d 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart @@ -5,11 +5,13 @@ // @dart = 2.8 import 'package:file/file.dart'; +import 'package:flutter_tools/src/android/application_package.dart'; import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/install.dart'; +import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:mockito/mockito.dart'; import '../../src/common.dart'; diff --git a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart index 3b8746f797d..0dfb7cfa9eb 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart @@ -7,7 +7,7 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_device.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; -import 'package:flutter_tools/src/application_package.dart'; +import 'package:flutter_tools/src/android/application_package.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; diff --git a/packages/flutter_tools/test/general.shard/android/android_install_test.dart b/packages/flutter_tools/test/general.shard/android/android_install_test.dart index 5617a4328cb..2beb28a0bcc 100644 --- a/packages/flutter_tools/test/general.shard/android/android_install_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_install_test.dart @@ -7,7 +7,7 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_device.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; -import 'package:flutter_tools/src/application_package.dart'; +import 'package:flutter_tools/src/android/application_package.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; diff --git a/packages/flutter_tools/test/general.shard/application_package_test.dart b/packages/flutter_tools/test/general.shard/application_package_test.dart index 8e962082b9f..a44b1ccd097 100644 --- a/packages/flutter_tools/test/general.shard/application_package_test.dart +++ b/packages/flutter_tools/test/general.shard/application_package_test.dart @@ -7,6 +7,7 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; +import 'package:flutter_tools/src/android/application_package.dart'; import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -17,6 +18,7 @@ import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/fuchsia/application_package.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:mockito/mockito.dart'; diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart index d1ab8ae1912..0cd539c337f 100644 --- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:file/file.dart'; import 'package:file/memory.dart'; -import 'package:flutter_tools/src/application_package.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'; @@ -19,6 +18,7 @@ import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device_port_forwader.dart'; +import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/ios_workflow.dart'; diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart index be15ee44be3..b0da4cda625 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart @@ -5,7 +5,6 @@ // @dart = 2.8 import 'package:file/memory.dart'; -import 'package:flutter_tools/src/application_package.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'; @@ -13,6 +12,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart index 3ee851850f6..c1e6b8f1f9c 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart @@ -6,7 +6,6 @@ import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; -import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -14,6 +13,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart index 12b8f7c5428..3f2a94a6a72 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart @@ -7,7 +7,6 @@ import 'dart:async'; import 'package:file/memory.dart'; -import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -16,6 +15,7 @@ import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device_port_forwader.dart'; +import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; diff --git a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart index 461ae934b79..fb5ff6986aa 100644 --- a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart @@ -5,7 +5,6 @@ // @dart = 2.8 import 'package:file/memory.dart'; -import 'package:flutter_tools/src/application_package.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'; @@ -15,6 +14,7 @@ import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device_port_forwader.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/macos/xcode.dart';