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

Consuming .xcframework #851

Closed
sgammon opened this issue Jul 24, 2020 · 9 comments · Fixed by #1344
Closed

Consuming .xcframework #851

sgammon opened this issue Jul 24, 2020 · 9 comments · Fixed by #1344
Labels
P1 We need to fix this right away

Comments

@sgammon
Copy link

sgammon commented Jul 24, 2020

Hello rules_apple authors,

I am trying to use a .xcframework in an iOS app project (specifically, Firebase's static library, which contains .xcframeworks for various components).

Is there a supported way to do this? I had tried via apple_static_framework_import, but found it rejecting anything not ending in .framework. So, examining the structure of an .xcframework, I tried:

def _apple_xcframework(name, path, **kwargs):

    """ Consume an Apple XCFramework. """

    apple_static_framework_import(
        name = name,
        framework_imports = select({
            # x86: simulator or target
            "//defs/conditions:dev": native.glob([
                 "%s/ios-i386_x86_64-simulator/**" % path,
            ]),

            # ARM: prod or debug
            "//defs/conditions:release": native.glob([
                 "%s/ios-armv7_arm64/**" % path,
            ]),
        }),
        **kwargs
    )

i am using this like so:

apple_xcframework(
    name = "FirebaseCore-XCFramework",
    path = "Firebase/FirebaseAnalytics/FirebaseCore.xcframework",
)

which roughly translates to:

apple_static_framework_import(
    name = "FirebaseCore-XCFramework",
    framework_imports = select({
        "//defs/conditions:dev": native.glob([
             "Firebase/FirebaseAnalytics/FirebaseCore.xcframework/ios-i386_x86_64-simulator/**",
        ]),

        "//defs/conditions:release": native.glob([
             "Firebase/FirebaseAnalytics/FirebaseCore.xcframework/ios-armv7_arm64/**",
        ]),
    }),
)

is this the right way to consume .xcframeworks?

@keith
Copy link
Member

keith commented Jul 24, 2020

FWIW up to this point there hasn't been any work AFAIK on supporting xcframeworks. I would love to hear if others are working on this.

@allevato
Copy link
Member

There's some work currently in progress to develop a rule to produce an .xcframework bundle (with the goal of replacing ios_static_framework and also providing a way to create distributable dynamic frameworks, since that use case isn't served by ios_framework). No timeline on this though.

We aren't working on anything to consume them, however. Interestingly, writing a single rule/macro to generalize consuming an .xcframework bundle wouldn't be straightforward. The internal directory names that include the platform, architectures, and simulator/device variant aren't strict requirements; Xcode may generate them in a certain way today, but all that really matters is the information in the Info.plist that maps those values to the correct .framework subdirectory. As far as Xcode is concerned, you could have Foo.xcframework/MyCatWontStopJumpingOnMyDeskDuringVideoCalls/Foo.framework and as long as there's a mapping from a particular platform/architecture/variant to that directory in the Info.plist, Xcode will find it.

It's not even safe to assume things like "well maybe iOS simulator frameworks will always be ios-i386_x86_64-simulator", because if someone decides to only distribute 64-bit builds of their framework, that pattern won't match. But what's even weirder is that I've also seen the pattern reversed, as ios-x86_64_i386-simulator... so even the order isn't predictable.

So that leaves us with basically what you've done; manually using select() to glob the relevant .framework subdirectory based on some conditions that are appropriate for your app and based on what you can see of the directory structure. Since the information about where frameworks are located is only predictable based on information in Info.plist, it wouldn't be available at analysis time so there's not much we could do to generalize it beyond that†.

† Maybe you could do this as a repository rule, where you could read the plist file and dynamically generate a framework import BUILD target from that, but it would require you to make each .xcframework a separate "repository", which may be inconvenient. That isn't something we'd work on.

@sgammon
Copy link
Author

sgammon commented Aug 10, 2020

@allevato does it change anything that i am using entirely google code and this is a problem getting firebase to work with iOS and bazel? they distribute their code via .xcframeworks

i suppose not

@thii
Copy link
Member

thii commented Nov 12, 2020

