// 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 '../base/process.dart'; import '../build_info.dart'; import '../cache.dart'; import '../globals.dart'; import '../runner/flutter_command_runner.dart'; bool _inflateXcodeArchive(String directory, List<int> archiveBytes) { 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 = Directory.systemTemp.createTempSync('flutter_xcode'); File tempFile = new File(path.join(tempDir.path, 'FlutterXcode.zip'))..createSync(); tempFile.writeAsBytesSync(archiveBytes); try { Directory dir = new Directory(directory); // Remove the old generated project if one is present if (dir.existsSync()) dir.deleteSync(recursive: true); // Create the directory so unzip can write to it dir.createSync(recursive: true); // Unzip the Xcode project into the new empty directory runCheckedSync(<String>['/usr/bin/unzip', tempFile.path, '-d', dir.path]); } catch (error) { printTrace('$error'); return false; } // Cleanup the temp directory after unzipping tempDir.deleteSync(recursive: true); // Verify that we have an Xcode project Directory flutterProj = new Directory(path.join(directory, 'FlutterApplication.xcodeproj')); if (!flutterProj.existsSync()) { 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() != Cache.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'); Directory toolDir = tools.getEngineArtifactsDirectory(TargetPlatform.ios, BuildMode.debug); File archiveFile = new File(path.join(toolDir.path, 'FlutterXcode.zip')); List<int> archiveBytes = archiveFile.readAsBytesSync(); if (archiveBytes.isEmpty) { printError('Error: No archive bytes received.'); return 1; } // Step 2: Inflate the archive into the user project directory bool result = _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(Cache.engineRevision); // Step 5: Tell the user the location of the generated project. printStatus('Xcode project created in $iosFilesPath/.'); return 0; }