-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Build solution for add to app to be able to use Swift Package Manager for plugins.
Design / Proposal
Unfortunately, SPM doesn't really have a way to convert a package into an xcframework like CocoaPods does. It seems at some point there may have been a hacky way to do it, but I was unable to get it to work.
As an alternative, we can create a FlutterPluginRegistrant Swift Package that then has dependencies on all the Swift Package plugins and CocoaPod xcframeworks. The tricky part, though, is adding the dependency of the Flutter framework. If a plugin does not have a direct dependency on the Flutter framework, it's not guaranteed to be processed before the plugin compiles, which can cause errors about Flutter header not being found.
In order to get around this issue, we could do one of the following:
Option 1 - Use Build Phases
In #146256, we get around this issue by using a pre-build script that copies the Flutter framework into a spot (BUILT_PRODUCTS_DIR) Swift Package Manager automatically uses as a framework search path.
We could do the same for add to app.
prepare_framework.sh
simulator=false
if [[ $SDKROOT == *"iphone"* ]]; then
if [[ $SDKROOT == *"simulator"* ]]; then
simulator=true
fi
else
echo "No iOS" 1>&2
exit -1
fi
directory="path/to/build/ios/framework/${CONFIGURATION}/FlutterFrameworks/Flutter.xcframework"
flutter_framework_directory=""
for file in "$directory"/*; do
if [[ "$file" == *"ios-"* ]]; then
is_simulator_directory=false
if [[ "$file" == *"-simulator" ]]; then
is_simulator_directory=true
fi
if $simulator; then
if $is_simulator_directory; then
flutter_framework_directory="$file"
fi
else
if ! $is_simulator_directory; then
flutter_framework_directory="$file"
fi
fi
fi
done
mkdir -p "$BUILT_PRODUCTS_DIR"
rsync -av --delete --filter "- .DS_Store" "$flutter_framework_directory/Flutter.framework" "$BUILT_PRODUCTS_DIR"It would require also adding a post compile/link/embed phase, that copies the Flutter.framework from the $BUILT_PRODUCTS_DIR to the ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH} directory and then codesigning it with $EXPANDED_CODE_SIGN_IDENTITY.
copy_and_codesign_framework.sh
xcode_frameworks_dir="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
mkdir -p -- $xcode_frameworks_dir
frameworkPath="${BUILT_PRODUCTS_DIR}/Flutter.framework"
# Thin Framework to only needed archs
lipo_info=$(lipo -info "${frameworkPath}/Flutter")
lipo "${frameworkPath}/Flutter" -verify_arch "${ARCHS}"
lipo_verification_result=$?
if [ $lipo_verification_result != 0 ]; then
echo "Binary ${frameworkPath}/Flutter does not contain ${ARCHS}" 1>&2
exit -1
fi
if [[ $lipo_info != "Non-fat file:"* ]]; then
extract_command=(
"lipo"
"-output"
"${frameworkPath}/Flutter"
)
for arch in "${ARCHS}"
do
extract_command+=("-extract")
extract_command+=("${arch}")
done
extract_command+=("${frameworkPath}/Flutter")
eval "${extract_command[*]}"
fi
# Copy Flutter framework from BUILT_PRODUCTS_DIR to TARGET_BUILD_DIR.
rsync -av --delete --filter "- .DS_Store" "${frameworkPath}" "${xcode_frameworks_dir}/"
# Sign the binaries we moved.
if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ]]; then
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
fiThis would required ENABLE_USER_SCRIPT_SANDBOXING to be set to NO in the project's settings.
Option 2 - Parse and alter Package.swift for each plugin
My current plan is instead to inject a dependency at the bottom of each plugin's Package.swift like so:
package.dependencies += [
.package(path: "/path/to/flutter_framework_swift_package")
]
let result = package.targets.filter({ $0.name == "plugin_name" })
// or maybe:
// let result = package.targets.filter({ $0.type == PackageDescription.Target.TargetType.regular })
if let target = result.first {
target.dependencies.append(
.product(name: "Flutter", package: "FlutterFrameworkPackage")
)
}We also will need to ensure the supported platforms for the plugin are higher than or equal to that of the flutter framework (otherwise Swift Package Manager may give an error).
To do this we can use swift package dump-package to convert the Package.swift to JSON to check if the version needs to be updated and then inject some swift code at the bottom of the Package.swift to alter the supported platforms, for example:
if package.platforms != nil {
package.platforms = package.platforms?.filter({ !String(describing: $0).contains("ios") })
package.platforms?.append(.iOS("12.0"))
} else {
package.platforms = [
.iOS("12.0")
]
}