From 5cebf70da41aeee623edf60c961ffb3ca4bcaca5 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 16 Feb 2016 15:01:48 -0800 Subject: [PATCH] `flutter start` initializes the Xcode project if the user has not already done so. --- .../lib/src/commands/create.dart | 2 +- .../flutter_tools/lib/src/commands/ios.dart | 313 +---------------- .../flutter_tools/lib/src/ios/device_ios.dart | 8 +- .../lib/src/ios/initialize_xcode.dart | 318 ++++++++++++++++++ 4 files changed, 327 insertions(+), 314 deletions(-) create mode 100644 packages/flutter_tools/lib/src/ios/initialize_xcode.dart diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index f233911f40..1fbafc67f2 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -13,7 +13,7 @@ import '../android/android.dart' as android; import '../artifacts.dart'; import '../base/globals.dart'; import '../dart/pub.dart'; -import 'ios.dart'; +import '../ios/initialize_xcode.dart'; class CreateCommand extends Command { final String name = 'create'; diff --git a/packages/flutter_tools/lib/src/commands/ios.dart b/packages/flutter_tools/lib/src/commands/ios.dart index c601684717..d70db12fc5 100644 --- a/packages/flutter_tools/lib/src/commands/ios.dart +++ b/packages/flutter_tools/lib/src/commands/ios.dart @@ -5,20 +5,9 @@ import "dart:async"; import "dart:io"; -import "package:path/path.dart" as path; - -import "../artifacts.dart"; import "../base/globals.dart"; -import "../base/process.dart"; import "../runner/flutter_command.dart"; -import "../runner/flutter_command_runner.dart"; - -/// A map from file path to file contents. -final Map iosTemplateFiles = { - 'ios/Info.plist': _infoPlistInitialContents, - 'ios/LaunchScreen.storyboard': _launchScreenInitialContents, - 'ios/Assets.xcassets/AppIcon.appiconset/Contents.json': _iconAssetInitialContents -}; +import "../ios/initialize_xcode.dart"; class IOSCommand extends FlutterCommand { final String name = "ios"; @@ -28,148 +17,6 @@ class IOSCommand extends FlutterCommand { argParser.addFlag('init', help: 'Initialize the Xcode project for building the iOS application'); } - static Uri _xcodeProjectUri(String revision) { - String uriString = "https://storage.googleapis.com/flutter_infra/flutter/$revision/ios/FlutterXcode.zip"; - return Uri.parse(uriString); - } - - Future> _fetchXcodeArchive() async { - printStatus("Fetching the Xcode project archive from the cloud..."); - - HttpClient client = new HttpClient(); - - Uri xcodeProjectUri = _xcodeProjectUri(ArtifactStore.engineRevision); - printStatus("Downloading $xcodeProjectUri..."); - HttpClientRequest request = await client.getUrl(xcodeProjectUri); - HttpClientResponse response = await request.close(); - - if (response.statusCode != 200) - throw new Exception(response.reasonPhrase); - - BytesBuilder bytesBuilder = new BytesBuilder(copy: false); - await for (List chunk in response) - bytesBuilder.add(chunk); - - return bytesBuilder.takeBytes(); - } - - Future _inflateXcodeArchive(String directory, List archiveBytes) async { - printStatus("Unzipping Xcode project to local directory..."); - - // We cannot use ArchiveFile because this archive contains files that are exectuable - // and there is currently no provision to modify file permissions during - // or after creation. See https://github.com/dart-lang/sdk/issues/15078. - // So we depend on the platform to unzip the archive for us. - - Directory tempDir = await Directory.systemTemp.create(); - File tempFile = new File(path.join(tempDir.path, "FlutterXcode.zip"))..createSync(); - 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; - } - - // Cleanup the temp directory after unzipping - runSync(['/bin/rm', '-rf', tempDir.path]); - - Directory flutterDir = new Directory(path.join(directory, 'Flutter')); - bool flutterDirExists = await flutterDir.exists(); - if (!flutterDirExists) - return false; - - // Move contents of the Flutter directory one level up - // There is no dart:io API to do this. See https://github.com/dart-lang/sdk/issues/8148 - - for (FileSystemEntity file in flutterDir.listSync()) { - try { - runCheckedSync(['/bin/mv', file.path, directory]); - } catch (error) { - return false; - } - } - - runSync(['/bin/rm', '-rf', flutterDir.path]); - - return true; - } - - void _writeUserEditableFilesIfNecessary(String directory) { - iosTemplateFiles.forEach((String filePath, String contents) { - File file = new File(filePath); - - if (!file.existsSync()) { - file.parent.createSync(recursive: true); - file.writeAsStringSync(contents); - printStatus('Created $filePath.'); - } - }); - } - - void _setupXcodeProjXcconfig(String filePath) { - StringBuffer localsBuffer = new StringBuffer(); - - localsBuffer.writeln("// This is a generated file; do not edit or check into version control."); - localsBuffer.writeln("// Recreate using `flutter ios --init`."); - - String flutterRoot = path.normalize(Platform.environment[kFlutterRootEnvironmentVariableName]); - localsBuffer.writeln("FLUTTER_ROOT=$flutterRoot"); - - // This holds because requiresProjectRoot is true for this command - String applicationRoot = path.normalize(Directory.current.path); - localsBuffer.writeln("FLUTTER_APPLICATION_PATH=$applicationRoot"); - - String dartSDKPath = path.normalize(path.join(Platform.resolvedExecutable, "..", "..")); - localsBuffer.writeln("DART_SDK_PATH=$dartSDKPath"); - - File localsFile = new File(filePath); - localsFile.createSync(recursive: true); - localsFile.writeAsStringSync(localsBuffer.toString()); - } - - Future _runInitCommand() async { - // Step 1: Fetch the archive from the cloud - String iosFilesPath = path.join(Directory.current.path, "ios"); - String xcodeprojPath = path.join(iosFilesPath, ".generated"); - List archiveBytes = await _fetchXcodeArchive(); - - if (archiveBytes.isEmpty) { - printError("Error: No archive bytes received."); - return 1; - } - - // Step 2: Inflate the archive into the user project directory - bool result = await _inflateXcodeArchive(xcodeprojPath, archiveBytes); - if (!result) { - printError("Could not inflate the Xcode project archive."); - return 1; - } - - // Step 3: Setup default user editable files if this is the first run of - // the init command. - _writeUserEditableFilesIfNecessary(iosFilesPath); - - // Step 4: Populate the Local.xcconfig with project specific paths - _setupXcodeProjXcconfig(path.join(xcodeprojPath, "Local.xcconfig")); - - // Step 5: Write the REVISION file - File revisionFile = new File(path.join(xcodeprojPath, "REVISION")); - revisionFile.createSync(); - revisionFile.writeAsStringSync(ArtifactStore.engineRevision); - - // Step 6: Tell the user the location of the generated project. - printStatus("Xcode project created at $xcodeprojPath/."); - printStatus("User editable settings are in $iosFilesPath/."); - - return 0; - } - @override Future runInProject() async { if (!Platform.isMacOS) { @@ -178,165 +25,9 @@ class IOSCommand extends FlutterCommand { } if (argResults['init']) - return await _runInitCommand(); + return await initializeXcodeProjectHarness(); printError("No flags specified."); 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" - } -} -'''; diff --git a/packages/flutter_tools/lib/src/ios/device_ios.dart b/packages/flutter_tools/lib/src/ios/device_ios.dart index c045ec4e51..65c802002a 100644 --- a/packages/flutter_tools/lib/src/ios/device_ios.dart +++ b/packages/flutter_tools/lib/src/ios/device_ios.dart @@ -17,6 +17,7 @@ import '../build_configuration.dart'; import '../device.dart'; import '../services.dart'; import '../toolchain.dart'; +import '../ios/initialize_xcode.dart'; import 'simulator.dart'; const String _ideviceinstallerInstructions = @@ -560,8 +561,11 @@ String _getIOSEngineRevision(ApplicationPackage app) { Future _buildIOSXcodeProject(ApplicationPackage app, { bool buildForDevice }) async { if (!FileSystemEntity.isDirectorySync(app.localPath)) { - printError('Path "${path.absolute(app.localPath)}" does not exist.\nDid you run `flutter ios --init`?'); - return false; + printTrace('Path "${path.absolute(app.localPath)}" does not exist. Initializing the Xcode project.'); + if ((await initializeXcodeProjectHarness()) != 0) { + printError('Could not initialize the Xcode project.'); + return false; + } } if (!_validateEngineRevision(app)) diff --git a/packages/flutter_tools/lib/src/ios/initialize_xcode.dart b/packages/flutter_tools/lib/src/ios/initialize_xcode.dart new file mode 100644 index 0000000000..9ad70a718d --- /dev/null +++ b/packages/flutter_tools/lib/src/ios/initialize_xcode.dart @@ -0,0 +1,318 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import '../artifacts.dart'; +import '../base/globals.dart'; +import '../base/process.dart'; +import '../runner/flutter_command_runner.dart'; + +/// A map from file path to file contents. +final Map iosTemplateFiles = { + 'ios/Info.plist': _infoPlistInitialContents, + 'ios/LaunchScreen.storyboard': _launchScreenInitialContents, + 'ios/Assets.xcassets/AppIcon.appiconset/Contents.json': _iconAssetInitialContents +}; + +Uri _xcodeProjectUri(String revision) { + String uriString = 'https://storage.googleapis.com/flutter_infra/flutter/$revision/ios/FlutterXcode.zip'; + return Uri.parse(uriString); +} + +Future> _fetchXcodeArchive() async { + printStatus('Fetching the Xcode project archive from the cloud...'); + + HttpClient client = new HttpClient(); + + Uri xcodeProjectUri = _xcodeProjectUri(ArtifactStore.engineRevision); + printStatus('Downloading $xcodeProjectUri...'); + HttpClientRequest request = await client.getUrl(xcodeProjectUri); + HttpClientResponse response = await request.close(); + + if (response.statusCode != 200) + throw new Exception(response.reasonPhrase); + + BytesBuilder bytesBuilder = new BytesBuilder(copy: false); + await for (List chunk in response) + bytesBuilder.add(chunk); + + return bytesBuilder.takeBytes(); +} + +Future _inflateXcodeArchive(String directory, List archiveBytes) async { + printStatus('Unzipping Xcode project to local directory...'); + + // We cannot use ArchiveFile because this archive contains files that are exectuable + // and there is currently no provision to modify file permissions during + // or after creation. See https://github.com/dart-lang/sdk/issues/15078. + // So we depend on the platform to unzip the archive for us. + + Directory tempDir = await Directory.systemTemp.create(); + File tempFile = new File(path.join(tempDir.path, 'FlutterXcode.zip'))..createSync(); + 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; + } + + // Cleanup the temp directory after unzipping + runSync(['/bin/rm', '-rf', tempDir.path]); + + Directory flutterDir = new Directory(path.join(directory, 'Flutter')); + bool flutterDirExists = await flutterDir.exists(); + if (!flutterDirExists) + return false; + + // Move contents of the Flutter directory one level up + // There is no dart:io API to do this. See https://github.com/dart-lang/sdk/issues/8148 + + for (FileSystemEntity file in flutterDir.listSync()) { + try { + runCheckedSync(['/bin/mv', file.path, directory]); + } catch (error) { + return false; + } + } + + runSync(['/bin/rm', '-rf', flutterDir.path]); + + return true; +} + +void _writeUserEditableFilesIfNecessary(String directory) { + iosTemplateFiles.forEach((String filePath, String contents) { + File file = new File(filePath); + + if (!file.existsSync()) { + file.parent.createSync(recursive: true); + file.writeAsStringSync(contents); + printStatus('Created $filePath.'); + } + }); +} + +void _setupXcodeProjXcconfig(String filePath) { + StringBuffer localsBuffer = new StringBuffer(); + + localsBuffer.writeln('// This is a generated file; do not edit or check into version control.'); + localsBuffer.writeln('// Recreate using `flutter ios --init`.'); + + String flutterRoot = path.normalize(Platform.environment[kFlutterRootEnvironmentVariableName]); + localsBuffer.writeln('FLUTTER_ROOT=$flutterRoot'); + + // This holds because requiresProjectRoot is true for this command + String applicationRoot = path.normalize(Directory.current.path); + localsBuffer.writeln('FLUTTER_APPLICATION_PATH=$applicationRoot'); + + String dartSDKPath = path.normalize(path.join(Platform.resolvedExecutable, '..', '..')); + localsBuffer.writeln('DART_SDK_PATH=$dartSDKPath'); + + File localsFile = new File(filePath); + localsFile.createSync(recursive: true); + localsFile.writeAsStringSync(localsBuffer.toString()); +} + +Future initializeXcodeProjectHarness() async { + // Step 1: Fetch the archive from the cloud + String iosFilesPath = path.join(Directory.current.path, 'ios'); + String xcodeprojPath = path.join(iosFilesPath, '.generated'); + List archiveBytes = await _fetchXcodeArchive(); + + if (archiveBytes.isEmpty) { + printError('Error: No archive bytes received.'); + return 1; + } + + // Step 2: Inflate the archive into the user project directory + bool result = await _inflateXcodeArchive(xcodeprojPath, archiveBytes); + if (!result) { + printError('Could not inflate the Xcode project archive.'); + return 1; + } + + // Step 3: Setup default user editable files if this is the first run of + // the init command. + _writeUserEditableFilesIfNecessary(iosFilesPath); + + // Step 4: Populate the Local.xcconfig with project specific paths + _setupXcodeProjXcconfig(path.join(xcodeprojPath, 'Local.xcconfig')); + + // Step 5: Write the REVISION file + File revisionFile = new File(path.join(xcodeprojPath, 'REVISION')); + revisionFile.createSync(); + revisionFile.writeAsStringSync(ArtifactStore.engineRevision); + + // Step 6: Tell the user the location of the generated project. + printStatus('Xcode project created at $xcodeprojPath/.'); + printStatus('User editable settings are in $iosFilesPath/.'); + + return 0; +} + +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" + } +} +''';