CocoaPods: working with internal pods without hassle
CocoaPods is cool. I was honoured to have a chat with Fabio Pelosin, the main contributor, at the NSLondon meetup some time ago and see how much passion those guys put in this project.
I was one of the first supporters back in 2012 and I have some Pods in the Specs repo (ADB prefixed). Recently, I spent several days going through some hidden aspects of CocoaPods, ending up reading some source code from the Core and Xcodeproj.
During one of the recent NSLondon(s) of 2013, Orta explained the advantages of CocoaPods and Abizer Nasir, in one of the following events, discussed the usage of Git submodules. Basically comparing their visions.
What I'm going to explain here is a solution to a common scenario:
Manage the versioning of internal private pods within projects without hassle.
Ok, let's start. You have your project under our DVCS (which I hope is Git for your sanity).
~/MyProject
└── .git
└── Sources
└── MyProject.xcodeproj
You use some third-party components like AFNetworking and MagicalRecords and you have submodules for that, you hipster!
~/MyProject
└── .git
└── Sources
└── MyProject.xcodeproj
└── Vendor
└── AFNetworking (submodule)
└── .git
└── MagicalRecord (submodule)
└── .git
You decide to use CocoaPods, install it following the instructions and you add the Podfile
.
platform :ios
pod 'AFNetworking', '~> 2.0'
pod 'MagicalRecord', '~> 2.2'
You remove the submodules and run pod install
: the workspace is created for you and you're good to go. So far so good.
~/MyProject
└── .git
└── Podfile
└── Sources
└── MyProject.xcodeproj
└── MyProject.xcworkspace
└── Pods
└── Pods.xcodeproj
└── AFNetworking
└── MagicalRecord
Then you realize that it'd be cool to have your own private repo of pods and create private pods for some parts of your project. You like modular things and maybe, one day your pods will be ready for a pull request to the Specs repo to contribute to the open source community.
So you create your own repo (mine is https://github.com/albertodebortoli/ADBCocoaPodsRepository) and add it to CocoaPods.
$ pod repo add REPO_NAME SOURCE_URL
Time to create a spec for your private pod, tag it and push it. Something like this:
Pod::Spec.new do |s|
s.name = 'MyInternalLibrary'
s.version = '1.0.0'
s.platform = :ios, '7.0'
s.summary = 'My first pod!!!!111'
s.homepage = 'https://github.com/me/MyInternalLibrary'
s.author = { 'John Doe' => 'john.doe@example.com' }
s.source = { :git => 'https://github.com/me/MyInternalLibrary.git', :tag => s.version.to_s }
s.license = { :type => 'New BSD License', :file => 'LICENSE' }
s.source_files = '*.{h,m}'
s.requires_arc = true
end
pod install
and the situation is as follows:
~/MyProject
└── .git
└── Podfile
└── Sources
└── MyProject.xcodeproj
└── MyProject.xcworkspace
└── Pods
└── Pods.xcodeproj
└── AFNetworking
└── MagicalRecord
└── MyInternalLibrary
~/MyInternalLibrary
└── .git
└── MyInternalLibrary.podspec
└── Sources
└── MyInternalLibraryDemo.xcodeproj
You want to put the Pods
folder in the .gitignore
file.
Cool! But... there's a but. You're actively developing on MyInternalLibrary
, you're touching those files several times a day. The files you're touching will be overwritten the next time you run pod install
. Oh shit. You don't wanna open ~/MyInternalLibrary
, touch things, pod install
~/MyProject
over and over. It's not practicable.
Solution is to use development pods. When you install a development pod, its files are symbolically linked within Pods.xcodeproj
.
The Podfile
, now, specifies that the pod needs to be fetched from a local directory (with the use of the :path
directive). A development pod does not require to be versioned with a Git repo, but it must contain the podspec which describes how to retrieve the pod files. This is your new Podfile
:
platform :ios
pod 'AFNetworking', '~> 2.0'
pod 'MagicalRecord', '~> 2.2'
pod 'MyInternalLibrary', :path => '~/MyInternalLibrary'
That's great, now you can touch things safely and you can commit changes to MyInternalLibrary
(in ~/MyInternalLibrary
) while you are working on MyProject.xcworkspace
.
Here comes the interesting bit.
You have a CI and deployment system that need to have a specific version of your internal pod. You are tempted to tag changes to MyInternalLibrary
when deploying and use a specific Podfile
in your release branch of MyProject
.
platform :ios
pod 'AFNetworking', '~> 2.0'
pod 'MagicalRecord', '~> 2.2'
pod 'MyInternalLibrary', '~> 1.0.1'
Yeah... yeah... you're cool.
No, you're not.
Do you see the problem here? Keep the versioning/tagging of the internal pods updated is a pain in the ass. Developer's time should never be wasted this way.
I see you thinking...
"Damn, pods are not quite right here. I'm a hipster and a submodule would be perfect! But ufff... submodules or CocoaPods? Uff uff...".
The answer is... use both! Is the versioning/tagging of your library (just for accommodating CocoaPods) complex? Well, let the submodules handle it for you (using the sha1
of the commit, you know... Git things to point to the correct revision).
The ideal solution is to put your internal library inside the folder of the project and treat it as a git submodule:
~/MyProject
└── .git
└── Podfile
└── Sources
└── MyProject.xcodeproj
└── MyProject.xcworkspace
└── MyInternalLibrary (submodule)
└── .git
└── MyInternalLibrary.podspec
└── Sources
└── MyInternalLibraryDemo.xcodeproj
└── Pods
└── Pods.xcodeproj
└── AFNetworking
└── MagicalRecord
└── MyInternalLibrary
Your Podfile
will be
platform :ios
pod 'AFNetworking', '~> 2.0'
pod 'MagicalRecord', '~> 2.2'
pod 'MyInternalLibrary', :path => './MyInternalLibrary'
When running pod install
, files in ~/MyProject/MyInternalLibrary
will be symbolically linked within Pods.xcodeproj
.
Now you can
- work on your project and change your libraries/pods while developing
- commit changes to your libraries/pods independently of your project
- tag/version your internal libraries/pods when you want to (not when it is requested by your project)
- avoid the need of a specific Podfile for the release branch on MyProject
People way too often want to embrace CocoaPods fully, others prefer just submodules (and they are wrong, period). I think that the pragmatic solution to edge cases like this one is to use both!
Work as if it was just a submodule but with the unleashed power of CocoaPods!