blob: b4249810161cc9e97a61dc8bb07d6cc67991a414 [file] [log] [blame]
// Copyright 2015 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/context.dart";
import "../base/process.dart";
import "../runner/flutter_command.dart";
import "../runner/flutter_command_runner.dart";
class IOSCommand extends FlutterCommand {
final String name = "ios";
final String description = "Commands for creating and updating Flutter iOS projects.";
IOSCommand() {
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<List<int>> _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<int> chunk in response)
bytesBuilder.add(chunk);
return bytesBuilder.takeBytes();
}
Future<bool> _inflateXcodeArchive(String directory, List<int> 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 {
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 _setupXcodeProjXcconfig(String filePath) {
StringBuffer localsBuffer = new StringBuffer();
localsBuffer.writeln("// Generated. Do not edit or check into version control!");
localsBuffer.writeln("// Recreate using `flutter ios`.");
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<int> _runInitCommand() async {
// Step 1: Fetch the archive from the cloud
String xcodeprojPath = path.join(Directory.current.path, "ios");
List<int> 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("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.");
return 1;
}
// Step 3: Populate the Local.xcconfig with project specific paths
_setupXcodeProjXcconfig(path.join(xcodeprojPath, "Local.xcconfig"));
// Step 4: 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;
}
@override
Future<int> runInProject() async {
if (!Platform.isMacOS) {
printStatus("iOS specific commands may only be run on a Mac.");
return 1;
}
if (argResults['init'])
return await _runInitCommand();
printError("No flags specified.");
return 1;
}
}