@sgammon Assuming the internal directory names are known beforehand, how are you handling building for multiple cpus? (e.g. with --ios_multi_cpus=x86_64,arm64)?

@vchernyshov
Copy link

@sgammon Have you manage to add Firebase to Bazel project?

@tinder-maxwellelliott
Copy link

tinder-maxwellelliott commented Feb 24, 2021

@vchernyshov It is very possible to add Firebase to an iOS Project

Infra/Pods:apple_xcframework_import.bzl

(You will need to make config definitions for //Infra/Defs:development and //Infra/Defs:release, those are up to you to define)

""" Consume an Apple XCFramework. """

load(
    "@build_bazel_rules_apple//apple:apple.bzl",
    "apple_dynamic_framework_import",
    "apple_static_framework_import"
)


def apple_dynamic_xcframework_import(name, path, development_prefix, release_prefix, **kwargs):

    apple_dynamic_framework_import(
        name = name,
        framework_imports = select({
            "@//Infra/Defs:development": native.glob([
                 "{path}/{development_prefix}/**".format(path = path, development_prefix = development_prefix),
            ]),
            "@//Infra/Defs:release": native.glob([
                 "{path}/{release_prefix}/**".format(path = path, release_prefix = release_prefix),
            ]),
            "//conditions:default": native.glob([
                 "{path}/{development_prefix}/**".format(path = path, development_prefix = development_prefix),
            ])
        }),
        visibility = ["//visibility:public"],
        **kwargs
    )

def apple_static_xcframework_import(name, path, development_prefix, release_prefix, **kwargs):

    apple_static_framework_import(
        name = name,
        framework_imports = select({
            "@//Infra/Defs:development": native.glob([
                 "{path}/{development_prefix}/**".format(path = path, development_prefix = development_prefix),
            ]),
            "@//Infra/Defs:release": native.glob([
                 "{path}/{release_prefix}/**".format(path = path, release_prefix = release_prefix),
            ]),
            "//conditions:default": native.glob([
                 "{path}/{development_prefix}/**".format(path = path, development_prefix = development_prefix),
            ])
        }),
        visibility = ["//visibility:public"],
        **kwargs
    )

WORKSPACE

http_archive(
        name = "Firebase",
        urls = ["https://github.com/firebase/firebase-ios-sdk/releases/download/SOME_VERSION/Firebase.zip"],
        build_file = "@//Infra/Pods/Firebase:BUILD",
        strip_prefix = "Firebase"
)

//Infra/Pods/Firebase:BUILD

load("@//Infra/Pods:apple_xcframework_import.bzl", "apple_static_xcframework_import")

apple_static_xcframework_import(
    name = "FirebaseAnalytics",
    path = "FirebaseAnalytics/FirebaseAnalytics.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework",
    release_prefix = "ios-arm64_armv7/FirebaseAnalytics.framework",
    deps = [
        ":FirebaseCore",
        ":FirebaseCoreDiagnostics",
        ":FirebaseInstallations",
        ":GoogleAppMeasurement",
        ":GoogleDataTransport",
        ":GoogleUtilities",
        ":PromisesObjC",
        ":nanopb"
    ]
)

apple_static_xcframework_import(
    name = "FirebaseCore",
    path = "FirebaseAnalytics/FirebaseCore.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/FirebaseCore.framework",
    release_prefix = "ios-arm64_armv7/FirebaseCore.framework"
)

apple_static_xcframework_import(
    name = "FirebaseCoreDiagnostics",
    path = "FirebaseAnalytics/FirebaseCoreDiagnostics.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/FirebaseCoreDiagnostics.framework",
    release_prefix = "ios-arm64_armv7/FirebaseCoreDiagnostics.framework"
)

apple_static_xcframework_import(
    name = "FirebaseInstallations",
    path = "FirebaseAnalytics/FirebaseInstallations.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/FirebaseInstallations.framework",
    release_prefix = "ios-arm64_armv7/FirebaseInstallations.framework"
)

apple_static_xcframework_import(
    name = "GoogleAppMeasurement",
    path = "FirebaseAnalytics/GoogleAppMeasurement.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/GoogleAppMeasurement.framework",
    release_prefix = "ios-arm64_armv7/GoogleAppMeasurement.framework"
)

apple_static_xcframework_import(
    name = "GoogleDataTransport",
    path = "FirebaseAnalytics/GoogleDataTransport.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/GoogleDataTransport.framework",
    release_prefix = "ios-arm64_armv7/GoogleDataTransport.framework"
)

apple_static_xcframework_import(
    name = "GoogleUtilities",
    path = "FirebaseAnalytics/GoogleUtilities.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/GoogleUtilities.framework",
    release_prefix = "ios-arm64_armv7/GoogleUtilities.framework"
)

apple_static_xcframework_import(
    name = "PromisesObjC",
    path = "FirebaseAnalytics/PromisesObjC.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/PromisesObjC.framework",
    release_prefix = "ios-arm64_armv7/PromisesObjC.framework"
)

apple_static_xcframework_import(
    name = "nanopb",
    path = "FirebaseAnalytics/nanopb.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/nanopb.framework",
    release_prefix = "ios-arm64_armv7/nanopb.framework"
)

apple_static_xcframework_import(
    name = "FirebaseCrashlytics",
    path = "FirebaseCrashlytics/FirebaseCrashlytics.xcframework",
    development_prefix = "ios-arm64_i386_x86_64-simulator/FirebaseCrashlytics.framework",
    release_prefix = "ios-arm64_armv7/FirebaseCrashlytics.framework",
    deps = [
        ":FirebaseAnalytics"
    ]
)

@vchernyshov
Copy link

@tinder-maxwellelliott thanks a lot for you help

But what do you mean:
(You will need to make config definitions for //Infra/Defs:development and //Infra/Defs:release, those are up to you to define)

I'm not very familiar with Bazel so might ask very simple questions)

I have managed to add Firebase with commented:
"@//Infra/Defs:development": native.glob([ "{path}/{development_prefix}/**".format(path = path, development_prefix = development_prefix), ]), "@//Infra/Defs:release": native.glob([ "{path}/{release_prefix}/**".format(path = path, release_prefix = release_prefix), ]),

I need to add Firestore as well but got the error when build the project:
/var/tmp/_bazel_vchernyshov/8432bf40a9c3dc219067a8a2fb9ccda3/execroot/main/Infra/Pods/Firebase/FirebaseFirestore/BoringSSL-GRPC.xcframework/ios-arm64_i386_x86_64-simulator/BoringSSL-GRPC.framework/Modules/module.modulemap:1:27: Skipping stray token

/var/tmp/_bazel_vchernyshov/8432bf40a9c3dc219067a8a2fb9ccda3/execroot/main/Infra/Pods/Firebase/FirebaseFirestore/BoringSSL-GRPC.xcframework/ios-arm64_i386_x86_64-simulator/BoringSSL-GRPC.framework/Modules/module.modulemap:1:28: Expected '{' to start module 'BoringSSL'

/var/tmp/_bazel_vchernyshov/8432bf40a9c3dc219067a8a2fb9ccda3/execroot/main/Infra/Pods/Firebase/FirebaseFirestore/BoringSSL-GRPC.xcframework/ios-arm64_i386_x86_64-simulator/BoringSSL-GRPC.framework/Modules/module.modulemap:1:28: Expected module declaration

@vchernyshov
Copy link

@tinder-maxwellelliott I have manage to fix part of issues by removing module.modulemap files.
But got linking error: this is gist with my build file and error: https://gist.github.com/vchernyshov/87ce0227d89fa24b776a54ab2eb0de75
If you have any advice how to solve this it will be grate.

@tinder-maxwellelliott
Copy link

First: Are you running your build with the flag --ios_multi_cpus=x86_64?

Here is a simple way to create those defines definitions, the idea here is that we only want to target certain architectures for different build configs:

config_setting(
     name = "development",
     values = {
         "cpu": "x86_64",
         "compilation_mode": "dbg",
     },
 )

  config_setting(
     name = "release",
     values = {
         "cpu": "arm64",
         "compilation_mode": "opt",
     },
 )

Also you should not remove the module map files, I did not have to do that to make this work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P1 We need to fix this right away
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants