Unverified Commit e8e5de44 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Parse .flutter-plugins-dependencies for add-to-app iOS modules (#61269)

parent 734782f5
......@@ -14,6 +14,7 @@ import 'package:path/path.dart' as path;
/// adding Flutter to an existing iOS app.
Future<void> main() async {
await task(() async {
String simulatorDeviceId;
section('Create Flutter module project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
......@@ -52,7 +53,7 @@ Future<void> main() async {
);
});
final Directory ephemeralReleaseHostApp = Directory(path.join(
final Directory ephemeralIOSHostApp = Directory(path.join(
projectDir.path,
'build',
'ios',
......@@ -60,13 +61,13 @@ Future<void> main() async {
'Runner.app',
));
if (!exists(ephemeralReleaseHostApp)) {
if (!exists(ephemeralIOSHostApp)) {
return TaskResult.failure('Failed to build ephemeral host .app');
}
if (!await _isAppAotBuild(ephemeralReleaseHostApp)) {
if (!await _isAppAotBuild(ephemeralIOSHostApp)) {
return TaskResult.failure(
'Ephemeral host app ${ephemeralReleaseHostApp.path} was not a release build as expected'
'Ephemeral host app ${ephemeralIOSHostApp.path} was not a release build as expected'
);
}
......@@ -85,21 +86,13 @@ Future<void> main() async {
);
});
final Directory ephemeralProfileHostApp = Directory(path.join(
projectDir.path,
'build',
'ios',
'iphoneos',
'Runner.app',
));
if (!exists(ephemeralProfileHostApp)) {
if (!exists(ephemeralIOSHostApp)) {
return TaskResult.failure('Failed to build ephemeral host .app');
}
if (!await _isAppAotBuild(ephemeralProfileHostApp)) {
if (!await _isAppAotBuild(ephemeralIOSHostApp)) {
return TaskResult.failure(
'Ephemeral host app ${ephemeralProfileHostApp.path} was not a profile build as expected'
'Ephemeral host app ${ephemeralIOSHostApp.path} was not a profile build as expected'
);
}
......@@ -118,7 +111,7 @@ Future<void> main() async {
);
});
final Directory ephemeralDebugHostApp = Directory(path.join(
final Directory ephemeralSimulatorHostApp = Directory(path.join(
projectDir.path,
'build',
'ios',
......@@ -126,19 +119,19 @@ Future<void> main() async {
'Runner.app',
));
if (!exists(ephemeralDebugHostApp)) {
if (!exists(ephemeralSimulatorHostApp)) {
return TaskResult.failure('Failed to build ephemeral host .app');
}
if (!exists(File(path.join(
ephemeralDebugHostApp.path,
ephemeralSimulatorHostApp.path,
'Frameworks',
'App.framework',
'flutter_assets',
'isolate_snapshot_data',
)))) {
return TaskResult.failure(
'Ephemeral host app ${ephemeralDebugHostApp.path} was not a debug build as expected'
'Ephemeral host app ${ephemeralSimulatorHostApp.path} was not a debug build as expected'
);
}
......@@ -154,7 +147,8 @@ Future<void> main() async {
String content = await pubspec.readAsString();
content = content.replaceFirst(
'\ndependencies:\n',
'\ndependencies:\n device_info:\n google_maps_flutter:\n', // One dynamic and one static framework.
// One dynamic framework, one static framework, and one that does not support iOS.
'\ndependencies:\n device_info:\n google_maps_flutter:\n android_alarm_manager:\n',
);
await pubspec.writeAsString(content, flush: true);
await inDirectory(projectDir, () async {
......@@ -173,13 +167,7 @@ Future<void> main() async {
);
});
final bool ephemeralHostAppWithCocoaPodsBuilt = exists(Directory(path.join(
projectDir.path,
'build',
'ios',
'iphoneos',
'Runner.app',
)));
final bool ephemeralHostAppWithCocoaPodsBuilt = exists(ephemeralIOSHostApp);
if (!ephemeralHostAppWithCocoaPodsBuilt) {
return TaskResult.failure('Failed to build ephemeral host .app with CocoaPods');
......@@ -190,10 +178,19 @@ Future<void> main() async {
if (!podfileLockOutput.contains(':path: Flutter/engine')
|| !podfileLockOutput.contains(':path: Flutter/FlutterPluginRegistrant')
|| !podfileLockOutput.contains(':path: Flutter/.symlinks/device_info/ios')
|| !podfileLockOutput.contains(':path: Flutter/.symlinks/google_maps_flutter/ios')) {
|| !podfileLockOutput.contains(':path: Flutter/.symlinks/google_maps_flutter/ios')
|| podfileLockOutput.contains('android_alarm_manager')) {
return TaskResult.failure('Building ephemeral host app Podfile.lock does not contain expected pods');
}
checkFileExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', 'device_info.framework', 'device_info'));
// Static, no embedded framework.
checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', 'google_maps_flutter.framework'));
// Android-only, no embedded framework.
checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', 'android_alarm_manager.framework'));
section('Add to existing iOS Objective-C app');
final Directory objectiveCHostApp = Directory(path.join(tempDir.path, 'hello_host_app'));
......@@ -255,8 +252,9 @@ Future<void> main() async {
}
section('Run platform unit tests');
await testWithNewiOSSimulator('TestAdd2AppSim', (String deviceId) =>
inDirectory(objectiveCHostApp, () =>
await testWithNewIOSSimulator('TestAdd2AppSim', (String deviceId) {
simulatorDeviceId = deviceId;
return inDirectory(objectiveCHostApp, () =>
exec(
'xcodebuild',
<String>[
......@@ -275,8 +273,8 @@ Future<void> main() async {
'EXPANDED_CODE_SIGN_IDENTITY=-',
'COMPILER_INDEX_STORE_ENABLE=NO',
],
)
)
));
}
);
section('Fail building existing Objective-C iOS app if flutter script fails');
......@@ -371,6 +369,7 @@ Future<void> main() async {
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
removeIOSimulator(simulatorDeviceId);
rmTree(tempDir);
}
});
......
......@@ -103,8 +103,10 @@ Future<bool> containsBitcode(String pathToBinary) async {
}
/// Creates and boots a new simulator, passes the new simulator's identifier to
/// `testFunction`, then shuts down and deletes simulator.
Future<void> testWithNewiOSSimulator(
/// `testFunction`.
///
/// Remember to call removeIOSimulator in the test teardown.
Future<void> testWithNewIOSSimulator(
String deviceName,
SimulatorFunction testFunction, {
String deviceTypeId = 'com.apple.CoreSimulator.SimDeviceType.iPhone-11',
......@@ -160,7 +162,10 @@ Future<void> testWithNewiOSSimulator(
);
await testFunction(deviceId);
}
/// Shuts down and deletes simulator with deviceId.
Future<void> removeIOSimulator(String deviceId) async {
if (deviceId != null && deviceId != '') {
await eval(
'xcrun',
......
# Copyright 2014 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
require 'json'
# Install pods needed to embed Flutter application, Flutter engine, and plugins
# from the host application Podfile.
#
......@@ -22,7 +28,8 @@ end
# install_flutter_engine_pod
# end
def install_flutter_engine_pod
engine_dir = File.join(__dir__, 'engine')
current_directory = File.expand_path('..', __FILE__)
engine_dir = File.expand_path('engine', current_directory)
if !File.exist?(engine_dir)
# Copy the debug engine to have something to link against if the xcode backend script has not run yet.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
......@@ -35,7 +42,8 @@ def install_flutter_engine_pod
# Keep pod path relative so it can be checked into Podfile.lock.
# Process will be run from project directory.
engine_pathname = Pathname.new engine_dir
project_directory_pathname = Pathname.new Dir.pwd
# defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
project_directory_pathname = defined_in_file.dirname
relative = engine_pathname.relative_path_from project_directory_pathname
pod 'Flutter', :path => relative.to_s, :inhibit_warnings => true
......@@ -55,19 +63,26 @@ def install_flutter_plugin_pods(flutter_application_path)
# Keep pod path relative so it can be checked into Podfile.lock.
# Process will be run from project directory.
current_directory_pathname = Pathname.new __dir__
project_directory_pathname = Pathname.new Dir.pwd
current_directory_pathname = Pathname.new File.expand_path('..', __FILE__)
# defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
project_directory_pathname = defined_in_file.dirname
relative = current_directory_pathname.relative_path_from project_directory_pathname
pod 'FlutterPluginRegistrant', :path => File.join(relative, 'FlutterPluginRegistrant'), :inhibit_warnings => true
symlinks_dir = File.join(relative, '.symlinks')
FileUtils.mkdir_p(symlinks_dir)
plugin_pods = parse_KV_file(File.join(flutter_application_path, '.flutter-plugins'))
plugin_pods.map do |r|
symlink = File.join(symlinks_dir, r[:name])
FileUtils.rm_f(symlink)
File.symlink(r[:path], symlink)
pod r[:name], :path => File.join(symlink, 'ios'), :inhibit_warnings => true
plugins_file = File.expand_path('.flutter-plugins-dependencies', flutter_application_path)
plugin_pods = flutter_parse_dependencies_file_for_ios_plugin(plugins_file)
plugin_pods.each do |plugin_hash|
plugin_name = plugin_hash['name']
plugin_path = plugin_hash['path']
if (plugin_name && plugin_path)
symlink = File.join(symlinks_dir, plugin_name)
FileUtils.rm_f(symlink)
File.symlink(plugin_path, symlink)
pod plugin_name, :path => File.join(symlink, 'ios'), :inhibit_warnings => true
end
end
end
......@@ -81,7 +96,7 @@ end
# Optional, defaults to two levels up from the directory of this script.
# MyApp/my_flutter/.ios/Flutter/../..
def install_flutter_application_pod(flutter_application_path)
app_framework_dir = File.join(__dir__, 'App.framework')
app_framework_dir = File.expand_path('App.framework', File.join('..', __FILE__))
app_framework_dylib = File.join(app_framework_dir, 'App')
if !File.exist?(app_framework_dylib)
# Fake an App.framework to have something to link against if the xcode backend script has not run yet.
......@@ -93,8 +108,9 @@ def install_flutter_application_pod(flutter_application_path)
# Keep pod and script phase paths relative so they can be checked into source control.
# Process will be run from project directory.
current_directory_pathname = Pathname.new __dir__
project_directory_pathname = Pathname.new Dir.pwd
current_directory_pathname = Pathname.new File.expand_path('..', __FILE__)
# defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
project_directory_pathname = defined_in_file.dirname
relative = current_directory_pathname.relative_path_from project_directory_pathname
pod '{{projectName}}', :path => relative.to_s, :inhibit_warnings => true
......@@ -110,37 +126,31 @@ def install_flutter_application_pod(flutter_application_path)
:execution_position => :before_compile
end
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
pods_array = []
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_array.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_array
# .flutter-plugins-dependencies format documented at
# https://flutter.dev/go/plugins-list-migration
def flutter_parse_dependencies_file_for_ios_plugin(file)
file_path = File.expand_path(file)
return [] unless File.exists? file_path
dependencies_file = File.read(file)
dependencies_hash = JSON.parse(dependencies_file)
# dependencies_hash.dig('plugins', 'ios') not available until Ruby 2.3
return [] unless dependencies_hash.has_key?('plugins')
return [] unless dependencies_hash['plugins'].has_key?('ios')
dependencies_hash['plugins']['ios'] || []
end
def flutter_root
generated_xcode_build_settings = parse_KV_file(File.join(__dir__, 'Generated.xcconfig'))
if generated_xcode_build_settings.empty?
puts "Generated.xcconfig must exist. Make sure `flutter pub get` is executed in the Flutter module."
exit
generated_xcode_build_settings_path = File.expand_path(File.join('..', '..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings.map { |p|
if p[:name] == 'FLUTTER_ROOT'
return p[:path]
end
}
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
# This should never happen...
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
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