Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Swift Package Manager to be used with Add to App #146957

Open
vashworth opened this issue Apr 17, 2024 · 3 comments
Open

Allow Swift Package Manager to be used with Add to App #146957

vashworth opened this issue Apr 17, 2024 · 3 comments
Assignees
Labels
P2 Important issues not at the top of the work list team-ios Owned by iOS platform team tool Affects the "flutter" command-line tool. See also t: labels. triaged-ios Triaged by iOS platform team

Comments

@vashworth
Copy link
Contributor

vashworth commented Apr 17, 2024

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

rsync -av --delete --filter "- .DS_Store" "${BUILT_PRODUCTS_DIR}/Flutter.framework" "${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"
fi

This 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")
    ]
}
@vashworth vashworth added tool Affects the "flutter" command-line tool. See also t: labels. P2 Important issues not at the top of the work list team-ios Owned by iOS platform team triaged-ios Triaged by iOS platform team labels Apr 17, 2024
@vashworth
Copy link
Contributor Author

vashworth commented Apr 17, 2024

cc @jmagman for feedback.

I have built a prototype and solution proposed is working as I would expect, but it seems like such a hacky thing to do

@p-mazhnik
Copy link
Contributor

Unfortunately, SPM doesn't really have a way to convert a package into an xcframework like CocoaPods does

The big limitation that Flutter has with xcframeworks and CocoaPods is the inability to handle common dependencies, #130220. If both host app and Flutter module (Flutter module's plugin) define the same pod dependency, and Flutter module is integrated using Option B (with xcframeworks), there will be an errors like Multiple commands produce 'CommonDependency.framework'. The workaround is to link every plugin source (podspec) from the Flutter module directly to the host app (in Podfile), instead of using plugins' xcframeworks produced by build ios-framework command, #114692. That was one of the reasons build ios-framework --no-plugins flag was introduced.

I am not so familiar with SPM, but the proposed alternative reminded me of this workaround, so I wanted to point it out. I hope the issue can be avoided for SPM.

Also, in CocoaPods case, each plugin defines Flutter as a dependency in their podspec, so maybe smth similar can be done in SPM case to avoid errors about Flutter header not being found.

s.ios.dependency 'Flutter'
s.osx.dependency 'FlutterMacOS'

@vashworth
Copy link
Contributor Author

The big limitation that Flutter has with xcframeworks and CocoaPods is the inability to handle common dependencies, #130220.

That's good to know about, thanks. I think SPM will not have this problem since dependency resolution will be handled by SPM, but I'll test it out.

Also, in CocoaPods case, each plugin defines Flutter as a dependency in their podspec, so maybe smth similar can be done in SPM case to avoid errors about Flutter header not being found.

Unfortunately, this isn't really possible with SPM because SPM needs to know where to find the framework and if it doesn't, it'll fail to resolve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P2 Important issues not at the top of the work list team-ios Owned by iOS platform team tool Affects the "flutter" command-line tool. See also t: labels. triaged-ios Triaged by iOS platform team
Projects
None yet
Development

No branches or pull requests

2 participants