From 978973fac9ccdb9069aaabe0651475079e1a336b Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 11 Feb 2016 17:13:06 -0800 Subject: [PATCH 1/4] iOS: Inflate the Xcode project into a "Generated" subdirectory --- packages/flutter_tools/lib/src/commands/ios.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/ios.dart b/packages/flutter_tools/lib/src/commands/ios.dart index 160b085c0b9..d5661658edf 100644 --- a/packages/flutter_tools/lib/src/commands/ios.dart +++ b/packages/flutter_tools/lib/src/commands/ios.dart @@ -59,6 +59,11 @@ class IOSCommand extends FlutterCommand { tempFile.writeAsBytesSync(archiveBytes); try { + // Remove the old generated project if one is present + runCheckedSync(['/bin/rm', '-rf', directory]); + // Create the directory so unzip can write to it + runCheckedSync(['/bin/mkdir', '-p', directory]); + // Unzip the Xcode project into the new empty directory runCheckedSync(['/usr/bin/unzip', tempFile.path, '-d', directory]); } catch (error) { return false; @@ -111,7 +116,7 @@ class IOSCommand extends FlutterCommand { Future _runInitCommand() async { // Step 1: Fetch the archive from the cloud - String xcodeprojPath = path.join(Directory.current.path, "ios"); + String xcodeprojPath = path.join(Directory.current.path, "ios", "Generated"); List archiveBytes = await _fetchXcodeArchive(); if (archiveBytes.isEmpty) { @@ -122,9 +127,7 @@ class IOSCommand extends FlutterCommand { // Step 2: Inflate the archive into the user project directory bool result = await _inflateXcodeArchive(xcodeprojPath, archiveBytes); if (!result) { - printError("Error: Could not init the Xcode project: the 'ios' directory already exists."); - printError("To proceed, remove the 'ios' directory and try again."); - printError("Warning: You may have made manual changes to files in the 'ios' directory."); + printError("Could not inflate the Xcode project archive."); return 1; } From 5f6e9cb39c6df7b0f7c836c842aa4d8d6839e2b8 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 11 Feb 2016 17:44:04 -0800 Subject: [PATCH 2/4] iOS: Generate the initial set of user editable files if these are not already present --- .../flutter_tools/lib/src/commands/ios.dart | 198 +++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/commands/ios.dart b/packages/flutter_tools/lib/src/commands/ios.dart index d5661658edf..5348b53111b 100644 --- a/packages/flutter_tools/lib/src/commands/ios.dart +++ b/packages/flutter_tools/lib/src/commands/ios.dart @@ -93,6 +93,41 @@ class IOSCommand extends FlutterCommand { return true; } + + void _writeUserEditableFilesIfNecessary(String directory) { + printStatus("Checking if user editable files need updates"); + + // Step 1: Check if the Info.plist exists and write one if not + File infoPlist = new File(path.join(directory, "Info.plist")); + if (!infoPlist.existsSync()) { + printStatus("Did not find an existing Info.plist. Creating one."); + infoPlist.writeAsStringSync(_infoPlistInitialContents); + } else { + printStatus("Info.plist present. Using existing."); + } + + // Step 2: Check if the LaunchScreen.storyboard exists and write one if not + File launchScreen = new File(path.join(directory, "LaunchScreen.storyboard")); + if (!launchScreen.existsSync()) { + printStatus("Did not find an existing LaunchScreen.storyboard. Creating one."); + launchScreen.writeAsStringSync(_launchScreenInitialContents); + } else { + printStatus("LaunchScreen.storyboard present. Using existing."); + } + + // Step 3: Check if the Assets.xcassets exists and write one if not + Directory xcassets = new Directory(path.join(directory, "Assets.xcassets")); + if (!xcassets.existsSync()) { + printStatus("Did not find an existing Assets.xcassets. Creating one."); + Directory iconsAssetsDir = new Directory(path.join(xcassets.path, "AppIcon.appiconset")); + iconsAssetsDir.createSync(recursive: true); + File iconContents = new File(path.join(iconsAssetsDir.path, "Contents.json")); + iconContents.writeAsStringSync(_iconAssetInitialContents); + } else { + printStatus("Assets.xcassets present. Using existing."); + } + } + void _setupXcodeProjXcconfig(String filePath) { StringBuffer localsBuffer = new StringBuffer(); @@ -116,7 +151,8 @@ class IOSCommand extends FlutterCommand { Future _runInitCommand() async { // Step 1: Fetch the archive from the cloud - String xcodeprojPath = path.join(Directory.current.path, "ios", "Generated"); + String iosFilesPath = path.join(Directory.current.path, "ios"); + String xcodeprojPath = path.join(iosFilesPath, "Generated"); List archiveBytes = await _fetchXcodeArchive(); if (archiveBytes.isEmpty) { @@ -131,6 +167,10 @@ class IOSCommand extends FlutterCommand { return 1; } + // Step 3: Setup default user editable files if this is the first run of + // the init command. + _writeUserEditableFilesIfNecessary(iosFilesPath); + // Step 3: Populate the Local.xcconfig with project specific paths _setupXcodeProjXcconfig(path.join(xcodeprojPath, "Local.xcconfig")); @@ -159,3 +199,159 @@ class IOSCommand extends FlutterCommand { return 1; } } + +final String _infoPlistInitialContents = ''' + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + Runner + CFBundleIdentifier + io.flutter.runner.Runner + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Flutter + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + +'''; + +final String _launchScreenInitialContents = ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + +'''; + +final String _iconAssetInitialContents = ''' +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} +'''; From 3d25df3f9952cd0ae47880dad9290b2f843d074e Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 11 Feb 2016 17:48:56 -0800 Subject: [PATCH 3/4] iOS: Update path to the application Xcode project to point to the generated subdirectory --- packages/flutter_tools/lib/src/application_package.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index 35fae609589..f0167deba5a 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart @@ -82,7 +82,7 @@ class AndroidApk extends ApplicationPackage { class IOSApp extends ApplicationPackage { static const String _defaultId = 'io.flutter.runner.Runner'; - static const String _defaultPath = 'ios'; + static const String _defaultPath = 'ios/Generated'; IOSApp({ String localPath: _defaultPath, From 23c632ab87a0deebc0f6117b2b459bd6383a7206 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 11 Feb 2016 18:39:25 -0800 Subject: [PATCH 4/4] iOS: Write a default .gitignore file for the "Generated" directory --- packages/flutter_tools/lib/src/commands/ios.dart | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/ios.dart b/packages/flutter_tools/lib/src/commands/ios.dart index 5348b53111b..1075584a0c0 100644 --- a/packages/flutter_tools/lib/src/commands/ios.dart +++ b/packages/flutter_tools/lib/src/commands/ios.dart @@ -167,19 +167,27 @@ class IOSCommand extends FlutterCommand { return 1; } - // Step 3: Setup default user editable files if this is the first run of + // Step 3: The generated project should NOT be checked into the users + // version control system. Be nice and write a gitignore for them if + // one does not exist. + File generatedGitignore = new File(path.join(iosFilesPath, ".gitignore")); + if (!generatedGitignore.existsSync()) { + generatedGitignore.writeAsStringSync("Generated/\n"); + } + + // Step 4: Setup default user editable files if this is the first run of // the init command. _writeUserEditableFilesIfNecessary(iosFilesPath); - // Step 3: Populate the Local.xcconfig with project specific paths + // Step 5: Populate the Local.xcconfig with project specific paths _setupXcodeProjXcconfig(path.join(xcodeprojPath, "Local.xcconfig")); - // Step 4: Write the REVISION file + // Step 6: Write the REVISION file File revisionFile = new File(path.join(xcodeprojPath, "REVISION")); revisionFile.createSync(); revisionFile.writeAsStringSync(ArtifactStore.engineRevision); - // Step 5: Tell the user the location of the generated project. + // Step 7: Tell the user the location of the generated project. printStatus("An Xcode project has been placed in 'ios/'."); printStatus("You may edit it to modify iOS specific configuration."); return 0;