bazel-ios/rules_ios

Module not found for xcframework dependency with static Swift libraries inside

viktorsimko-rev opened this issue · 16 comments

I'm trying to add an xcframework dependency containing static Swift libraries which looks something like this:

├── Info.plist
├── ios-arm64
│   ├── SomeLibrary.swiftmodule
│   └── libSomeLibrary.a
└── ios-arm64_x86_64-simulator
    ├── SomeLibrary.swiftmodule
    └── libSomeLibrary.a

So as you can see it just contains a .switmodule and the static archive.

Currently the import target for this is created here in the third branch (("static", "library")) in library.bzl:

    if (linkage, packaging) == ("dynamic", "framework"):
        apple_dynamic_framework_import(
            name = resolved_target_name,
            framework_imports = native.glob(
                [path + "/**/*"],
                exclude = ["**/.DS_Store"],
            ),
            deps = [],
            dsym_imports = dsym_imports,
            tags = _MANUAL,
        )
    elif (linkage, packaging) == ("static", "framework"):
        apple_static_framework_import(
            name = resolved_target_name,
            framework_imports = native.glob(
                [path + "/**/*"],
                exclude = ["**/.DS_Store"],
            ),
            deps = [],
            tags = _MANUAL,
        )
    elif (linkage, packaging) == ("static", "library"):
        native.objc_import(
            name = resolved_target_name,
            archives = [path],
            tags = _MANUAL,
        )
    else:
        fail("Unsupported xcframework slice type {} ({}) in {}".format(
            build_type,
            identifier,
            xcframework_name,
        ))

It uses native.objc_import which doesn't expose the Swift module, so it won't be found compile time by the dependent targets.

If I create a rule for exposing the module using a SwiftInfo provider it solves the issue:

def _swift_import_impl(ctx):
    providers = [dep[CcInfo] for dep in ctx.attr.deps]
    providers += [dep[apple_common.Objc] for dep in ctx.attr.deps]

    return providers + [
        swift_common.create_swift_info(
            modules = [
                swift_common.create_module(
                    name = ctx.attr.name,
                    swift = swift_common.create_swift_module(
                        swiftdoc = None,
                        swiftmodule = ctx.file.swiftmodule,
                    )
                )
            ]
        )
    ]

swift_import = rule(
    implementation = _swift_import_impl,
    attrs = {
        "swiftmodule": attr.label(allow_single_file = True),
        "deps": attr.label_list(default = [])
    }
)

usage of the rule in the above included code snippet from library.bzl:

    elif (linkage, packaging) == ("static", "library"):
        libdir = path.rpartition("/")[0]
        native.objc_import(
            name = resolved_target_name + "_objc_import",
            archives = [path],
            tags = _MANUAL,
        )
        swift_import(
            name = resolved_target_name,
            swiftmodule = native.glob([libdir + "/**/*.swiftmodule"], exclude_directories = 0)[0],
            deps = [resolved_target_name + "_objc_import"]
        )

Do you know of an already existent solution for this? Am I missing something?

There's a similar issue with missing headers for Objective C static libraries within xcframeworks: https://**/archives/CRPRV58UF/p1659897884759499

I plan on creating a reproducible example this week so we can add support / fix the issues here.

Might be the case this setup is not currently supported.

@ob @jerrymarino

Yep it looks like giving rules_ios a library based framework needs a number of fixes. I think what you've got here: globbing for the same inputs adjacent to the .a is a fine way to add it.

@viktorsimko-rev thanks again for the issue - what it looks like is this case is basically not handled yet: a .xcframework with .a s in it.

@luispadron @jerrymarino thank you for looking into this!

@viktorsimko-rev Would you happen to know how to create a .swiftmodule when archiving a static library via Xcode? I've got my PR up fixing these issues for Objective-C but my archives don't contain an .swiftmodule for use within Swift frameworks

@luispadron it creates a .swiftmodule by default for Static Library targets. (If it's Swift only at least)

@luispadron sorry, I'm a bit sleepy, it creates it but then doesn't include it in the archive indeed.
I think the only option here might be just building it and not archiving.

You've got it - would be great if we can get this working 👍

@luispadron I'm not sure about how to actually create one of these off hand in Xcode - if there is anything close to a public or canonical specification of what this code should do it'd probably be in the SPM code. I would suggest to look there, or see if there is an easy way to create one from there.

Ingesting vendor binaries into rules_ios created by SPM ( and Xcode ) is a highly reasonable use case. You can also reach out to the vendor in question ( or if they have code on github ) about how it was created.

@jerrymarino @luispadron
I've found this: apple/swift-package-manager#2981 (comment)
Here Boris copies the .swiftmodule from derived data into the archive.

So I checked derived data using the link you posed @viktorsimko-rev and I'm still not seeing a .swiftmodule

└── Release-iphoneos
    └── libLoggerStaticLib.a 

It only has the .a file and now .swiftmodule anywhere in the derived data folder. From the issue you linked it looks like this requires a framework, and not a static library to work.. Going to see if we can wrap it in a framework somehow. Truly, love how undocumented all of these Apple formats are.

@luispadron That's weird, because it's there for me:

Release-iphonesimulator
├── TestLib.swiftmodule
│   ├── Project
│   │   ├── arm64-apple-ios-simulator.swiftsourceinfo
│   │   ├── arm64.swiftsourceinfo
│   │   ├── x86_64-apple-ios-simulator.swiftsourceinfo
│   │   └── x86_64.swiftsourceinfo
│   ├── arm64-apple-ios-simulator.swiftdoc
│   ├── arm64-apple-ios-simulator.swiftinterface
│   ├── arm64-apple-ios-simulator.swiftmodule
│   ├── arm64.swiftdoc
│   ├── arm64.swiftinterface
│   ├── arm64.swiftmodule
│   ├── x86_64-apple-ios-simulator.swiftdoc
│   ├── x86_64-apple-ios-simulator.swiftinterface
│   ├── x86_64-apple-ios-simulator.swiftmodule
│   ├── x86_64.swiftdoc
│   ├── x86_64.swiftinterface
│   └── x86_64.swiftmodule
└── libTestLib.a

Could you post the project here? I can take it and update the fixture in the PR.

Oh I see this just a pure Swift project, thats fine, will use this to add an extra fixture on-top of the Objective-C one I made. There are two issues with static library handling from what I can tell

  • rules_ios doesn't use the .swiftmodule included with the static library (this issue)
  • rules_ios doesnt use the Headers included with the static library

@viktorsimko-rev I've updated #530 to include a Swift fixture static library xcframework. I'm focused on fixing the Objective-C use case right now but if you already have a working solution for the Swift one please feel free to push changes to that PR / branch from there!

@luispadron The solution I currently have does not yet work with import middleman or vfs, but I will try to see what can I do with those, I just don't know how much time I'm going to have in the upcoming days for that.