Developing Private In-House Libraries with CocoaPods
For iOS/Mac development, CocoaPods (http://cocoapods.org/) is the dependency manager we use at Object Partners to discover and integrate a wide variety of open source libraries available online. Following the CocoaPods “podspec” is great for sharing library code with masses. That said, it also has nice support for managing the privately shared libraries that OPI uses across app projects.
This post provides a glimpse into how we leverage the power of CocoaPods to manage private libraries for iOS development. It assumes the reader is familiar with CocoaPods and how it is used to discover and bring in third party libraries. It illustrates OPI’s approach to developing and releasing shared iOS code through a step-by-step walkthrough of our process. The key focus areas we will touch on while walking through our process to manage in-house libraries are:
- Setting up a Private Spec Repo
- Local Development Support for a private in-house pod
- Release Process for a private in-house pod
- Ease In-House Development with Custom Script Commands
Throughout this post there is reference to the pod-dev script that has a set of useful commands supplementing CocoaPods and supporting in-house iOS library development. The shell script template can be download here and customized to meet your needs.
To get the most out of this blog post, it is recommended you download the pod-dev script, customize it for your environment and make sure it is available on your executable PATH.
Ok, let’s get started.
What You Will Need
- – Mac OSX
- – Git (ie: GitHub with Private Repos)
- – XCode 5.1.1
- – CocoaPods 0.33.1
- – pod-dev Custom Script -> Support in-house CocoaPod Development
Six Steps We Will Cover
- Step 1: Setting up a Private Spec Repo
- Step 2: Enable Local Development Support
- Step 3: Initial Setup of an In-House Library
- Step 4: Create CocoaPods Podspec for the In-House Library
- Step 5: App Development: Working with In-House CocoaPods
- Step 6: Releasing an In-House CocoaPod
Useful Links
- http://guides.cocoapods.org/making/private-cocoapods.html
- http://guides.cocoapods.org/contributing/release-process.html
Step 1: Setting up a Private Spec Repo
OPI uses GitHub to manage project artifacts for mobile apps and shared
library code. The first step in managing private in-house libraries is to setup a private GitHub repository for holding a CocoaPod podspec for each private pod.
At OPI we created a new private repository named “OPISpecs” but you can name this repository anything you like for your purposes. As versions of a podspec are pushed to this private repository its structure will adhere to:
[PRIVATE REPO NAME] --[SPEC NAME] --[VERSION] --[SPEC NAME].podspec
To add this newly created repository to a CocoaPods installation execute the following command:
$ pod repo add [Private Repo Name] [GitHub HTTPS clone URL]
Once the repo has been added, the CocoaPods installation will look in the private specs repository as well as the Master Specs repository for available pods.
To verify that CocoaPods knows about this repo:
$ cd ~/.cocoapods/repos
You should see the private GitHub repository you just created cloned to this
directory.
Step 2: Enable Local Development Support
You are now ready to prepare your computer for local development of a private in-house library using CocoaPods. The recommended way to manage development is to create a home folder where all private CocoaPods will be developed.
The ‘pod-dev’ script expects this home folder to be setup as an environment variable named $POD_LOCAL_HOME. I recommend adding this to your .bashprofile so it is globally set when you open a command-line window. As an example.
$ POD_LOCAL_HOME=~/PodsLocal $ export POD_LOCAL_HOME
Step 3: Initial Setup of an In-House Library
To continue this walkthrough, lets pretend you are creating a new in-house library for iOS development. For this example let’s call the library ‘SampleFoundation’.
Create the XCode Static Library Project
Create a new static library project in XCode by doing the following:
- Open XCode and select New Project
- Select Cocoa Touch Static Library and Click Next
- Enter SampleFoundation as the Product Name, and a suitable organization and company identifier.
- When you click Next you will be prompted where to save the project.
- Navigate to the $POD_LOCAL_HOME folder you setup in step 2
- Click Create
- Confirm $POD_LOCAL_HOME/SampleFoundation was created and SampleFoundation.xcodeproj is ready for development.
Create New GitHub Repository for the Static Library Project
Login to GitHub and create a new private repository named ‘SampleFoundation’ to manage the iOS code for the library. At OPI we have a separate private repository for each in-house library we are developing. This allows each privately shared library’s development and release cycles to be managed independently.
Make sure you create a blank repository and the GitHub option to “Initialize this repository with a README” remains unchecked. This will ensure less hassle when initializing and pushing the repository from your computer.
Initialize Git for the XCode project
Once the repository is created online we need to push our XCode static library project to the newly created remote repository. The basic steps from a command line window:
$ cd $POD_LOCAL_HOME/SampleFoundation $ git init .
Before committing it is wise to modify the .gitignore file to play nicely with XCode projects. The following is a typical .gitignore for XCode projects with CocoaPods. You will notice CocoaPods related files are part of .gitignore. The pros and cons of whether or not to source control any CocoaPods generated artifacts are discussed in http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control?.
# files created by XCode 3 *.pbxuser *.mode1 *.mode1v3 *.mode2v3 *.perspectivev3 *.xcclassmodel/ # files created by Xcode 4 xcuserdata/ # build products *.build/ build/ DerivedData/ # Misc .DS_Store #custom & CocoaPods Podfile.lock Pods/
Commit and Push to GitHub
The final step in the initial project setup is to commit the changes and push everything to your created GitHub remote repository. The following steps should do the trick:
$ git add .gitignore $ git add * $ git commit -s -m"Initial Commit of Library" $ git remote add origin git@github.com:[userOrOrganization]/SampleFoundation.git $ git push -u origin master
Step 4: Create CocoaPods Podspec for the In-House Library
If all went smoothly and you now have a Git source controlled SampleFoundation library project ready for development. You are now ready to create a CocoaPod podspec for the project and continue developing your CocoaPod-enabled library project.
As an aside, if your in-house library is dependent on any third party code you must take steps to ensure the Header Search Paths in your XCode project resolve all external header files to successfully compile.
Ok let’s get back to the walkthrough. The next step in this process is to create a podspec for your library. From $POD_LOCAL_HOME/SampleFoundation run the following command to generate an initial podspec:
$ pod spec create SampleFoundation [GitHub Repo URL]
Preparing Podspec for Local Development
Although the command above provides a good starting point, in order to take full advantage of developing and releasing in-house libraries, like we do at OPI, you need to modify the podspec to fit your needs.
For illustrative purposes, an example of the SampleFoundation.podspec is shown in the code snippet below. Pay attention to the 0.0.1.LOCAL value assigned to s.version and :tag elements. At OPI, we use the “LOCAL” suffix as a convention to indicate that this library version is in local development and does not yet exist in the Specs Repo (setup in step 1).
The “pod-dev release” custom command keys on this suffix when it is time to release the version to the Private Specs Repo – more on this later.
Pod::Spec.new do |s| s.name = "SampleFoundation" s.version = "0.0.1.LOCAL" s.summary = "Just Testing" s.description = < 'Proprietary', :text => < "company@address.com" } # ――― Platform Specifics s.platform = :ios # ――― Source Location s.source = { :git => "https://github.com/tlomenda/SampleFoundation.git", :tag => "0.0.1.LOCAL" } # ――― Source Code s.source_files = "SampleFoundation/**/*.{h,m,c}" # ――― Resources # s.resources = "Resources/*.png" # ――― Project Linking # s.framework = "SomeFramework" # s.frameworks = "SomeFramework", "AnotherFramework" # s.library = "iconv" # s.libraries = "iconv", "xml2" # ――― Project Settings s.requires_arc = true end
Using “pod-dev version” command
To verify the version you are working on, from the command line within $POD_LOCAL_HOME/SampleFoundation:
$ pod-dev version 0.0.1.LOCAL
Happy Developing
Whew, you should now have a source controlled, CocoaPod enabled, XCode library project to develop all the wonderful shareable code used across your iOS apps. If got this far, congratulations.
Step 5: App Development: Working with In-House CocoaPods
All this setup pays off as you now have an established, repeatable process for setting up in-house libraries for your iOS app development. Adding more private shared libraries as you need them becomes straightforward. One of the advantages with setting things up like we have in the previous steps is the flexibility it gives us in developing the library code.
Typically, there are two ways shared library code is developed. One, the shared library code is developed in-tandem with the iOS App that uses the library. Two, the shared library code is developed independently from iOS App and the app only uses released versions of the library. With Cocoapods and the pod-dev script, we can easily support both approaches.
Let’s see how by returning to our SampleFoundation library we have been using in this walkthrough. Imagine you are developing an iOS app that is already CocoaPods enabled – i.e. you have a Podfile that defines library dependencies required for your app.
In the Podfile you are going to bring in the SampleFoundation library as a dependency. Taking it up a notch, by adding SampleFoundation dependency as shown below, the dependency can be installed for in-tandem development or released version only.
platform :ios, '7.0' #Third Party Pods pod '', '' #--BEGIN LOCAL In-House Dependencies podLocal = ENV['POD_LOCAL_HOME'] pod 'SharedFoundation', :path => podLocal + '/SampleFoundation' #--END LOCAL In-House Dependencies #--BEGIN RELEASED In-House Dependencies #pod 'SampleFoundation', '0.0.1' #--END RELEASED In-House Dependencies
Notice the BEGIN LOCAL and BEGIN RELEASED sections. This is how we are able to switch between the two approaches for developing library code. This is demonstrated further in the following paragraphs.
Developing Library In-Tandem with App
Staying with our SampleFoundation example, developing the library in-tandem with the iOS app means we are going to reference the local, Git managed codebase for the library. From our setup we know that SampleFoundation is located in $POD_LOCAL_HOME/SampleFoundation.
CocoaPods allows us to reference the local version of the library by pointing to it via the “:path” keyword. When the app installs an in-house library with “:path”, the XCode workspace for the iOS app will point to the local files for SampleFoundation. This allows us to make changes to SampleFoundation code in-tandem with the code specific to your app.
The pod-dev script has commands to easily switch from local to released given a Podfile with LOCAL and RELEASED sections clearly marked as shown above. To enable “tandem mode”, from your iOS app project directory enter the following commands:
$ pod-dev clean $ pod-dev local-refs $ pod install $ open [APPNAME].xcworkspace
The command sequence above will remove any installed CocoaPods, enable the LOCAL section in the Podfile and disable RELEASED (comments out the section block), and installs SampleFoundation using the :path notation.
When you open your app’s XCode workspace, you will see in the Pods project reference a group called “Development Pods/SampleFoundation”. This indicates SampleFoundation is referenced as the local development version of the library code.
This allows you to develop the library code at the same time as the iOS App code. At OPI, we have found that this has become the most common way to evolve the library code so that it remains relevant and reusable within the context of our iOS apps.
Using a Released Version of Library with App
There are times when it is necessary to enforce referencing only released versions of private shared library code. This is required when:
1) Working on multiple iOS apps where you want most of the apps to be insulated from local development changes that are taking place within the shared library.
2) Tagging a final release version of the iOS App codebase. Before tagging the release it is recommended that you switch to released versions of all shared libraries. This ensures the App final release can be re-built in a consistent state.
To switch to “released mode” you would do the following on the command line within your app project directory:
$ pod-dev clean $ pod-dev released-refs $ pod install $ open [APPNAME].xcworkspace
The command sequence above will remove any installed CocoaPods, enable the RELEASED section in the Podfile and disable LOCAL (comments out the section block), and installs SampleFoundation version tag pulled remotely from GitHub.
Step 6: Releasing an In-House CocoaPod
Up to this point we have focused on setting up your computer to develop and manage private in-house shared library code for your iOS apps. Hopefully, along the way you have gained insight on how to work with private shared libraries and come up with ways to leverage this process.
There is going to come a time during your development cycle where you will be ready to release a version of your in-house library to your private CocoaPods Spec Repo. Again, the pod-dev script helps simplify things for you – assuming things are setup as described in the previous steps. By executing “pod-dev release”, this command will tag the version in the library’s GitHub repository, validate the podspec, install the podspec in your CocoaPods Spec Repo and prepare the next local development version for you.
The pod-dev script works off the .LOCAL version suffix we introduced in step 4. Recall when developing SampleFoundation the .LOCAL suffix indicates a local development work in progress. Still using SampleFoundation as an example, when you are ready to release a version of this library you would run the following commands:
$ cd $POD_LOCAL_HOME/SampleFoundation $ pod-dev version -- verify that the development version 0.0.1.LOCAL $ pod-dev release Are you ready to release version '0.0.1.LOCAL' as '0.0.1' (y or n)? ... Enter next (new) version for this Pod: 0.0.2 New Version will be '0.0.2.LOCAL'... Release DONE!
Well that seems easy enough, but a lot happened. If you look at the pod-dev script the release command executes the following sequence to commit to GitHub, validate and push the podspec to the in-house CocoaPods Spec Repo
$ git commit -a -m"Final Release for $VERSION_RELEASE" $ git tag $VERSION_RELEASE $ git push --tags $ pod push $INHOUSE_SPEC_NAME --allow-warnings
Verifying All is Well
On GitHub look at the shared library’s repository and verify you see 0.0.1 in Released/Tags.
On GitHub look at your In-House CocoaPods Spec Repo and verify you see:
REPO --SampleFoundation --0.0.1 --SampleFoundation.podspec
What if Something Goes Wrong
The most common reason something goes wrong is during the validation of the podspec during the ‘pod push’ command. If validation of the podspec fails the version of the podspec is not pushed to your in-house CocoaPods Spec Repo.
The best thing to do at this point is rollback your release which will remove the tag from the shared library’s GitHub repository. This allows you to fix the issue and retry the release. To rollback a release from $POD_LOCAL_HOME/SampleFoundation:
$ pod-dev rollback Enter version to rollback for 'SampleFoundation' 0.0.1 Rollback Release '0.0.1' for Pod 'SampleFoundation' Deleted tag '0.0.1'
After this, modify the SampleFoundation.podspec back to 0.0.1.LOCAL, fix anything else in the podspec that did not pass validation and try the release again.
pod-dev Script Revisited
As mentioned, the pod-dev custom script is attached for you to download and use as you see fit. The following commands are available in this script as a starting point:
- pod-dev clearcache –> Wipes out the local CocoaPods GIT Cache
- pod-dev clean –> Removes Pods and Podsfile.lock from XCode project directory
- pod-dev init-inhouse –> Initializes the inhouse repository
- pod-dev get-localpod [gitCloneUrl] [PodName] –> Clones to POD_LOCAL_HOME
- pod-dev remove-localpod <podName>
- pod-dev local-refs –> Switch references to Local PODS
- pod-dev released-refs –> Switch references to Released PODS
- pod-dev version –> Prints out current version in podspec from XCode library project directory
- pod-dev release –> From XCode library project directory releases version to in-house Specs repo
- pod-dev rollback –> FromXCode library project directory rolls back tag in Git Repo for Pod and Specs
In Closing
This walkthrough provided a glimpse into how we use CocoaPods and the concept of a Private In-House Spec Repository to develop and manage private shared libraries for our iOS Apps. I hope it proved helpful if you were looking for a way to do something similar with In-House iOS libraries. If you have any questions regarding the approach outlined in this blog feel free to email me at torey.lomenda@objectpartners.com.
Thanks for reading this blog post.
Have you run into any problems when referencing one spec from another? I followed your steps, verified that cocoapods can see the dependent spec, reference said spec with
`s.dependency ‘PrivateSpec’ ‘~> 0.1.0’`
but when I run lint on this spec [PrivateClientSpec], I get the error
Unable to find a specification for [PrivateSpec] depended upon by [PrivateClientSpec]
Thanks, and for your post.
@Chris: Same happens here. Solution seems to be to setup a private spec repo. That, however, would mean that all dependencies have to be published already, right? So the question is: How to reference a development pod from another development pod in the podspecs?
@Chris: Same happens here. Solution seems to be to setup a private spec repo. That, however, would mean that all dependencies have to be published already, right? So the question is: How to reference a development pod from another development pod in the podspecs?
Would it be possible to repost the link to the ‘pod-dev’ script. The link in the article does not appear to be available for download anymore.
All should be well with the ‘pod-dev’ script link. Thanks for bringing it to my attention.
Your script definitely helps ease the process of maintaining private in-house libraries, however I had to make one change to the script to make it compatible with Cocoapods v0.37.2.
Under the ealeasePod() function, the following line
“pod push $INHOUSE_SPEC_NAME –allow-warnings”
needs changed to
“pod repo push $INHOUSE_SPEC_NAME –allow-warnings”
They may have the process with a newer version than the one referenced in the article.
Thanks!
Thanks for the comment. Yes you are correct. I have updated my version of the script, but I have not updated the script referenced in the article.
Since version 4.5 Artifactory supports proxing and hosting CocoaPods repositories – https://www.jfrog.com/confluence/display/RTF/Artifactory+4.5