The Only Lars

Byte-sized adventures in software engineering

Cocoapods: Creating a Pod Spec

Update 4-15-2013: Updated for changes that came with v0.18.x and v0.17 since this page is now linked to on cocoapods.org.

Note: Cocoapods is all very new and in rapid development (currently v0.18.1 as of this update), if you are in the know and any of the below is wrong or incorrect, please let me know and I’ll change it.

If you’re reading this there is a good chance you have at least heard of Cocoapods. For the uninitiated, Cocoapods is a dependency manager for Mac and iOS projects built on Ruby and based on the Ruby community’s “rubygem bundler”. Each “pod” has a “spec” that is utilized in order to know how to integrate a given component with your project and resolve any dependencies your project may have with other libraries. I’ll be referring to these from here on out as either a “podspec” or more simply - a “spec”.

Since I’ve had some trouble easily writing my podspecs in the past (and have run into poorly-tested specs in the specs repository), I have put together a collection of knowledge on the things I have learned while building and testing my podspecs.

I’m assuming that you already have cocoapods installed, and that you are looking to build and test a new .podspec file for a component.

Let’s get started!

Table of Contents

What is a Spec?

The basis behind how cocoapods is able to function is on a project’s “podspec”. Podspecs are created by maintainers of a project (or sometimes just other developers who want to use a component as a pod) and submitted to a public repository of specs in an organized git repository on github.

Specs identify everything about a library or component that needs to be performed before you can properly use it in your project. This includes everything from supported platform, ARC-readiness and required frameworks to other C flags that might need to be switched on.

Basic Requirements

At a minimum, your spec needs to have the following attributes defined in order to be valid. If you think about it, if any one of these were missing, the spec itself wouldn’t really make sense:

  • Component name
  • Semantic version number
  • Platform
  • Summary
  • Author info dictionary
  • Component homepage
  • License
  • Source location
  • Source files list
  • Any component dependencies*
  • Required apple frameworks*

Semantic Versioning

Cocoapods highly suggests using (as in - don’t not do it) semantic versioning to version your cocoapods. Without semantic versioning, it becomes much more difficult if not impossible to resolve some cross-dependencies between similar pod dependencies in your project, if any exist. All that was very complicated to say - use semantic versioning (e.g. “v1.1.0”). Tag your code in your repository with a tag corresponding to the version number of your component (for v3.0.0 of your component, tag your code 3.0.0).

Dependencies

The dependency line is technically optional if your component does not rely on any external pod.

Frameworks

The frameworks line is also technically optional if your pod only uses the standard apple frameworks.

Construction

A Podspec is constructed by building a new Spec object using a ruby “block”. My ruby-fu is still pretty green, so I won’t try and explain the ruby-ness of a podspec. To create a podspec using a block, we’re going to declare the following in a new file named <component_name>.podspec. Please don’t literally put the angle brackets in your file name:

AwesomeComponent.podspec
1
2
3
Pod::Spec.new do |s|
  # pod customization goes in here
end

This creates a new Spec object that is included in the Pod library and inserts it into the local variable s for us to customize before it is returned. I’ve gone ahead and filled in some basic information for a spec I wrote for LARSAdController to demonstrate:

LARSAdController.podspec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Pod::Spec.new do |s|
  s.name         = 'LARSAdController'
  s.version      = '3.0.0'
  s.summary      = 'Lightweight ad mediation for iOS'
  s.author = {
    'Lars Anderson' => 'youremail@here.com'
  }
  s.source = {
    :git => 'https://github.com/larsacus/LARSAdController.git',
    :tag => '3.0.0'
  }
  s.source_files = 'Source/*.{h,m}'
  s.dependency     'AdMob'
end

This will include all source files with file extensions .h & .m in the Source/ folder in the github repository https://github.com/larsacus/LARSAdController.git in the source tag 3.0.0 while also including the pod for the dependency of AdMob.

Hashes

If you’re unfamiliar with ruby, you’ll notice some interesting {} structures being assigned to variables like source and author. These structures are called “hashes” and they are exactly like your standard dictionary in objective-c:

key => value

For the source hash, the colon (:) before the key name is important. This defines things like :git and :tag as ruby “symbols” that are defined within cocoapods.

Tags and Unversioned Repositories

There are other methods of specifying the location of source code instead of tags, but you should really be using tags to mark your code commit for your spec. If you are not the owner of the repository you are creating a spec for, file an issue with the repository to request that a tag be made. If this doesn’t work, then you should specify the version of your podspec to begin at 0.0.1 to indicate that this pod’s spec is unmanaged. Should the owner finally get a tag, then you can update the podspec with the correct version with little fear of conflicting with your previously unmanaged podspec’s version.

Update 4-15-2013: You now cannot submit a pod to the podspec repository referencing a specific commit - you must specify a tag.

Version Simplification

Since this is just a ruby file, you can use all of the ruby tricks on any string in this file. To simplify our podspec for revving our version later, you can tell our source hash to use the podspec’s version as the repository’s tag to use using the to_s helper:

1
2
3
4
5
6
7
8
Pod::Spec.new do |s|
  ..
  s.source = {
    :git => 'https://github.com/larsacus/LARSAdController.git',
    :tag => s.version.to_s
  }
  ...
end

Completing Your Spec

This is where writing a podspec gets a little complicated. The general example specs from the wiki are just fine, but when you start customizing your spec to the specific needs of your component or library, you can easily find yourself running to google with few available resources.

Help

For the majority of the issues you will run into when creating your podspec, the cocoapods wiki will have a page that will answer your question. Almost every issue I was having, I eventually found the answer to in the wiki. It could be a little better organized, but I really can’t complain too much since I haven’t submitted a pull request to fix it myself.

Since this original writing, a new sister project of CocoaPods cocoadocs.org has been released that has a wealth of well-organized information available as well as documentation on every appledoc-documented Pod.

If you simply cannot find an answer anywhere, submit an issue on the Cocoapods/Specs repository (not the Cocoapods/Cocoapods repo) and someone is usually knowledgable enough to figure out what you’re doing wrong or help you file a bug to fix the issue.

Learn By Example

If you have a component or library that is built a specific way and you’re having an issue, try and find a similar component that is built like yours and try that Pod’s spec configuration. For instance, if your component is a static library, find a spec (like Testflight) that is built utilizing a static library.

Testing

I found that this step was by far the most difficult to perform. I was not about to simply deploy a podspec that I had not tested, so this left me not even wanting to build a podspec for my component. I’m going to spare you the workflow iterations I tried before arriving at what I believe to be the “correct” way to test your podspec.

I also understand that I am fully able to submit pull requests to modify the cocoapods wiki to make it more clear. This is something that I simply have not done, yet. This is way easier for now.

Spec Lint

The first step to testing your spec is to use cocoapods’ built-in “lint” tool to test for syntax compliance and minimum requirements. In your terminal prompt, run the lint tool on your podspec using:

1
pod spec lint "<spec location>/SpecName.podspec"

If anything is syntactically wrong with your spec, it will show up here after running lint. Refer to the cocoapods wiki if any errors or warnings come up.

As of CocoaPods v0.17, you can no longer submit pod specs to the spec repo when lint reports warnings.

In-Project (Functional) Testing

It’s very important to actually test your pod in a fresh project without any other configuration. Don’t be the guy that creates a spec, submits it, then you get a bunch of complaints because your spec doesn’t actually install properly when it’s installed. It’s also annoying to find a spec you can use, put it as a dependency on your project, only to find out it’s not configured properly. It looks bad on the cocoapods community when this happens.

Please functionally test your specs before submitting them:

  1. Create a new empty xcode project
  2. Create your Podfile
  3. Add your pod as a project dependency
  4. Specify local file path for new podspec

This is where I had a huge issue testing my podspec. The trick I learned is to specify a local file path for your Podfile to look for your new podspec:

1
pod 'LARSAdController', '3.0', :path => '~/Specs/LARSAdController'

Now you can install and test your pod:

  1. Install pod - pod install (initial install) - pod update (subsequent installs)
  2. Test to ensure your component works
  3. Rinse, repeat until component installs correctly

Subspecs

A cool feature of cocoapods is its ability for you to specify sub-components of your component to include. This will enable other developers to be able to a-la-carte choose which sub-components of your library to include in their project. This reduces the bloat, especially when only some parts of your project have dependencies on other large components.

Subspecs are totally optional in your podspec.

Defining a Subspec

To define a subspec in your spec file, define a subspec variable (?) on your spec variable - much like we did in creating the spec itself:

1
2
3
4
5
6
7
8
9
10
Pod::Spec.new do |s|
  ..
  s.subspec 'iAds' do |a|
    a.source_files = 'Source/iAds/*.{h,m}'
    a.dependency 'LARSAdController/Core'
    a.frameworks = 'QuartzCore'
    a.weak_frameworks = 'AdSupport'
  end
  ...
end

Give your subspec a descriptive name, specify the source files to use, etc. In the above example, I have named this subspec ‘iAds’ since this will be including iAd functionality in with my pod. You’ll continue to customize the subspec in the same way that you customized your parent spec object.

As of CocoaPods v0.17, subspecs will no longer inherit source files from the parent spec. In order to retain the same functionality, simply add a new “base” subspec called “Core” that includes all of the base files for your implementation. If your “Core” files are the only files that you would like installed when users do not specify a subspec, you will need to add a default_subspec property on your podspec and specify your “Core” subspec:

1
2
3
4
5
6
7
8
Pod::Spec.new do |s|
  s.default_subspec = 'Core'
  ..
  s.subspec 'iAds' do |a|
    # define subspec here
  end
  ...
end

The above code block will only install the Core subspec when your pod is called out as a dependency when the user does not specify a subspec in their podfile. If you did not include the default_subspec property, then all subspecs would be installed by default.

There are some properties that are not allowed to be defined on a subspec and can only be defined on the parent spec. These “Top level attributes” are outlined on the podspec page on the Cocoapods wiki.

Continue to create additional subspecs for however many sub-specifications that you need to create using the template:

1
2
3
s.subspec 'SubspecName' do |a|
  # Customize
end

Final Product

In the end, you should have a podspec that looks a lot like mine does for LARSAdController.

Depending on a Subspec

To use the subspec in a Podfile, define the dependency like normal, but suffix it with a / and the subspec name:

1
pod 'LARSAdController/iAds', '3.0'

Releasing

Congratulations! You’ve now completed the hardest parts of constructing your podspec. This next part is easy as long as your podspec lints correctly and installs - which you’ve already tested, right? In order to contribute to the Cocoapods/Specs repository, you will need to clone the repo, add your spec and submit a pull request to have your spec added.

Forking/Cloning

To add your spec to the list of podspecs, you will need to fork the Cocoapods/Specs repo using your own github account. If you have cloned a fork of the repository and haven’t committed to it even in the last 12 hours, you will want to update your repository by doing a git pull on your repo specifying the original remote location to pull the latest podspecs from. This repository gets updated quite frequently, so make sure you are up to date.

You will then need to clone the repository on your local machine and add your spec using the established file structure:

ProjectName/Version/ProjectName.podspec

So for the last release of LARSAdController, I put my latest podspec in LARSAdController/3.0.0/LARSAdController.podspec - one podspec per version folder.

Once this is added, you’ll want to add the files to git, commit, and push them back up to your own fork of the Specs repo.

Pull Request

Once the podspec is committed and pushed to your fork on github, you can submit a new pull request to merge the changes you made in your fork to the master repository that cocoapods is pulling their spec library from.

The friendly, hard-working folks who actively work to get your components merged in will then make sure that everything is linted and working correctly (syntactically, but not functionally) before allowing your changes to be merged into the master specs branch.

If this succeeds, then your podspec will now be available for everyone to use in their own projects! Pat yourself on the back - it wasn’t all that bad, was it?