Private module map for a framework
Asked Answered
L

2

22

I'm using this answer to create a module map to create a module for CommonCrypto so I can use it in a framework.

Doing this however means that any projects that I use this framework in have access to CommonCrypto with import CommonCrypto - and even worse, declaring CommonCrypto in another framework and importing this into the project results in Redefinition of module 'CommonCrypto' errors.

I.e. the following setup:

MainProject
    |--> import FrameworkA - module map for CommonCrypto
    |--> import FrameworkB - module map for CommonCrypto

Is there a way to create a module map but have it private to that Framework its created/used in? (Much like the internal access attribute in Swift for a Framework). The llvm Clang docs show a private attribute but I can't work out where to put this in my module map, and it might not even be for this purpose! There's also an export attribute but again I'm not entirely sure how to use this...!

This is my module map I'm using for CommonCrypto - the $(SDKROOT) gets swapped out in a build phase to the correct location (for iphoneos or iphonesimulator SDKs):

module CommonCrypto [system] [extern_c] {
    umbrella header "$(SDKROOT)/usr/include/CommonCrypto/CommonCrypto.h"
    export *
}

This works fine (except you can't "go to definition" but I don't mind that) for use in FrameworkA / FrameworkB.

Lathrope answered 14/12, 2015 at 11:1 Comment(3)
Hi, any updated on this? I have the same exact setup for libz :(Reverberate
Rich, could you please provide your build phase script that replaces $(SKROOT)? Thanks!Warship
@appleitung: Script is here: gist.github.com/rhodgkins/5eecee8bcbdb6021fc798247132e9fa7 and then set it up like this: postimg.org/image/fj7j9nsqp in a project. Don't forget to add the directory $(PROJECT_DIR)/$(TARGET_NAME)/ExternalFrameworks/ to the *Framework Search Paths build setting as well. Then all module maps should sit in the input directory folder - with the folder name containing the modulemap being the name.Lathrope
R
8

Disclaimer: I have not tried this for CommonCrypto but it works for my case with libz

A possible solution to this is to create a module.private.modulemap as described in the Clang documentation

So for example in FrameworkA you can write a module.modulemap file for FrameworkA like so:

module FrameworkACommon {
}

Then you would create a module.private.modulemap file like so

explicit  FrameworkACommon.Crypto [system] [extern_c] {
   header "/Applications/Xcode6-Beta5.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.0.sdk/usr/include/CommonCrypto/CommonCrypto.h"
    link "CommonCrypto"
    export *
}

Then repeat for FrameworkB.

Now CommonCrypto is a private module in both FrameworkA and FrameworkB and the names won't clash.

Reverberate answered 3/2, 2016 at 10:49 Comment(10)
Nice! Thanks for sharing - I shall try it!Lathrope
@Reverberate I get lot of 'use of undeclared type ...'. Have you faced this ?Migrant
You're probably getting undeclared type because your module is not exporting any headers. The export * will export headers to FrameworkACommon and to import it you have to write FrameworkACommon.Crypto but it will stop there. Exported headers won't be visible past FrameworkACommon, they are internal now.Reverberate
Is there any way to get this kind of thing to work using CocoaPods? I've tried a number of things with modulemaps, but I just can't seem to get it to workDekow
@Dekow You can specify a module map for a CocoaPod spec.Lathrope
Is that a typo: "in FrameworkA you can write a module.modulemap file for CommonCrypto like so". Is that meant to say "in FrameworkA you can write a module.modulemap file for FrameworkA like so"?Juliettajuliette
@Juliettajuliette That is a typo. Fixed.Reverberate
Struggling to get this to work with Xcode 8 :-) The module maps you give, you don't just add those to the Swift import path, you also set them as the "Module Map File" and "Private Module Map File" in the target, right? Also, yours says "module FrameworkACommon {}", given the setup of the question, is that another typo and should be "module FrameworkA" and the "explicit module "FrameworkA.CommonCrypto"?Juliettajuliette
Can you please check this: https://mcmap.net/q/176152/-extending-a-public-class-for-partial-public-access-in-swift/1364053Offertory
This answer no longer works, since the way private module maps work has changed in Clang. The root module of a private module map now needs to be named Foo_Private. See https://mcmap.net/q/501994/-excluding-internal-headers-from-framework-umbrella-header for a complete way that works in December 2020.Horace
P
0

I'm not entirely sure this applies to the use case described in this question, but I'm gonna post an answer nonetheless, in hope it helps some folks out there struggling with preventing a module in a Framework from being accessible from an external app.

I've posted this answer in a similar, more recent, SO question.

It involves using native code from Swift through a module without making this module accessible to external apps. I think the real trick here, and that could relate to this question the most, is the use of this new experimental Swift feature that allows to set an access-level modifier to an import, and thus prevent it from leaking in the external app that includes the Framework.

As I said in the other SO question, this feature will be fully available in Swift 6, and imports should even be internal by default.

Piscine answered 12/7 at 10:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.