Commit a600fe7f authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by Dan Field

Support materializing Flutter module host app on iOS (#21276)

* Prototype

* Fix paths to Flutter library resources

* Invoke pod install as necessary for materialized modules

* Add devicelab test for module use on iOS

* Remove debug output

* Rebase, reame materialize editable

* Add devicelab test editable iOS host app

* Removed add2app test section
parent 5c07e6df
...@@ -24,6 +24,7 @@ Future<Null> main() async { ...@@ -24,6 +24,7 @@ Future<Null> main() async {
section('Create Flutter module project'); section('Create Flutter module project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.'); final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try { try {
await inDirectory(tempDir, () async { await inDirectory(tempDir, () async {
await flutter( await flutter(
...@@ -34,14 +35,14 @@ Future<Null> main() async { ...@@ -34,14 +35,14 @@ Future<Null> main() async {
section('Add plugins'); section('Add plugins');
final File pubspec = File(path.join(tempDir.path, 'hello', 'pubspec.yaml')); final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
String content = await pubspec.readAsString(); String content = await pubspec.readAsString();
content = content.replaceFirst( content = content.replaceFirst(
'\ndependencies:\n', '\ndependencies:\n',
'\ndependencies:\n battery:\n package_info:\n', '\ndependencies:\n battery:\n package_info:\n',
); );
await pubspec.writeAsString(content, flush: true); await pubspec.writeAsString(content, flush: true);
await inDirectory(Directory(path.join(tempDir.path, 'hello')), () async { await inDirectory(projectDir, () async {
await flutter( await flutter(
'packages', 'packages',
options: <String>['get'], options: <String>['get'],
...@@ -50,7 +51,7 @@ Future<Null> main() async { ...@@ -50,7 +51,7 @@ Future<Null> main() async {
section('Build Flutter module library archive'); section('Build Flutter module library archive');
await inDirectory(Directory(path.join(tempDir.path, 'hello', '.android')), () async { await inDirectory(Directory(path.join(projectDir.path, '.android')), () async {
await exec( await exec(
'./gradlew', './gradlew',
<String>['flutter:assembleDebug'], <String>['flutter:assembleDebug'],
...@@ -59,8 +60,7 @@ Future<Null> main() async { ...@@ -59,8 +60,7 @@ Future<Null> main() async {
}); });
final bool aarBuilt = exists(File(path.join( final bool aarBuilt = exists(File(path.join(
tempDir.path, projectDir.path,
'hello',
'.android', '.android',
'Flutter', 'Flutter',
'build', 'build',
...@@ -75,7 +75,7 @@ Future<Null> main() async { ...@@ -75,7 +75,7 @@ Future<Null> main() async {
section('Build ephemeral host app'); section('Build ephemeral host app');
await inDirectory(Directory(path.join(tempDir.path, 'hello')), () async { await inDirectory(projectDir, () async {
await flutter( await flutter(
'build', 'build',
options: <String>['apk'], options: <String>['apk'],
...@@ -83,8 +83,7 @@ Future<Null> main() async { ...@@ -83,8 +83,7 @@ Future<Null> main() async {
}); });
final bool ephemeralHostApkBuilt = exists(File(path.join( final bool ephemeralHostApkBuilt = exists(File(path.join(
tempDir.path, projectDir.path,
'hello',
'build', 'build',
'host', 'host',
'outputs', 'outputs',
...@@ -99,31 +98,30 @@ Future<Null> main() async { ...@@ -99,31 +98,30 @@ Future<Null> main() async {
section('Clean build'); section('Clean build');
await inDirectory(Directory(path.join(tempDir.path, 'hello')), () async { await inDirectory(projectDir, () async {
await flutter('clean'); await flutter('clean');
}); });
section('Running `flutter make-host-app-editable` to Materialize host app'); section('Make Android host app editable');
await inDirectory(Directory(path.join(tempDir.path, 'hello')), () async { await inDirectory(projectDir, () async {
await flutter( await flutter(
'make-host-app-editable', 'make-host-app-editable',
options: <String>['android'], options: <String>['android'],
); );
}); });
section('Build materialized host app'); section('Build editable host app');
await inDirectory(Directory(path.join(tempDir.path, 'hello')), () async { await inDirectory(projectDir, () async {
await flutter( await flutter(
'build', 'build',
options: <String>['apk'], options: <String>['apk'],
); );
}); });
final bool materializedHostApkBuilt = exists(File(path.join( final bool editableHostApkBuilt = exists(File(path.join(
tempDir.path, projectDir.path,
'hello',
'build', 'build',
'host', 'host',
'outputs', 'outputs',
...@@ -132,11 +130,11 @@ Future<Null> main() async { ...@@ -132,11 +130,11 @@ Future<Null> main() async {
'app-release.apk', 'app-release.apk',
))); )));
if (!materializedHostApkBuilt) { if (!editableHostApkBuilt) {
return TaskResult.failure('Failed to build materialized host .apk'); return TaskResult.failure('Failed to build editable host .apk');
} }
section('Add to Android app'); section('Add to existing Android app');
final Directory hostApp = Directory(path.join(tempDir.path, 'hello_host_app')); final Directory hostApp = Directory(path.join(tempDir.path, 'hello_host_app'));
mkdir(hostApp); mkdir(hostApp);
...@@ -145,11 +143,11 @@ Future<Null> main() async { ...@@ -145,11 +143,11 @@ Future<Null> main() async {
hostApp, hostApp,
); );
copy( copy(
File(path.join(tempDir.path, 'hello', '.android', 'gradlew')), File(path.join(projectDir.path, '.android', 'gradlew')),
hostApp, hostApp,
); );
copy( copy(
File(path.join(tempDir.path, 'hello', '.android', 'gradle', 'wrapper', 'gradle-wrapper.jar')), File(path.join(projectDir.path, '.android', 'gradle', 'wrapper', 'gradle-wrapper.jar')),
Directory(path.join(hostApp.path, 'gradle', 'wrapper')), Directory(path.join(hostApp.path, 'gradle', 'wrapper')),
); );
......
// Copyright (c) 2018 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:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/ios.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
/// Tests that the Flutter module project template works and supports
/// adding Flutter to an existing iOS app.
Future<Null> main() async {
await task(() async {
section('Create Flutter module project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>['--org', 'io.flutter.devicelab', '-t', 'module', 'hello'],
);
});
await prepareProvisioningCertificates(projectDir.path);
section('Build ephemeral host app without CocoaPods');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>['ios'],
);
});
final bool ephemeralHostAppBuilt = exists(Directory(path.join(
projectDir.path,
'build',
'ios',
'iphoneos',
'Runner.app',
)));
if (!ephemeralHostAppBuilt) {
return TaskResult.failure('Failed to build ephemeral host .app');
}
section('Clean build');
await inDirectory(projectDir, () async {
await flutter('clean');
});
section('Add plugins');
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
String content = await pubspec.readAsString();
content = content.replaceFirst(
'\ndependencies:\n',
'\ndependencies:\n battery:\n package_info:\n',
);
await pubspec.writeAsString(content, flush: true);
await inDirectory(projectDir, () async {
await flutter(
'packages',
options: <String>['get'],
);
});
section('Build ephemeral host app with CocoaPods');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>['ios'],
);
});
final bool ephemeralHostAppWithCocoaPodsBuilt = exists(Directory(path.join(
projectDir.path,
'build',
'ios',
'iphoneos',
'Runner.app',
)));
if (!ephemeralHostAppWithCocoaPodsBuilt) {
return TaskResult.failure('Failed to build ephemeral host .app with CocoaPods');
}
section('Clean build');
await inDirectory(projectDir, () async {
await flutter('clean');
});
section('Make iOS host app editable');
await inDirectory(projectDir, () async {
await flutter(
'make-host-app-editable',
options: <String>['ios'],
);
});
section('Build editable host app');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>['ios'],
);
});
final bool editableHostAppBuilt = exists(Directory(path.join(
projectDir.path,
'build',
'ios',
'iphoneos',
'Runner.app',
)));
if (!editableHostAppBuilt) {
return TaskResult.failure('Failed to build editable host .app');
}
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
//rmTree(tempDir);
}
});
}
...@@ -297,6 +297,13 @@ tasks: ...@@ -297,6 +297,13 @@ tasks:
stage: devicelab_ios stage: devicelab_ios
required_agent_capabilities: ["mac/ios"] required_agent_capabilities: ["mac/ios"]
module_test_ios:
description: >
Checks that the module project template works and supports add2app on iOS.
stage: devicelab
required_agent_capabilities: ["mac/ios"]
flaky: true
external_ui_integration_test_ios: external_ui_integration_test_ios:
description: > description: >
Checks that external UIs work on iOS. Checks that external UIs work on iOS.
......
#include "Flutter.xcconfig"
#include "Pods/Target Support Files/Pods-Host/Pods-Host.debug.xcconfig"
#include "../../hello/.ios/Flutter/Generated.xcconfig"
ENABLE_BITCODE=NO
#include "Flutter.xcconfig"
#include "Pods/Target Support Files/Pods-Host/Pods-Host.release.xcconfig"
FLUTTER_BUILD_MODE=release
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Host.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Host.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate
@end
#import "AppDelegate.h"
@implementation AppDelegate
@end
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"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" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"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"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
#import "ViewController.h"
#import "Flutter/Flutter.h"
#import "FlutterPluginRegistrant/GeneratedPluginRegistrant.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self
action:@selector(handleButtonAction)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Press me" forState:UIControlStateNormal];
[button setBackgroundColor:[UIColor blueColor]];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}
- (void)handleButtonAction {
FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
[GeneratedPluginRegistrant registerWithRegistry:flutterViewController];
[self presentViewController:flutterViewController animated:false completion:nil];
}
@end
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
platform :ios, '9.0'
target 'Host' do
flutter_application_path = '../hello'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
end
...@@ -80,6 +80,9 @@ BuildApp() { ...@@ -80,6 +80,9 @@ BuildApp() {
AssertExists "${project_path}" AssertExists "${project_path}"
local derived_dir="${SOURCE_ROOT}/Flutter" local derived_dir="${SOURCE_ROOT}/Flutter"
if [[ -e "${project_path}/.ios" ]]; then
derived_dir="${SOURCE_ROOT}/../.ios/Flutter"
fi
RunCommand mkdir -p -- "$derived_dir" RunCommand mkdir -p -- "$derived_dir"
AssertExists "$derived_dir" AssertExists "$derived_dir"
......
...@@ -156,7 +156,7 @@ class CocoaPods { ...@@ -156,7 +156,7 @@ class CocoaPods {
// Don't do anything for iOS when host platform doesn't support it. // Don't do anything for iOS when host platform doesn't support it.
return; return;
} }
final Directory runnerProject = iosProject.directory.childDirectory('Runner.xcodeproj'); final Directory runnerProject = iosProject.xcodeProject;
if (!runnerProject.existsSync()) { if (!runnerProject.existsSync()) {
return; return;
} }
...@@ -223,7 +223,7 @@ class CocoaPods { ...@@ -223,7 +223,7 @@ class CocoaPods {
final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true); final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true);
final ProcessResult result = await processManager.run( final ProcessResult result = await processManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: iosProject.directory.path, workingDirectory: iosProject.hostAppRoot.path,
environment: <String, String>{ environment: <String, String>{
// For backward compatibility with previously created Podfile only. // For backward compatibility with previously created Podfile only.
'FLUTTER_FRAMEWORK_DIR': engineDirectory, 'FLUTTER_FRAMEWORK_DIR': engineDirectory,
......
...@@ -291,7 +291,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -291,7 +291,7 @@ Future<XcodeBuildResult> buildXcodeProject({
modern: false, modern: false,
); );
final XcodeProjectInfo projectInfo = xcodeProjectInterpreter.getInfo(app.project.directory.path); final XcodeProjectInfo projectInfo = xcodeProjectInterpreter.getInfo(app.project.hostAppRoot.path);
if (!projectInfo.targets.contains('Runner')) { if (!projectInfo.targets.contains('Runner')) {
printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.'); printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.');
printError('Open Xcode to fix the problem:'); printError('Open Xcode to fix the problem:');
...@@ -326,7 +326,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -326,7 +326,7 @@ Future<XcodeBuildResult> buildXcodeProject({
// Before the build, all service definitions must be updated and the dylibs // Before the build, all service definitions must be updated and the dylibs
// copied over to a location that is suitable for Xcodebuild to find them. // copied over to a location that is suitable for Xcodebuild to find them.
await _addServicesToBundle(app.project.directory); await _addServicesToBundle(app.project.hostAppRoot);
final FlutterProject project = await FlutterProject.current(); final FlutterProject project = await FlutterProject.current();
await updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
...@@ -334,8 +334,8 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -334,8 +334,8 @@ Future<XcodeBuildResult> buildXcodeProject({
targetOverride: targetOverride, targetOverride: targetOverride,
buildInfo: buildInfo, buildInfo: buildInfo,
); );
refreshPluginsList(project);
if (hasPlugins(project)) { if (hasPlugins(project) || (project.isModule && project.ios.podfile.existsSync())) {
// If the Xcode project, Podfile, or Generated.xcconfig have changed since // If the Xcode project, Podfile, or Generated.xcconfig have changed since
// last run, pods should be updated. // last run, pods should be updated.
final Fingerprinter fingerprinter = Fingerprinter( final Fingerprinter fingerprinter = Fingerprinter(
...@@ -381,7 +381,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -381,7 +381,7 @@ Future<XcodeBuildResult> buildXcodeProject({
buildCommands.add('-allowProvisioningDeviceRegistration'); buildCommands.add('-allowProvisioningDeviceRegistration');
} }
final List<FileSystemEntity> contents = app.project.directory.listSync(); final List<FileSystemEntity> contents = app.project.hostAppRoot.listSync();
for (FileSystemEntity entity in contents) { for (FileSystemEntity entity in contents) {
if (fs.path.extension(entity.path) == '.xcworkspace') { if (fs.path.extension(entity.path) == '.xcworkspace') {
buildCommands.addAll(<String>[ buildCommands.addAll(<String>[
...@@ -446,7 +446,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -446,7 +446,7 @@ Future<XcodeBuildResult> buildXcodeProject({
initialBuildStatus = logger.startProgress('Starting Xcode build...'); initialBuildStatus = logger.startProgress('Starting Xcode build...');
final RunResult buildResult = await runAsync( final RunResult buildResult = await runAsync(
buildCommands, buildCommands,
workingDirectory: app.project.directory.path, workingDirectory: app.project.hostAppRoot.path,
allowReentrantFlutter: true allowReentrantFlutter: true
); );
buildSubStatus?.stop(); buildSubStatus?.stop();
...@@ -473,7 +473,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -473,7 +473,7 @@ Future<XcodeBuildResult> buildXcodeProject({
'-allowProvisioningDeviceRegistration', '-allowProvisioningDeviceRegistration',
].contains(buildCommand); ].contains(buildCommand);
}).toList(), }).toList(),
workingDirectory: app.project.directory.path, workingDirectory: app.project.hostAppRoot.path,
)); ));
if (buildResult.exitCode != 0) { if (buildResult.exitCode != 0) {
...@@ -492,7 +492,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -492,7 +492,7 @@ Future<XcodeBuildResult> buildXcodeProject({
stderr: buildResult.stderr, stderr: buildResult.stderr,
xcodeBuildExecution: XcodeBuildExecution( xcodeBuildExecution: XcodeBuildExecution(
buildCommands: buildCommands, buildCommands: buildCommands,
appDirectory: app.project.directory.path, appDirectory: app.project.hostAppRoot.path,
buildForPhysicalDevice: buildForDevice, buildForPhysicalDevice: buildForDevice,
buildSettings: buildSettings, buildSettings: buildSettings,
), ),
...@@ -677,7 +677,7 @@ Future<bool> upgradePbxProjWithFlutterAssets(IosProject project) async { ...@@ -677,7 +677,7 @@ Future<bool> upgradePbxProjWithFlutterAssets(IosProject project) async {
assert(await xcodeProjectFile.exists()); assert(await xcodeProjectFile.exists());
final List<String> lines = await xcodeProjectFile.readAsLines(); final List<String> lines = await xcodeProjectFile.readAsLines();
if (lines.any((String line) => line.contains('path = Flutter/flutter_assets'))) if (lines.any((String line) => line.contains('flutter_assets in Resources')))
return true; return true;
const String l1 = ' 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };'; const String l1 = ' 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };';
......
...@@ -228,6 +228,7 @@ Depends on all your plugins, and provides a function to register them. ...@@ -228,6 +228,7 @@ Depends on all your plugins, and provides a function to register them.
s.source_files = "Classes", "Classes/**/*.{h,m}" s.source_files = "Classes", "Classes/**/*.{h,m}"
s.source = { :path => '.' } s.source = { :path => '.' }
s.public_header_files = './Classes/**/*.h' s.public_header_files = './Classes/**/*.h'
s.dependency 'Flutter'
{{#plugins}} {{#plugins}}
s.dependency '{{name}}' s.dependency '{{name}}'
{{/plugins}} {{/plugins}}
...@@ -296,7 +297,7 @@ Future<void> injectPlugins(FlutterProject project) async { ...@@ -296,7 +297,7 @@ Future<void> injectPlugins(FlutterProject project) async {
final List<Plugin> plugins = findPlugins(project); final List<Plugin> plugins = findPlugins(project);
await _writeAndroidPluginRegistrant(project, plugins); await _writeAndroidPluginRegistrant(project, plugins);
await _writeIOSPluginRegistrant(project, plugins); await _writeIOSPluginRegistrant(project, plugins);
if (!project.isModule && project.ios.directory.existsSync()) { if (!project.isModule && project.ios.hostAppRoot.existsSync()) {
final CocoaPods cocoaPods = CocoaPods(); final CocoaPods cocoaPods = CocoaPods();
if (plugins.isNotEmpty) if (plugins.isNotEmpty)
cocoaPods.setupPodfile(project.ios); cocoaPods.setupPodfile(project.ios);
......
...@@ -157,37 +157,53 @@ class IosProject { ...@@ -157,37 +157,53 @@ class IosProject {
/// The parent of this project. /// The parent of this project.
final FlutterProject parent; final FlutterProject parent;
/// The directory of this project. Directory get _ephemeralDirectory => parent.directory.childDirectory('.ios');
Directory get directory => parent.directory.childDirectory(isModule ? '.ios' : 'ios'); Directory get _editableDirectory => parent.directory.childDirectory('ios');
/// This parent folder of `Runner.xcodeproj`.
Directory get hostAppRoot {
if (!isModule || _editableDirectory.existsSync())
return _editableDirectory;
return _ephemeralDirectory;
}
/// The root directory of the iOS wrapping of Flutter and plugins. This is the
/// parent of the `Flutter/` folder into which Flutter artifacts are written
/// during build.
///
/// This is the same as [hostAppRoot] except when the project is
/// a Flutter module with an editable host app.
Directory get _flutterLibRoot => isModule ? _ephemeralDirectory : _editableDirectory;
/// The bundle name of the host app, `Runner.app`.
String get hostAppBundleName => '$_hostAppBundleName.app'; String get hostAppBundleName => '$_hostAppBundleName.app';
/// True, if the parent Flutter project is a module. /// True, if the parent Flutter project is a module.
bool get isModule => parent.isModule; bool get isModule => parent.isModule;
/// The xcode config file for [mode]. /// The xcode config file for [mode].
File xcodeConfigFor(String mode) => directory.childDirectory('Flutter').childFile('$mode.xcconfig'); File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig');
/// The 'Podfile'. /// The 'Podfile'.
File get podfile => directory.childFile('Podfile'); File get podfile => hostAppRoot.childFile('Podfile');
/// The 'Podfile.lock'. /// The 'Podfile.lock'.
File get podfileLock => directory.childFile('Podfile.lock'); File get podfileLock => hostAppRoot.childFile('Podfile.lock');
/// The 'Manifest.lock'. /// The 'Manifest.lock'.
File get podManifestLock => directory.childDirectory('Pods').childFile('Manifest.lock'); File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock');
/// The 'Info.plist' file of the host app. /// The 'Info.plist' file of the host app.
File get hostInfoPlist => directory.childDirectory(_hostAppBundleName).childFile('Info.plist'); File get hostInfoPlist => hostAppRoot.childDirectory(_hostAppBundleName).childFile('Info.plist');
/// '.xcodeproj' folder of the host app. /// '.xcodeproj' folder of the host app.
Directory get xcodeProject => directory.childDirectory('$_hostAppBundleName.xcodeproj'); Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppBundleName.xcodeproj');
/// The '.pbxproj' file of the host app. /// The '.pbxproj' file of the host app.
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
/// Xcode workspace directory of the host app. /// Xcode workspace directory of the host app.
Directory get xcodeWorkspace => directory.childDirectory('$_hostAppBundleName.xcworkspace'); Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppBundleName.xcworkspace');
/// Xcode workspace shared data directory for the host app. /// Xcode workspace shared data directory for the host app.
Directory get xcodeWorkspaceSharedData => xcodeWorkspace.childDirectory('xcshareddata'); Directory get xcodeWorkspaceSharedData => xcodeWorkspace.childDirectory('xcshareddata');
...@@ -232,8 +248,12 @@ class IosProject { ...@@ -232,8 +248,12 @@ class IosProject {
Future<void> ensureReadyForPlatformSpecificTooling() async { Future<void> ensureReadyForPlatformSpecificTooling() async {
_regenerateFromTemplateIfNeeded(); _regenerateFromTemplateIfNeeded();
if (!directory.existsSync()) if (!_flutterLibRoot.existsSync())
return; return;
await _updateGeneratedXcodeConfigIfNeeded();
}
Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
if (Cache.instance.isOlderThanToolsStamp(generatedXcodePropertiesFile)) { if (Cache.instance.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
await xcode.updateGeneratedXcodeProperties( await xcode.updateGeneratedXcodeProperties(
project: parent, project: parent,
...@@ -246,28 +266,40 @@ class IosProject { ...@@ -246,28 +266,40 @@ class IosProject {
void _regenerateFromTemplateIfNeeded() { void _regenerateFromTemplateIfNeeded() {
if (!isModule) if (!isModule)
return; return;
final bool pubspecChanged = isOlderThanReference(entity: directory, referenceFile: parent.pubspecFile); final bool pubspecChanged = isOlderThanReference(entity: _ephemeralDirectory, referenceFile: parent.pubspecFile);
final bool toolingChanged = Cache.instance.isOlderThanToolsStamp(directory); final bool toolingChanged = Cache.instance.isOlderThanToolsStamp(_ephemeralDirectory);
if (!pubspecChanged && !toolingChanged) if (!pubspecChanged && !toolingChanged)
return; return;
_deleteIfExistsSync(directory); _deleteIfExistsSync(_ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'library'), directory); _overwriteFromTemplate(fs.path.join('module', 'ios', 'library'), _ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral'), directory); // Add ephemeral host app, if a editable host app does not already exist.
if (hasPlugins(parent)) { if (!_editableDirectory.existsSync()) {
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), directory); _overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral'), _ephemeralDirectory);
if (hasPlugins(parent)) {
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), _ephemeralDirectory);
}
} }
} }
Future<void> makeHostAppEditable() async { Future<void> makeHostAppEditable() async {
throwToolExit('making host app editable has not yet been implemented for iOS'); assert(isModule);
if (_editableDirectory.existsSync())
throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.');
_deleteIfExistsSync(_ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'library'), _ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral'), _editableDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), _editableDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_editable_cocoapods'), _editableDirectory);
await _updateGeneratedXcodeConfigIfNeeded();
await injectPlugins(parent);
} }
File get generatedXcodePropertiesFile => directory.childDirectory('Flutter').childFile('Generated.xcconfig'); File get generatedXcodePropertiesFile => _flutterLibRoot.childDirectory('Flutter').childFile('Generated.xcconfig');
Directory get pluginRegistrantHost { Directory get pluginRegistrantHost {
return isModule return isModule
? directory.childDirectory('Flutter').childDirectory('FlutterPluginRegistrant') ? _flutterLibRoot.childDirectory('Flutter').childDirectory('FlutterPluginRegistrant')
: directory.childDirectory(_hostAppBundleName); : hostAppRoot.childDirectory(_hostAppBundleName);
} }
void _overwriteFromTemplate(String path, Directory target) { void _overwriteFromTemplate(String path, Directory target) {
......
...@@ -36,15 +36,15 @@ ...@@ -36,15 +36,15 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = ../.ios/Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
741F495E21355F27001E2961 /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/engine/Flutter.framework; sourceTree = "<group>"; }; 741F495E21355F27001E2961 /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = ../.ios/Flutter/engine/Flutter.framework; sourceTree = "<group>"; };
741F496521356807001E2961 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; }; 741F496521356807001E2961 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = ../.ios/Flutter/App.framework; sourceTree = "<group>"; };
74974046213559DB008C567A /* Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; 74974046213559DB008C567A /* Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
74974047213559DB008C567A /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 74974047213559DB008C567A /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
7497404A213559E7008C567A /* Flutter.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Flutter.xcconfig; sourceTree = "<group>"; }; 7497404A213559E7008C567A /* Flutter.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Flutter.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = ../.ios/Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
......
...@@ -2,5 +2,5 @@ platform :ios, '8.0' ...@@ -2,5 +2,5 @@ platform :ios, '8.0'
target 'Runner' do target 'Runner' do
flutter_application_path = '../' flutter_application_path = '../'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb'))) eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
end end
...@@ -20,11 +20,6 @@ def parse_KV_file(file, separator='=') ...@@ -20,11 +20,6 @@ def parse_KV_file(file, separator='=')
return pods_array return pods_array
end end
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
def flutter_root(f) def flutter_root(f)
generated_xcode_build_settings = parse_KV_file(File.join(f, File.join('.ios', 'Flutter', 'Generated.xcconfig'))) generated_xcode_build_settings = parse_KV_file(File.join(f, File.join('.ios', 'Flutter', 'Generated.xcconfig')))
if generated_xcode_build_settings.empty? if generated_xcode_build_settings.empty?
...@@ -38,32 +33,26 @@ def flutter_root(f) ...@@ -38,32 +33,26 @@ def flutter_root(f)
} }
end end
framework_dir = File.join(File.expand_path(File.dirname(__FILE__)), 'Flutter') framework_dir = File.join(flutter_application_path, '.ios', 'Flutter')
engine_dir = File.join(framework_dir, 'engine') engine_dir = File.join(framework_dir, 'engine')
if !File.exist?(engine_dir) if !File.exist?(engine_dir)
# Copy the debug engine to have something to link against if the xcode backend script has not run yet. # Copy the debug engine to have something to link against if the xcode backend script has not run yet.
debug_framework_dir = File.join(flutter_root(flutter_application_path), 'bin', 'cache', 'artifacts', 'engine', 'ios') debug_framework_dir = File.join(flutter_root(flutter_application_path), 'bin', 'cache', 'artifacts', 'engine', 'ios')
FileUtils.mkdir(engine_dir) FileUtils.mkdir_p(engine_dir)
FileUtils.cp_r(File.join(debug_framework_dir, 'Flutter.framework'), engine_dir) FileUtils.cp_r(File.join(debug_framework_dir, 'Flutter.framework'), engine_dir)
FileUtils.cp(File.join(debug_framework_dir, 'Flutter.podspec'), engine_dir) FileUtils.cp(File.join(debug_framework_dir, 'Flutter.podspec'), engine_dir)
end end
symlink = File.join('.symlinks', 'flutter') pod 'Flutter', :path => engine_dir
pod 'FlutterPluginRegistrant', :path => File.join(framework_dir, 'FlutterPluginRegistrant')
File.symlink(framework_dir, symlink)
pod 'Flutter', :path => File.join(symlink, 'engine')
symlinks_dir = File.join(framework_dir, '.symlinks')
FileUtils.mkdir_p(symlinks_dir)
plugin_pods = parse_KV_file(File.join(flutter_application_path, '.flutter-plugins')) plugin_pods = parse_KV_file(File.join(flutter_application_path, '.flutter-plugins'))
plugin_pods.map { |r| plugin_pods.map { |r|
symlink = File.join('.symlinks', 'plugins', r[:name]) symlink = File.join(symlinks_dir, r[:name])
FileUtils.rm_f(symlink)
File.symlink(r[:path], symlink) File.symlink(r[:path], symlink)
pod r[:name], :path => File.join(symlink, 'ios') pod r[:name], :path => File.join(symlink, 'ios')
} }
symlink = File.join('.symlinks', 'FlutterApp')
File.symlink(File.absolute_path(flutter_application_path), symlink)
pod 'FlutterPluginRegistrant', :path => File.join(symlink, '.ios', 'Flutter','FlutterPluginRegistrant')
...@@ -46,7 +46,7 @@ void main() { ...@@ -46,7 +46,7 @@ void main() {
mockProcessManager = MockProcessManager(); mockProcessManager = MockProcessManager();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
projectUnderTest = await FlutterProject.fromDirectory(fs.directory('project')); projectUnderTest = await FlutterProject.fromDirectory(fs.directory('project'));
projectUnderTest.ios.directory.childDirectory('Runner.xcodeproj').createSync(recursive: true); projectUnderTest.ios.xcodeProject.createSync(recursive: true);
cocoaPodsUnderTest = CocoaPods(); cocoaPodsUnderTest = CocoaPods();
pretendPodVersionIs('1.5.0'); pretendPodVersionIs('1.5.0');
fs.file(fs.path.join( fs.file(fs.path.join(
......
...@@ -148,20 +148,20 @@ void main() { ...@@ -148,20 +148,20 @@ void main() {
testInMemory('does nothing in plugin or package root project', () async { testInMemory('does nothing in plugin or package root project', () async {
final FlutterProject project = await aPluginProject(); final FlutterProject project = await aPluginProject();
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectNotExists(project.ios.directory.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h')); expectNotExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app'))); expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
expectNotExists(project.ios.directory.childDirectory('Flutter').childFile('Generated.xcconfig')); expectNotExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
expectNotExists(project.android.hostAppGradleRoot.childFile('local.properties')); expectNotExists(project.android.hostAppGradleRoot.childFile('local.properties'));
}); });
testInMemory('injects plugins for iOS', () async { testInMemory('injects plugins for iOS', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.ios.directory.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h')); expectExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
}); });
testInMemory('generates Xcode configuration for iOS', () async { testInMemory('generates Xcode configuration for iOS', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.ios.directory.childDirectory('Flutter').childFile('Generated.xcconfig')); expectExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
}); });
testInMemory('injects plugins for Android', () async { testInMemory('injects plugins for Android', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
...@@ -183,7 +183,7 @@ void main() { ...@@ -183,7 +183,7 @@ void main() {
testInMemory('creates iOS pod in module', () async { testInMemory('creates iOS pod in module', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await aModuleProject();
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
final Directory flutter = project.ios.directory.childDirectory('Flutter'); final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter');
expectExists(flutter.childFile('podhelper.rb')); expectExists(flutter.childFile('podhelper.rb'));
expectExists(flutter.childFile('Generated.xcconfig')); expectExists(flutter.childFile('Generated.xcconfig'));
final Directory pluginRegistrantClasses = flutter final Directory pluginRegistrantClasses = flutter
...@@ -201,7 +201,7 @@ void main() { ...@@ -201,7 +201,7 @@ void main() {
expect(project.android.isModule, isTrue); expect(project.android.isModule, isTrue);
expect(project.ios.isModule, isTrue); expect(project.ios.isModule, isTrue);
expect(project.android.hostAppGradleRoot.basename, '.android'); expect(project.android.hostAppGradleRoot.basename, '.android');
expect(project.ios.directory.basename, '.ios'); expect(project.ios.hostAppRoot.basename, '.ios');
}); });
testInMemory('is known for non-module', () async { testInMemory('is known for non-module', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
...@@ -209,7 +209,7 @@ void main() { ...@@ -209,7 +209,7 @@ void main() {
expect(project.android.isModule, isFalse); expect(project.android.isModule, isFalse);
expect(project.ios.isModule, isFalse); expect(project.ios.isModule, isFalse);
expect(project.android.hostAppGradleRoot.basename, 'android'); expect(project.android.hostAppGradleRoot.basename, 'android');
expect(project.ios.directory.basename, 'ios'); expect(project.ios.hostAppRoot.basename, 'ios');
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment