Third Party Dependency Management with Carthage
Dependencies — What are they and how do I use them?
At the heart of software development is software reuse, or leveraging existing code to further the development of new and future code. It is with this mindset that teams are able to focus on the problem at hand and not waste time reinventing the wheel. The code reused can be a foundational building block or a just a handy library of common components, either way, not directly specific to the problem at hand.
These components are known as dependencies. Managing the integration of these components with a software project is called dependency management.
This process has an associated overhead related to it which is unavoidable, but necessary. When it comes to mobile software development the following are several options for dependency management, and an in depth guide for how we manage dependencies here at Compass.
Git submodules are the original dependency manager for git repositories. They offer a way to define other repositories used by your app but stored and managed by externally of your project repository.
Submodules is a very manual way of adding dependencies to your project. It will checkout that code from the repository, and that is it. This workflow is lacking for mobile applications as it is much easier to work with a compiled library or framework over raw source code.
Cocoapods is a dependency manager for iOS and Cocoa applications that abstracts away the details of dependency management. It downloads and integrates the dependencies into an Xcode workspace.
Let us do the integration for you via Xcode’s Workspaces
Cocoapods biggest feature, the automation of dependencies is perhaps its biggest flaw. At Compass we believe that you should never be handcuffed to your workflow, and tend to be weary of a magical solution. Dependency integration is not a trivial task, and it is best not to assume that it can be achieved in a one size fits all command, see Exhibit A.
Carthage is “A simple, decentralized dependency manager for Cocoa”
Carthage takes the simplicity of submodules and elevates it to work for mobile applications by not only checking out the dependency repositories, but also building the code into easily integrable frameworks. This leaves the framework integration work for the developer, removing the ambiguity and unwanted automation from the process.
At Compass, we strongly prefer Carthage over Cocoapods.
Swift Package Manager
Finally, we have the relatively newer Swift Package Manager.
The Swift Package Manager is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
The Swift Package Manager is developing a reputation as the next big thing in iOS Dependency Management. Unfortunately, due to its strict restriction to Swift-only and its opt-in nature, it has not yet achieved its goal of mainstream dominance. If and until that day comes, our team will be using Carthage for all our iOS dependency management, and strongly recommend you do the same.
The remainder of this article is going to demonstrate using Carthage as your dependency manager.
Let’s go through the setups of adding a dependency to your Xcode project.
1. Installing Carthage
2. Creating the Cartfile
3. Resolving Dependencies
4. Configuring Xcode
The easiest way to install Carthage is with Homebrew.
brew install carthage
More detailed and alternative installation instruction can be found here.
Creating the Cartfile
First things first, you will need to add a Cartfile to the root level of your repository. The Cartfile is where your app dependencies are declared and optionally constrained to a version. A more detailed explanation can be found here.
Adding Snail to your Cartfile looks like:
github "UrbanCompass/Snail" "0.0.19"
github is one of three supported origins defined here.
"UrbanCompass/Snail" refers to where that source can be found. You can think of this as a shortcut for the repo URL: https://github.com/UrbanCompass/snail
Additionally, you can specify a semantic version number to use, in this case, Snail will only use version 0.0.19. Additionally, Carthage supports other variation of the version specified here.
Omitting the version number is considered bad practice.
With your Cartfile setup, we can now use Carthage to checkout and build these dependencies into frameworks. This is as simple as:
carthage update --platform iOS
It is good practice to use the platform flag to limit the frameworks to the platforms on which you will be developing.
After this command completes, you will now see a new directory Carthage.
The Carthage directory will have two sub directories, Build and Checkout.
Checkouts is where Carthage checks out or clones the dependency from Github.
Build is where Carthage builds the dependencies into a framework per platform, meaning, that a framework will be built for every platform (osx, ios, tvos, watchos) that the dependency supports.
Building utilized platforms can add a significant amount of time to your carthage update command.
Since we built just the iOS platform, we should see the Snail.framework in the Carthage/Build/iOS directory
You may have noticed a new file called Cartfile.resolved was also created.
The Cartfile.resolved file is generated or updated by the
carthage update command. It is used by Carthage to refers specific versions, tags, or commits used to checkout and build the dependencies listed in the Cartfile.
Cartfile.resolvedfile is meant to be human-readable and diffable, you must not modify it. The format of the file is very strict, and the order in which dependencies are listed is important for the build process. — Carthage
Now that your dependencies are specified in your Cartfile and downloaded to your repository, we are ready to configure Xcode to use these frameworks.
First, we need to give these Frameworks a place to live within our Xcode project. Right click on the project file and create a Frameworks group if one does not already exist.
We can now add frameworks to Xcode by right clicking and selecting Add Files to <Your_Project>.
Navigating to Carthage/Build/iOS we can now add Snail.framework.
The Snail framework is now added to your project, but you’re not done yet! First we should check that target membership of the framework matches the correct target.
If you are building a macOS app, congratulations, you are now done. Otherwise, continue below.
Extra Steps for iOS, tvOS, watchOS
The final step required to configure a Carthage dependencies is to ensure the frameworks are copied with a Run Script. The following instructions are also described here.
To add a Run Script to your target:
- Open the Xcode Project file
- Select the Target
- Click the
+icon add select New Run Script Phase
This Run Script will be specific to copying Carthage Frameworks, so its name should reflect that. Double click on the
Run Script label and change the name to
Copy Carthage Frameworks
Your run script should now look something like the image below
Replace the placeholder text with the Carthage script:
This script works around an App Store submission bug triggered by universal binaries and ensures that necessary bitcode-related files and dSYMs are copied when archiving.
With the debug information copied into the built products directory, Xcode will be able to symbolicate the stack trace whenever you stop at a breakpoint. This will also enable you to step through third-party code in the debugger.
When archiving your application for submission to the App Store or TestFlight, Xcode will also copy these files into the dSYMs subdirectory of your application’s
.xcarchivebundle. — Carthage
Finally, any Frameworks added by Carthage need to be added to this script as Input Files. Specify each Frameworks path like so
When you are finished your framework should look something like:
Congratulations! You are all wired up and ready to start using your new Frameworks.
Adding Frameworks to a Test Target
When adding frameworks to a test target, the process is exactly the same as above, with one difference. Instead of defining dependencies in your Cartfile, dependencies should be defined in a separate Cartfile.private.
"Quick/Quick" "v1.1.0"github "Quick/Nimble" "v7.0.1"
Following the same steps above, but using your test target, the Runscript Phase should look something like this:
The following project can be used as a guide:
Using dependencies in mobile development is mostly unavoidable. However the method in which we use to bring these dependencies in can make a big difference to your workflow. Although we remain hopeful for the Swift Package Manager, Carthage provides the cleanest way to manage dependencies and wire them up to your Xcode project.