blob: f9853b677176bba3e5914bbf01125787a4aa46f4 [file] [log] [blame]
// 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/process.dart';
import '../globals.dart';
import '../runner/flutter_command_runner.dart';
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 {
// 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]);
// Verify that we have an Xcode project
Directory flutterProj = new Directory(path.join(directory, 'FlutterApplication.xcodeproj'));
bool flutterProjExists = await flutterProj.exists();
if (!flutterProjExists) {
printError("${flutterProj.path} does not exist");
return false;
}
return true;
}
void updateXcodeLocalProperties(String projectPath) {
StringBuffer localsBuffer = new StringBuffer();
localsBuffer.writeln('// This is a generated file; do not edit or check into version control.');
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(path.join(projectPath, 'ios', '.generated', 'Local.xcconfig'));
localsFile.createSync(recursive: true);
localsFile.writeAsStringSync(localsBuffer.toString());
}
bool xcodeProjectRequiresUpdate() {
File revisionFile = new File(path.join(Directory.current.path, 'ios', '.generated', 'REVISION'));
// If the revision stamp does not exist, the Xcode project definitely requires
// an update
if (!revisionFile.existsSync()) {
printTrace("A revision stamp does not exist. The Xcode project has never been initialized.");
return true;
}
if (revisionFile.readAsStringSync() != ArtifactStore.engineRevision) {
printTrace("The revision stamp and the Flutter engine revision differ. Project needs to be updated.");
return true;
}
printTrace("Xcode project is up to date.");
return false;
}
Future<int> setupXcodeProjectHarness(String flutterProjectPath) async {
// Step 1: Fetch the archive from the cloud
String iosFilesPath = path.join(flutterProjectPath, 'ios');
String xcodeprojPath = path.join(iosFilesPath, '.generated');
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('Could not inflate the Xcode project archive.');
return 1;
}
// Step 3: Populate the Local.xcconfig with project specific paths
updateXcodeLocalProperties(flutterProjectPath);
// Step 4: 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.
printStatus('Xcode project created in $iosFilesPath/.');
return 0;
}