From c8c55b409871cd4ba31e04cce77e1f3919cf8e7b Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Mon, 4 May 2020 11:31:08 -0700 Subject: [PATCH] Check Xcode build setting FULL_PRODUCT_NAME for bundle name (#55799) --- dev/bots/deploy_gallery.sh | 8 +- dev/devicelab/lib/tasks/perf_tests.dart | 10 +- .../ios/Runner.xcodeproj/project.pbxproj | 12 +- .../xcshareddata/xcschemes/Runner.xcscheme | 16 +- .../flutter_gallery/ios/Runner/Info.plist | 2 +- .../lib/src/application_package.dart | 14 +- packages/flutter_tools/lib/src/ios/mac.dart | 2 +- .../flutter_tools/lib/src/ios/simulators.dart | 18 +- packages/flutter_tools/lib/src/project.dart | 62 +++- .../ios_device_start_nonprebuilt_test.dart | 331 ++++++++++-------- .../test/general.shard/ios/mac_test.dart | 2 +- .../general.shard/ios/simulators_test.dart | 26 +- .../test/general.shard/project_test.dart | 33 ++ packages/flutter_tools/test/src/mocks.dart | 5 +- 14 files changed, 319 insertions(+), 222 deletions(-) diff --git a/dev/bots/deploy_gallery.sh b/dev/bots/deploy_gallery.sh index b4c5c8a05c..cf33eb3021 100755 --- a/dev/bots/deploy_gallery.sh +++ b/dev/bots/deploy_gallery.sh @@ -73,15 +73,15 @@ elif [[ "$OS" == "darwin" ]]; then exit 1 fi - if [[ ! -d "build/ios/iphoneos/Runner.app/Frameworks/App.framework/flutter_assets" ]]; then + if [[ ! -d "build/ios/iphoneos/Flutter Gallery.app/Frameworks/App.framework/flutter_assets" ]]; then echo "Error: flutter_assets not assembled" exit 1 fi if [[ - -d "build/ios/iphoneos/Runner.app/Frameworks/App.framework/flutter_assets/isolate_snapshot_data" || - -d "build/ios/iphoneos/Runner.app/Frameworks/App.framework/flutter_assets/kernel_blob.bin" || - -d "build/ios/iphoneos/Runner.app/Frameworks/App.framework/flutter_assets/vm_snapshot_data" + -d "build/ios/iphoneos/Flutter Gallery.app/Frameworks/App.framework/flutter_assets/isolate_snapshot_data" || + -d "build/ios/iphoneos/Flutter Gallery.app/Frameworks/App.framework/flutter_assets/kernel_blob.bin" || + -d "build/ios/iphoneos/Flutter Gallery.app/Frameworks/App.framework/flutter_assets/vm_snapshot_data" ]]; then echo "Error: compiled debug version of app with --release flag" exit 1 diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index 9194be69ce..9d577c22e1 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -451,7 +451,15 @@ class CompileTest { watch.start(); await flutter('build', options: options); watch.stop(); - final String appPath = '$cwd/build/ios/Release-iphoneos/Runner.app/'; + final Directory appBuildDirectory = dir(path.join(cwd, 'build/ios/Release-iphoneos')); + final Directory appBundle = appBuildDirectory + .listSync() + .whereType() + .singleWhere((Directory directory) => path.extension(directory.path) == '.app', orElse: () => null); + if (appBundle == null) { + throw 'Failed to find app bundle in ${appBuildDirectory.path}'; + } + final String appPath = appBundle.path; // IPAs are created manually, https://flutter.dev/ios-release/ await exec('tar', ['-zcf', 'build/app.ipa', appPath]); releaseSizeInBytes = await file('$cwd/build/app.ipa').length(); diff --git a/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/project.pbxproj b/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/project.pbxproj index 7d31ab0d39..06732c34c4 100644 --- a/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/project.pbxproj +++ b/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/project.pbxproj @@ -40,7 +40,7 @@ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146EE1CF9000F007C117D /* Flutter Gallery.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Flutter Gallery.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -88,7 +88,7 @@ 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, + 97C146EE1CF9000F007C117D /* Flutter Gallery.app */, ); name = Products; sourceTree = ""; @@ -157,7 +157,7 @@ ); name = Runner; productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productReference = 97C146EE1CF9000F007C117D /* Flutter Gallery.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -369,7 +369,7 @@ "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = io.flutter.examples.gallery; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "Flutter Gallery"; }; name = Profile; }; @@ -494,7 +494,7 @@ "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = io.flutter.examples.gallery; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "Flutter Gallery"; }; name = Debug; }; @@ -515,7 +515,7 @@ "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = io.flutter.examples.gallery; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "Flutter Gallery"; }; name = Release; }; diff --git a/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfdb..0ad117603b 100644 --- a/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -27,19 +27,17 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + - - diff --git a/dev/integration_tests/flutter_gallery/ios/Runner/Info.plist b/dev/integration_tests/flutter_gallery/ios/Runner/Info.plist index 026d41a2d0..0655da2090 100644 --- a/dev/integration_tests/flutter_gallery/ios/Runner/Info.plist +++ b/dev/integration_tests/flutter_gallery/ios/Runner/Info.plist @@ -11,7 +11,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - Flutter Gallery + $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index 251e856074..cd4fd1c1cd 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart @@ -357,18 +357,22 @@ abstract class IOSApp extends ApplicationPackage { } class BuildableIOSApp extends IOSApp { - BuildableIOSApp(this.project, String projectBundleId) - : super(projectBundleId: projectBundleId); + BuildableIOSApp(this.project, String projectBundleId, String hostAppBundleName) + : _hostAppBundleName = hostAppBundleName, + super(projectBundleId: projectBundleId); static Future fromProject(IosProject project) async { final String projectBundleId = await project.productBundleIdentifier; - return BuildableIOSApp(project, projectBundleId); + final String hostAppBundleName = await project.hostAppBundleName; + return BuildableIOSApp(project, projectBundleId, hostAppBundleName); } final IosProject project; + final String _hostAppBundleName; + @override - String get name => project.hostAppBundleName; + String get name => _hostAppBundleName; @override String get simulatorBundlePath => _buildAppPath('iphonesimulator'); @@ -377,7 +381,7 @@ class BuildableIOSApp extends IOSApp { String get deviceBundlePath => _buildAppPath('iphoneos'); String _buildAppPath(String type) { - return globals.fs.path.join(getIosBuildDirectory(), type, name); + return globals.fs.path.join(getIosBuildDirectory(), type, _hostAppBundleName); } } diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 9c07e784f8..d3d6e92da3 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -632,7 +632,7 @@ bool upgradePbxProjWithFlutterAssets(IosProject project, Logger logger) { final Match match = oldAssets.firstMatch(line); if (match != null) { if (printedStatuses.add(match.group(1))) { - logger.printStatus('Removing obsolete reference to ${match.group(1)} from ${project.hostAppBundleName}'); + logger.printStatus('Removing obsolete reference to ${match.group(1)} from ${project.xcodeProject?.basename}'); } } else { buffer.writeln(line); diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 0ac16acc98..e63d1e219f 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -685,7 +685,7 @@ class _IOSSimulatorLogReader extends DeviceLogReader { // Match the log prefix (in order to shorten it): // * Xcode 8: Sep 13 15:28:51 cbracken-macpro localhost Runner[37195]: (Flutter) Observatory listening on http://127.0.0.1:57701/ // * Xcode 9: 2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) Observatory listening on http://127.0.0.1:57701/ - static final RegExp _mapRegex = RegExp(r'\S+ +\S+ +\S+ +(\S+ +)?(\S+)\[\d+\]\)?: (\(.*?\))? *(.*)$'); + static final RegExp _mapRegex = RegExp(r'\S+ +\S+ +(?:\S+) (.+?(?=\[))\[\d+\]\)?: (\(.*?\))? *(.*)$'); // Jan 31 19:23:28 --- last message repeated 1 time --- static final RegExp _lastMessageSingleRegex = RegExp(r'\S+ +\S+ +\S+ --- last message repeated 1 time ---$'); @@ -700,12 +700,16 @@ class _IOSSimulatorLogReader extends DeviceLogReader { String _filterDeviceLine(String string) { final Match match = _mapRegex.matchAsPrefix(string); if (match != null) { - final String category = match.group(2); - final String tag = match.group(3); - final String content = match.group(4); - // Filter out non-Flutter originated noise from the engine. - if (_appName != null && category != _appName) { + // The category contains the text between the date and the PID. Depending on which version of iOS being run, + // it can contain "hostname App Name" or just "App Name". + final String category = match.group(1); + final String tag = match.group(2); + final String content = match.group(3); + + // Filter out log lines from an app other than this one (category doesn't match the app name). + // If the hostname is included in the category, check that it doesn't end with the app name. + if (_appName != null && !category.endsWith(_appName)) { return null; } @@ -725,7 +729,7 @@ class _IOSSimulatorLogReader extends DeviceLogReader { if (_appName == null) { return '$category: $content'; - } else if (category == _appName) { + } else if (category == _appName || category.endsWith(' $_appName')) { return content; } diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 7ec69d04ba..77cd01409f 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -341,7 +341,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$'''); static const String _productBundleIdVariable = r'$(PRODUCT_BUNDLE_IDENTIFIER)'; - static const String _hostAppBundleName = 'Runner'; + static const String _hostAppProjectName = 'Runner'; Directory get ephemeralDirectory => parent.directory.childDirectory('.ios'); Directory get _editableDirectory => parent.directory.childDirectory('ios'); @@ -362,9 +362,6 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { /// a Flutter module with an editable host app. Directory get _flutterLibRoot => isModule ? ephemeralDirectory : _editableDirectory; - /// The bundle name of the host app, `Runner.app`. - String get hostAppBundleName => '$_hostAppBundleName.app'; - /// True, if the parent Flutter project is a module project. bool get isModule => parent.isModule; @@ -387,19 +384,19 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock'); /// The default 'Info.plist' file of the host app. The developer can change this location in Xcode. - File get defaultHostInfoPlist => hostAppRoot.childDirectory(_hostAppBundleName).childFile('Info.plist'); + File get defaultHostInfoPlist => hostAppRoot.childDirectory(_hostAppProjectName).childFile('Info.plist'); @override Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks'); @override - Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppBundleName.xcodeproj'); + Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppProjectName.xcodeproj'); @override File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); @override - Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppBundleName.xcworkspace'); + Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppProjectName.xcworkspace'); /// Xcode workspace shared data directory for the host app. Directory get xcodeWorkspaceSharedData => xcodeWorkspace.childDirectory('xcshareddata'); @@ -414,7 +411,11 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { /// The product bundle identifier of the host app, or null if not set or if /// iOS tooling needed to read it is not installed. - Future get productBundleIdentifier async { + Future get productBundleIdentifier async => + _productBundleIdentifier ??= await _parseProductBundleIdentifier(); + String _productBundleIdentifier; + + Future _parseProductBundleIdentifier() async { String fromPlist; final File defaultInfoPlist = defaultHostInfoPlist; // Users can change the location of the Info.plist. @@ -456,28 +457,51 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { return null; } + /// The bundle name of the host app, `My App.app`. + Future get hostAppBundleName async => + _hostAppBundleName ??= await _parseHostAppBundleName(); + String _hostAppBundleName; + + Future _parseHostAppBundleName() async { + // The product name and bundle name are derived from the display name, which the user + // is instructed to change in Xcode as part of deploying to the App Store. + // https://flutter.dev/docs/deployment/ios#review-xcode-project-settings + // The only source of truth for the name is Xcode's interpretation of the build settings. + String productName; + if (globals.xcodeProjectInterpreter.isInstalled) { + final Map xcodeBuildSettings = await buildSettings; + if (xcodeBuildSettings != null) { + productName = xcodeBuildSettings['FULL_PRODUCT_NAME']; + } + } + if (productName == null) { + globals.printTrace('FULL_PRODUCT_NAME not present, defaulting to $_hostAppProjectName'); + } + return productName ?? '$_hostAppProjectName.app'; + } + /// The build settings for the host app of this project, as a detached map. /// /// Returns null, if iOS tooling is unavailable. - Future> get buildSettings async { + Future> get buildSettings async => + _buildSettings ??= await _xcodeProjectBuildSettings(); + Map _buildSettings; + + Future> _xcodeProjectBuildSettings() async { if (!globals.xcodeProjectInterpreter.isInstalled) { return null; } - Map buildSettings = _buildSettings; - buildSettings ??= await globals.xcodeProjectInterpreter.getBuildSettings( + final Map buildSettings = await globals.xcodeProjectInterpreter.getBuildSettings( xcodeProject.path, - _hostAppBundleName, + _hostAppProjectName, ); if (buildSettings != null && buildSettings.isNotEmpty) { // No timeouts, flakes, or errors. - _buildSettings = buildSettings; return buildSettings; } return null; } - Map _buildSettings; - Future ensureReadyForPlatformSpecificTooling() async { await _regenerateFromTemplateIfNeeded(); if (!_flutterLibRoot.existsSync()) { @@ -614,7 +638,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ? _flutterLibRoot .childDirectory('Flutter') .childDirectory('FlutterPluginRegistrant') - : hostAppRoot.childDirectory(_hostAppBundleName); + : hostAppRoot.childDirectory(_hostAppProjectName); } Future _overwriteFromTemplate(String path, Directory target) async { @@ -888,7 +912,7 @@ class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject { @override String get pluginConfigKey => MacOSPlugin.kConfigKey; - static const String _hostAppBundleName = 'Runner'; + static const String _hostAppProjectName = 'Runner'; @override bool existsSync() => _macOSDirectory.existsSync(); @@ -932,13 +956,13 @@ class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject { File get podManifestLock => _macOSDirectory.childDirectory('Pods').childFile('Manifest.lock'); @override - Directory get xcodeProject => _macOSDirectory.childDirectory('$_hostAppBundleName.xcodeproj'); + Directory get xcodeProject => _macOSDirectory.childDirectory('$_hostAppProjectName.xcodeproj'); @override File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); @override - Directory get xcodeWorkspace => _macOSDirectory.childDirectory('$_hostAppBundleName.xcworkspace'); + Directory get xcodeWorkspace => _macOSDirectory.childDirectory('$_hostAppProjectName.xcworkspace'); @override Directory get symlinks => ephemeralDirectory.childDirectory('.symlinks'); 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 cba8491948..a0584e44e0 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 @@ -13,6 +13,8 @@ import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/mac.dart'; +import 'package:flutter_tools/src/ios/xcodeproj.dart'; +import 'package:flutter_tools/src/macos/xcode.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; @@ -64,67 +66,34 @@ final FakePlatform macPlatform = FakePlatform( ); void main() { - FileSystem fileSystem; - FakeProcessManager processManager; - BufferLogger logger; + group('IOSDevice.startApp succeeds in release mode', () { + FileSystem fileSystem; + FakeProcessManager processManager; + BufferLogger logger; + MockXcode mockXcode; + MockXcodeProjectInterpreter mockXcodeProjectInterpreter; - setUp(() { - logger = BufferLogger.test(); - fileSystem = MemoryFileSystem.test(); - processManager = FakeProcessManager.list([]); - }); + setUp(() { + logger = BufferLogger.test(); + fileSystem = MemoryFileSystem.test(); + processManager = FakeProcessManager.list([]); - testUsingContext('IOSDevice.startApp succeeds in release mode with buildable app', () async { - final IOSDevice iosDevice = setUpIOSDevice( - fileSystem: fileSystem, - processManager: processManager, - logger: logger, - ); - setUpIOSProject(fileSystem); - final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); - final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter'); + mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); + when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); + when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( + (_) { + return Future.value(XcodeProjectInfo( + ['Runner'], + ['Debug', 'Release'], + ['Runner'], + )); + } + ); + mockXcode = MockXcode(); + when(mockXcode.isVersionSatisfactory).thenReturn(true); + }); - processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject))); - processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); - processManager.addCommand(const FakeCommand(command: [...kRunReleaseArgs, '-showBuildSettings'])); - processManager.addCommand(FakeCommand( - command: [ - 'ios-deploy', - '--id', - '123', - '--bundle', - 'build/ios/iphoneos/Runner.app', - '--no-wifi', - '--justlaunch', - '--args', - const [ - '--enable-dart-profiling', - '--enable-service-port-fallback', - '--disable-service-auth-codes', - '--observatory-port=53781', - ].join(' ') - ]) - ); - - final LaunchResult launchResult = await iosDevice.startApp( - buildableIOSApp, - debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), - platformArgs: {}, - ); - - expect(launchResult.started, true); - expect(processManager.hasRemainingExpectations, false); - }, overrides: { - ProcessManager: () => processManager, - FileSystem: () => fileSystem, - Logger: () => logger, - Platform: () => macPlatform, - }); - - testUsingContext('IOSDevice.startApp succeeds in release mode with buildable ' - 'app with flaky buildSettings call', () async { - LaunchResult launchResult; - FakeAsync().run((FakeAsync time) { + testUsingContext('with buildable app', () async { final IOSDevice iosDevice = setUpIOSDevice( fileSystem: fileSystem, processManager: processManager, @@ -132,29 +101,18 @@ void main() { ); setUpIOSProject(fileSystem); final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); - final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter'); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject))); processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); - // The first showBuildSettings call should timeout. - processManager.addCommand( - const FakeCommand( - command: [...kRunReleaseArgs, '-showBuildSettings'], - duration: Duration(minutes: 5), // this is longer than the timeout of 1 minute. - )); - // The second call succeedes and is made after the first times out. - processManager.addCommand( - const FakeCommand( - command: [...kRunReleaseArgs, '-showBuildSettings'], - exitCode: 0, - )); + processManager.addCommand(const FakeCommand(command: [...kRunReleaseArgs, '-showBuildSettings'])); processManager.addCommand(FakeCommand( command: [ 'ios-deploy', '--id', '123', '--bundle', - 'build/ios/iphoneos/Runner.app', + 'build/ios/iphoneos/My Super Awesome App.app', '--no-wifi', '--justlaunch', '--args', @@ -167,94 +125,160 @@ void main() { ]) ); - iosDevice.startApp( + final LaunchResult launchResult = await iosDevice.startApp( buildableIOSApp, debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), platformArgs: {}, - ).then((LaunchResult result) { - launchResult = result; - }); + ); - // Elapse duration for process timeout. - time.flushMicrotasks(); - time.elapse(const Duration(minutes: 1)); - - // Elapse duration for overall process timer. - time.flushMicrotasks(); - time.elapse(const Duration(minutes: 5)); - - time.flushTimers(); + expect(launchResult.started, true); + expect(processManager.hasRemainingExpectations, false); + }, overrides: { + ProcessManager: () => processManager, + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, + Xcode: () => mockXcode, }); - expect(launchResult?.started, true); - expect(processManager.hasRemainingExpectations, false); - }, overrides: { - ProcessManager: () => processManager, - FileSystem: () => fileSystem, - Logger: () => logger, - Platform: () => macPlatform, - }); + testUsingContext('with flaky buildSettings call', () async { + LaunchResult launchResult; + FakeAsync().run((FakeAsync time) { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: processManager, + logger: logger, + ); + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); - testUsingContext('IOSDevice.startApp succeeds in release mode with buildable ' - 'app with concurrent build failure', () async { - final IOSDevice iosDevice = setUpIOSDevice( - fileSystem: fileSystem, - processManager: processManager, - logger: logger, - ); - setUpIOSProject(fileSystem); - final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); - final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter'); + processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject))); + processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); + // The first showBuildSettings call should timeout. + processManager.addCommand( + const FakeCommand( + command: [...kRunReleaseArgs, '-showBuildSettings'], + duration: Duration(minutes: 5), // this is longer than the timeout of 1 minute. + )); + // The second call succeedes and is made after the first times out. + processManager.addCommand( + const FakeCommand( + command: [...kRunReleaseArgs, '-showBuildSettings'], + exitCode: 0, + )); + processManager.addCommand(FakeCommand( + command: [ + 'ios-deploy', + '--id', + '123', + '--bundle', + 'build/ios/iphoneos/My Super Awesome App.app', + '--no-wifi', + '--justlaunch', + '--args', + const [ + '--enable-dart-profiling', + '--enable-service-port-fallback', + '--disable-service-auth-codes', + '--observatory-port=53781', + ].join(' ') + ]) + ); - processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject))); - // The first xcrun call should fail with a - // concurrent build exception. - processManager.addCommand( - const FakeCommand( - command: kRunReleaseArgs, - exitCode: 1, - stdout: kConcurrentBuildErrorMessage, - )); - processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); - processManager.addCommand( - const FakeCommand( - command: [...kRunReleaseArgs, '-showBuildSettings'], - exitCode: 0, - )); - processManager.addCommand(FakeCommand( - command: [ - 'ios-deploy', - '--id', - '123', - '--bundle', - 'build/ios/iphoneos/Runner.app', - '--no-wifi', - '--justlaunch', - '--args', - const [ - '--enable-dart-profiling', - '--enable-service-port-fallback', - '--disable-service-auth-codes', - '--observatory-port=53781', - ].join(' ') - ]) - ); + iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), + platformArgs: {}, + ).then((LaunchResult result) { + launchResult = result; + }); - final LaunchResult launchResult = await iosDevice.startApp( - buildableIOSApp, - debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), - platformArgs: {}, - ); + // Elapse duration for process timeout. + time.flushMicrotasks(); + time.elapse(const Duration(minutes: 1)); - expect(logger.statusText, - contains('Xcode build failed due to concurrent builds, will retry in 2 seconds')); - expect(launchResult.started, true); - expect(processManager.hasRemainingExpectations, false); - }, overrides: { - ProcessManager: () => processManager, - FileSystem: () => fileSystem, - Logger: () => logger, - Platform: () => macPlatform, + // Elapse duration for overall process timer. + time.flushMicrotasks(); + time.elapse(const Duration(minutes: 5)); + + time.flushTimers(); + }); + + expect(launchResult?.started, true); + expect(processManager.hasRemainingExpectations, false); + }, overrides: { + ProcessManager: () => processManager, + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, + Xcode: () => mockXcode, + }); + + testUsingContext('with concurrent build failures', () async { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: processManager, + logger: logger, + ); + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + + processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject))); + // The first xcrun call should fail with a + // concurrent build exception. + processManager.addCommand( + const FakeCommand( + command: kRunReleaseArgs, + exitCode: 1, + stdout: kConcurrentBuildErrorMessage, + )); + processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); + processManager.addCommand( + const FakeCommand( + command: [...kRunReleaseArgs, '-showBuildSettings'], + exitCode: 0, + )); + processManager.addCommand(FakeCommand( + command: [ + 'ios-deploy', + '--id', + '123', + '--bundle', + 'build/ios/iphoneos/My Super Awesome App.app', + '--no-wifi', + '--justlaunch', + '--args', + const [ + '--enable-dart-profiling', + '--enable-service-port-fallback', + '--disable-service-auth-codes', + '--observatory-port=53781', + ].join(' ') + ]) + ); + + final LaunchResult launchResult = await iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), + platformArgs: {}, + ); + + expect(logger.statusText, + contains('Xcode build failed due to concurrent builds, will retry in 2 seconds')); + expect(launchResult.started, true); + expect(processManager.hasRemainingExpectations, false); + }, overrides: { + ProcessManager: () => processManager, + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, + Xcode: () => mockXcode, + }); }); } @@ -263,10 +287,9 @@ void setUpIOSProject(FileSystem fileSystem) { fileSystem.file('.packages').writeAsStringSync('\n'); fileSystem.directory('ios').createSync(); fileSystem.directory('ios/Runner.xcworkspace').createSync(); - fileSystem.directory('ios/Runner.xcodeproj').createSync(); - fileSystem.file('ios/Runner.xcodeproj/project.pbxproj').createSync(); + fileSystem.file('ios/Runner.xcodeproj/project.pbxproj').createSync(recursive: true); // This is the expected output directory. - fileSystem.directory('build/ios/iphoneos/Runner.app').createSync(recursive: true); + fileSystem.directory('build/ios/iphoneos/My Super Awesome App.app').createSync(recursive: true); } IOSDevice setUpIOSDevice({ @@ -311,3 +334,5 @@ IOSDevice setUpIOSDevice({ class MockArtifacts extends Mock implements Artifacts {} class MockCache extends Mock implements Cache {} +class MockXcode extends Mock implements Xcode {} +class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {} diff --git a/packages/flutter_tools/test/general.shard/ios/mac_test.dart b/packages/flutter_tools/test/general.shard/ios/mac_test.dart index 5efc05539a..84241b6ca6 100644 --- a/packages/flutter_tools/test/general.shard/ios/mac_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/mac_test.dart @@ -396,7 +396,7 @@ Exited (sigterm)''', final MockFile pbxprojFile = MockFile(); when(project.xcodeProjectInfoFile).thenReturn(pbxprojFile); - when(project.hostAppBundleName).thenReturn('UnitTestRunner.app'); + when(project.hostAppBundleName).thenAnswer((_) => Future.value('UnitTestRunner.app')); when(pbxprojFile.readAsLinesSync()) .thenAnswer((_) => flutterAssetPbxProjLines); when(pbxprojFile.existsSync()) 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 545c91b1fb..70c0b88f3a 100644 --- a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart @@ -452,10 +452,10 @@ void main() { simControl: mockSimControl, xcode: mockXcode, ); - await launchDeviceUnifiedLogging(device, 'Runner'); + await launchDeviceUnifiedLogging(device, 'My Super Awesome App'); const String expectedPredicate = 'eventType = logEvent AND ' - 'processImagePath ENDSWITH "Runner" AND ' + 'processImagePath ENDSWITH "My Super Awesome App" AND ' '(senderImagePath ENDSWITH "/Flutter" OR senderImagePath ENDSWITH "/libswiftCore.dylib" OR processImageUUID == senderImageUUID) AND ' 'NOT(eventMessage CONTAINS ": could not find icon for representation -> com.apple.") AND ' 'NOT(eventMessage BEGINSWITH "assertion failed: ") AND ' @@ -539,7 +539,9 @@ void main() { fakeProcessManager ..addCommand(const FakeCommand( command: ['tail', '-n', '0', '-F', 'system.log'], - stdout: 'Dec 20 17:04:32 md32-11-vm1 Runner[88374]: flutter: Observatory listening on http://127.0.0.1:64213/1Uoeu523990=/', + stdout: ''' +Dec 20 17:04:32 md32-11-vm1 My Super Awesome App[88374]: flutter: Observatory listening on http://127.0.0.1:64213/1Uoeu523990=/ +Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' )) ..addCommand(const FakeCommand( command: ['tail', '-n', '0', '-F', '/private/var/log/system.log'] @@ -571,9 +573,9 @@ void main() { ..addCommand(const FakeCommand( command: ['tail', '-n', '0', '-F', 'system.log'], stdout: ''' -2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) Observatory listening on http://127.0.0.1:57701/ -2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) )))))))))) -2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) #0 Object.noSuchMethod (dart:core-patch/dart:core/object_patch.dart:46)''' +2017-09-13 15:26:57.228948-0700 localhost My Super Awesome App[37195]: (Flutter) Observatory listening on http://127.0.0.1:57701/ +2017-09-13 15:26:57.228948-0700 localhost My Super Awesome App[37195]: (Flutter) )))))))))) +2017-09-13 15:26:57.228948-0700 localhost My Super Awesome App[37195]: (Flutter) #0 Object.noSuchMethod (dart:core-patch/dart:core/object_patch.dart:46)''' )) ..addCommand(const FakeCommand( command: ['tail', '-n', '0', '-F', '/private/var/log/system.log'] @@ -607,19 +609,19 @@ void main() { ..addCommand(const FakeCommand( command: ['tail', '-n', '0', '-F', 'system.log'], stdout: ''' -2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) Single line message -2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) Multi line message +2017-09-13 15:26:57.228948-0700 localhost My Super Awesome App[37195]: (Flutter) Single line message +2017-09-13 15:26:57.228948-0700 localhost My Super Awesome App[37195]: (Flutter) Multi line message continues... continues... -2020-03-11 15:58:28.207175-0700 localhost Runner[72166]: (libnetwork.dylib) [com.apple.network:] [28 www.googleapis.com:443 stream, pid: 72166, tls] cancelled +2020-03-11 15:58:28.207175-0700 localhost My Super Awesome App[72166]: (libnetwork.dylib) [com.apple.network:] [28 www.googleapis.com:443 stream, pid: 72166, tls] cancelled [28.1 64A98447-EABF-4983-A387-7DB9D0C1785F 10.0.1.200.57912<->172.217.6.74:443] Connected Path: satisfied (Path is satisfied), interface: en18 Duration: 0.271s, DNS @0.000s took 0.001s, TCP @0.002s took 0.019s, TLS took 0.046s bytes in/out: 4468/1933, packets in/out: 11/10, rtt: 0.016s, retransmitted packets: 0, out-of-order packets: 0 -2017-09-13 15:36:57.228948-0700 localhost Runner[37195]: (Flutter) Multi line message again +2017-09-13 15:36:57.228948-0700 localhost My Super Awesome App[37195]: (Flutter) Multi line message again and it goes... and goes... -2017-09-13 15:36:57.228948-0700 localhost Runner[37195]: (Flutter) Single line message, not the part of the above +2017-09-13 15:36:57.228948-0700 localhost My Super Awesome App[37195]: (Flutter) Single line message, not the part of the above ''' )) ..addCommand(const FakeCommand( @@ -657,7 +659,7 @@ void main() { group('unified logging', () { testUsingContext('log reader handles escaped multiline messages', () async { - const String logPredicate = 'eventType = logEvent AND processImagePath ENDSWITH "Runner" ' + const String logPredicate = 'eventType = logEvent AND processImagePath ENDSWITH "My Super Awesome App" ' 'AND (senderImagePath ENDSWITH "/Flutter" OR senderImagePath ENDSWITH "/libswiftCore.dylib" ' 'OR processImageUUID == senderImageUUID) AND NOT(eventMessage CONTAINS ": could not find icon ' 'for representation -> com.apple.") AND NOT(eventMessage BEGINSWITH "assertion failed: ") ' diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index 738e02801b..e8c68a90e5 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart @@ -466,6 +466,39 @@ apply plugin: 'kotlin-android' }); }); + group('application bundle name', () { + MemoryFileSystem fs; + MockXcodeProjectInterpreter mockXcodeProjectInterpreter; + setUp(() { + fs = MemoryFileSystem(); + mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); + }); + + testUsingContext('app product name defaults to Runner.app', () async { + final FlutterProject project = await someProject(); + expect(await project.ios.hostAppBundleName, 'Runner.app'); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + XcodeProjectInterpreter: () => mockXcodeProjectInterpreter + }); + + testUsingContext('app product name xcodebuild settings', () async { + final FlutterProject project = await someProject(); + when(mockXcodeProjectInterpreter.getBuildSettings(any, any)).thenAnswer((_) { + return Future>.value({ + 'FULL_PRODUCT_NAME': 'My App.app' + }); + }); + + expect(await project.ios.hostAppBundleName, 'My App.app'); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + XcodeProjectInterpreter: () => mockXcodeProjectInterpreter + }); + }); + group('organization names set', () { testInMemory('is empty, if project not created', () async { final FlutterProject project = await someProject(); diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index 4afbb3ee14..4fa35d8879 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -45,7 +45,7 @@ class MockApplicationPackageStore extends ApplicationPackageStore { versionCode: 1, launchActivity: 'io.flutter.android.mock.MockActivity', ), - iOS: BuildableIOSApp(MockIosProject(), MockIosProject.bundleId), + iOS: BuildableIOSApp(MockIosProject(), MockIosProject.bundleId, MockIosProject.appBundleName), ); } @@ -529,12 +529,13 @@ class MockPollingDeviceDiscovery extends PollingDeviceDiscovery { class MockIosProject extends Mock implements IosProject { static const String bundleId = 'com.example.test'; + static const String appBundleName = 'My Super Awesome App.app'; @override Future get productBundleIdentifier async => bundleId; @override - String get hostAppBundleName => 'Runner.app'; + Future get hostAppBundleName async => appBundleName; } class MockAndroidDevice extends Mock implements AndroidDevice {