Multiple platform support for Single Target in podfile
Asked Answered
B

5

23

Is there any way to add multiple platform support for a single target through podfile?

For example my project is common for both iOS and Mac. They consume the same code base. So instead of creating multiple target for the same code, I added support for both iOS and MacOSX in the same target. It builds fine.

Now I want to add a dependency through Cocoapods. I create a podfile and specify my target's dependency on the pod. The pod in question here supports multiple platform in a similar way i.e. single target.

But now while building my project it fails for iOS.

Specifying multiple platforms in Podfile for single target produces an error.

And if I just specify platform as only iOS or Mac then the project in question fails to build on other platform.

Has anyone experienced this before? How can I add multiple platform for a single target through podfile?

P.S. - I know I can achieve it by creating multiple targets in my project. But I want to keep that as my last option.

Boar answered 7/1, 2019 at 13:23 Comment(6)
I'm similar situation. Were you able to find a solution for this?Iced
I also have the same issue. Any progress?Boice
Did you find a solution?Maryalice
Sorry guys. As much as I would like to help you guys. But I worked on this project quite long back and I ended up getting it to work perfectly but do not remember the solution. you can take a look at this git repo and try to see if the podspec or project settings help you in some way. github.com/microsoftgraph/msgraph-sdk-objcBoar
@VikasDadheech Your solution seems to have been to not use Cocoapods anymore as the project you have linked itself defines a pod but it uses no other pods (there is no Podfile) and thus doesn't fit to the question above.Metaphrase
Check the answer github.com/CocoaPods/CocoaPods/issues/…Chink
D
2
def import_pods

pod 'CorePlot'

end

target 'FirstAppWithMacOS' do
    # define your platform here.
    platform :ios, '9.0'
    import_pods
end

target 'FirstMacOSApp' do
    # define your platform here.
    platform :osx, '10.10'
    import_pods
end

See below image of my project :

enter image description here

Devour answered 7/6, 2019 at 11:7 Comment(5)
You describe two targets. The question and the problem is that XCode only has one target for iOS and Mac, not two. Let's call the target "iOSAndMac". Then your solution wont work.Boice
@Boice yes, i have one macOS app and inside i have one iOS app. used above pods code to combine them and execute in once. (edited my answer with example of project)Devour
@Boice might be helpful this link - #32086934Devour
I cannot use a macos-only target as XCode wont let me uncheck iPad after selecting macOS (UIKit for mac requires iPad is included). So if I use two targets like above, one will be iOS only and one will be iOS+macOS which is not ideal. It might work, but I have other issues with Cocapods right now not supporting Xcode 11 beta so I cannot verify. Your solution could be a workaround, but I assume it may not work. As the question was for one target, this is more of a workaround (that may or may not work)Boice
@iNiravKotecha But you still have two targets and that's not what this question is all about. Spliting your target into two targets means you have to make sure to always add files to both targets, change any build setting you want to change in both targets, link the same libraries to both targets, etc. Xcode can handle a single target for two platforms just fine but Cocoapods cannot.Metaphrase
M
1

Short answer

Is is not supported as of CocoaPods 1.10.2. There are reasons why it isn't supported and probably won't be supported any time soon or only when building with Xcode 13.

Long answer

When integrating a pod to a target, CocoaPods (CP) must perform a couple of steps to make that happen. I won't list all of them here but the following four are relevant to this answer:

  1. Create an umbrella header.
  2. Build a (dynamic/static) framework or static library.
  3. Adding the umbrella header to the header search path of the target.
  4. Liking the built framework/library to the target.

Let me go through these steps one by one:

1. Create an umbrella header

The content of the umbrella header depends on the platform the pod is built for. E.g. if it is macOS, it will import Cocoa, if it is iOS, it will import UIKit. As umbrella headers are treated in a special way, especially when using modules (and when dealing with Swift code as well, you do want to use modules, trust me), using #ifdef to make parts of them platform dependent is a rather bad idea. So to support multiple platforms, CP would need to create one umbrella header per platform.

Please note that this is exactly what CP does, if you integrate the same pod into two targets for two different platforms. E.g. if one target is macOS and one is iOS, there will be two umbrella headers, one ending in -macOS.h and one ending in -iOS.h. So in theory, this functionality is there, there is just no way to specify more than one platform per target so far and thus the created umbrella header will only work with one target platform. That is what Vikas Dadheech probably meant when he said:

And if I just specify platform as only iOS or Mac
then the project in question fails to build on other platform.

Currently the created header will only play nice with either platform. Please note that this also holds true for any auto-generated module map file (.modulemap).

2. Build a (dynamic/static) framework or static library

Just as before, how the library/framework is built depends on the platform as a Podspec file can specify different build options per platform and even select different files to be included in a build depending on platform, the pod must be built separately per platform, even if both platforms would use the same CPU instruction set (e.g. ARM Mac and ARM iPad) and the code otherwise contains nothing that is platform specific.

And just as before, this functionality is there when integrating the same pod in into different targets for different platforms, resulting in two libraries/frameworks with a platform suffix.

3. Adding the umbrella header to the header search path of the target

Now its getting a bit more tricky. Assuming we have two umbrella headers (and maybe also two module map files), only one of them must be visible when building the target they integrate into and which one depends the target platform. This is no problem when we are talking about different targets as different build settings per target are easy but in that case it's a single target and this comes with some implications.

Yet even that is solvable, e.g. by making settings depend on SDK, after all you will require a different SDK per platform, so you can do things like this:

HEADER_SEARCH_PATHS[sdk=macosx*] =  ....
HEADER_SEARCH_PATHS[sdk=iphoneos*] = ...

to ensure that only the headers and map files for the correct platform are visible for the build.

4. Liking the built framework/library to the target

And here comes the problem: If you want to use Xcode's linking, you can either link against the macOS or the iOS version you've built in step 2 but you cannot link to either one depending on the target platform you are building for. So how do you make sure the binary links against the correct library and only one of them?

You can ...

  • combine all versions into a universal binary and always link against this one. This will work as long as your target platforms have different CPU architectures (e.g. x86 for Mac and ARM for iOS) but it won't work if your platforms have equal ones (e.g. ARM Mac and ARM iPhone). With the instruction of ARM Macs, this option is pretty much dead and it was already not possible before when your two targets are iOS and tvOS since both have been ARM, too.

  • not use Xcode's linking at all and instead set linking flags by hand as well as library search paths using the same trick as for the headers above. Well, this will only work with static libraries and frameworks out of the box and only reliably with the new Xcode build system if you manually set target dependencies. With dynamic frameworks you also manually must copy the frameworks, sometimes (re-)sign them correctly, and also manage dependencies by hand. All possible, but seems rather fragile and may have even more implications than I just named above. I guess this is a somewhat risky approach with lots of possible pitfalls and some setups will simply not work that way at all, no matter what you do.

  • use the selective linking feature of Xcode 13. This is exactly what is needed here and Apple has probably implemented it as the CPU architecture no longer is suitable for many linking cases. Yet Xcode 13 is currently still in beta and if the feature relies upon that capability, it will require using Xcode 13 for building and this requires a Mac running at least Big Sur.

Metaphrase answered 6/9, 2021 at 1:47 Comment(0)
M
-2

The platform device is controlled by Base SDK and Supported Platforms in Architectures section in your target's Build Settings. If you didn't create the separated two targets for both macOS and iOS devices, there should be only one choice left for you to support those platform entries, that is, duplicate new two existing build configurations for macOS platform, then you could configure the settings separately.

For example, you have an existing iOS-based project and there are two default build configurations named Debug and Release. Now try to two new one for macOS, choose the main Xcode project -> choose project name (not target name here) -> select Info section in top -> tap + button under the Configurations section -> choose Duplicate "Debug"/"Release" Configuration -> rename it as Debug-macOS/Release-macOS or others, rename the original Debug/Release to Debug-iOS/Release-iOS, too.

Now you have two pairs of configurations to debug and archive the targeted platforms under the only one target's build settings. You could also create a new scheme for macOS development, just make sure that you choose the right configurations in different actions.

Talking to the main Podfile on cocoapods, if you maintain a cocoapods library by yourself and you want to add it as a dependency, it'd better support both macOS and iOS platform in your podspec file, of course, you need to make compatibility for those platforms, AFNetworking did it like that. Here is the main conversion task, do not use any API under the UIKit for macOS platform. To distinguish the platform difference in compiling time, you could use these macros for that,

#if TARGET_OS_IOS
   // iOS supported
#elif TARGET_OS_OSX
   // macOS supported
#endif

If you want to use a third-party library in your standalone project target, check the compatible issue first, if it only works for iOS platform, you could specify the dependent library for only one platform like this,

pod 'PonyDebugger', :configurations => ['Debug-iOS', 'Release-iOS']

Same with macOS supported.

Hope it helps!

Mercymerdith answered 11/6, 2019 at 7:58 Comment(4)
Why downvote? Does someone point out something wrong for me? ThanksMercymerdith
Hmm not sure, up voted anyway for quality of informationHohenlohe
Your solution doesn't work as Cocoapods will create an Umbrella header for the built pod and this umbrella header will include Cocoa when you specified platform :macos in your Podfile, even if the current configuration is building for iOS. And as Cocoa is not available for iOS, the build will fail. It will include UIKit for platform :ios. You can only provide one platfrom per target.Metaphrase
Oh and in case you think you can solve this by specifying no platform at all, well, you cannot. If you specify no platform, CP extracts the platform from your build configuration and if you have multiple configurations targeting multiple platforms, then pod install will fail with Consistency issue: build setting SDKROOT has multiple values:Metaphrase
B
-3

I did same kind of implementation in my one of project which has 3 targets. I have created 'def' shared pods and then call this shared_pods in all 3 targets. In this way, It works pretty well. I did only for iOS app.

# Uncomment the next line to define a global platform for your project
 platform :ios, '9.0'

def shared_pods
    pod 'Stripe'
    pod 'Google/SignIn'
end

target 'App_Dev' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

    shared_pods
 end

target 'App_QA' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

    shared_pods
 end


target 'App_Release' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

    shared_pods
 end
Brightman answered 8/1, 2019 at 12:48 Comment(1)
Thanks for the answer. But it does not solve my query. I want to support multiple platforms. To make it more clear, in your podfile you have one target 'App_Dev'. But the pods are building only for iOS platform. What if 'App_Dev' also supported Mac platform. In that case how can I get the pods to build for both iOS and Mac platforms.Boar
S
-3

If You want to support multiple platforms in a single target in your Podfile, You can use the platform directive to specify the platforms you want to support. For Example

    platform :ios, '9.0'
    platform :watchos, '3.0'

Also you can specify a minimum version and a maximum version for each platform. For Example

    platform :ios, '9.0'..'14.0'
Sulfur answered 12/1, 2023 at 9:32 Comment(1)
I've got this error: Invalid 'Podfile' file: The target 'Pods' already has a platform set. It seems cocoapods doesn't allow multiple platform declared like that.Dumpcart

© 2022 - 2024 — McMap. All rights reserved.