Getting Started with Carthage

Updated Oct 17, 2023#ios#swift#objc#xcode

Carthage is a dependency manager for Swift and Objective-C projects that aims to provide a simpler tool that’s more flexible and easier to understand and maintain. Here’s how Carthage achieves this:

  • It doesn’t change your Xcode project or force you to use a workspace.
  • You don’t need Podspecs or a centralized repository where library authors submit their pods. If you can build your project as a framework, you can use it with Carthage, which leverages existing information straight from Git and Xcode.
  • Carthage doesn’t do anything magically; you’re always in control. You add dependencies to your Xcode project and Carthage fetches and builds them.

Carthage builds your dependencies and provides you with binary frameworks, but you retain full control over your project structure and setup. Carthage does not automatically modify your project files or your build settings.

Note that Carthage only supports dynamic frameworks, which are only available on iOS 8 or later (or any version of OS X). Using XCFrameworks as of version 0.37.0 (January 2021), and require XCFrameworks when building on an Apple Silicon Mac.

Carthage will check to make sure that downloaded Swift (and mixed Objective-C/Swift) frameworks were built with the same version of Swift that is in use locally. If there is a version mismatch, Carthage will proceed to build the framework from source. If the framework cannot be built from source, Carthage will fail.

Quick Start

  1. Get Carthage by running brew install carthage.
  2. Create a Cartfile in the same directory where your .xcodeproj or .xcworkspace is
  3. List the desired dependencies in the Cartfile, for example:
github "Alamofire/Alamofire" ~> 5.5
  1. Run carthage update --use-xcframeworks
  2. A Cartfile.resolved file and a Carthage directory will appear in the same directory where your .xcodeproj or .xcworkspace is
  3. Drag the built .xcframework bundles from Carthage/Build into the “Frameworks and Libraries” section of your application’s Xcode project.
  4. If you are using Carthage for an application, select “Embed & Sign”, otherwise “Do Not Embed”.

Make sure to commit your Cartfile.resolved, because anyone else using the project will need that file to build the same framework versions.

After you’ve finished the above steps and pushed your changes, other users of the project only need to fetch the repository and run carthage bootstrap to get started with the frameworks you’ve added.

Target iOS

  1. Create a Cartfile that lists the frameworks you’d like to use in your project.
  2. Run carthage update. This will fetch dependencies into a Carthage/Checkouts folder, then build each one or download a pre-compiled framework.
  3. Open your application targets’ General settings tab. For Xcode 11.0 and higher, in the “Frameworks, Libraries, and Embedded Content” section, drag and drop each framework you want to use from the Carthage/Build folder on disk. Then, in the “Embed” section, select “Do Not Embed” from the pulldown menu for each item added. For Xcode 10.x and lower, in the “Linked Frameworks and Libraries” section, drag and drop each framework you want to use from the Carthage/Build folder on disk.
  4. On your application targets’ Build Phases settings tab, click the + icon and choose New Run Script Phase. Create a Run Script in which you specify your shell (ex: /bin/sh), add the following contents to the script area below the shell:
/usr/local/bin/carthage copy-frameworks
  1. Create a file named input.xcfilelist and a file named output.xcfilelist
  2. Add the paths to the frameworks you want to use to your input.xcfilelist. For example:
$(SRCROOT)/Carthage/Build/iOS/Result.framework
$(SRCROOT)/Carthage/Build/iOS/ReactiveSwift.framework
$(SRCROOT)/Carthage/Build/iOS/ReactiveCocoa.framework
  1. Add the paths to the copied frameworks to the output.xcfilelist. For example:
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Result.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ReactiveSwift.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ReactiveCocoa.framework

With output files specified alongside the input files, Xcode only needs to run the script when the input files have changed or the output files are missing. This means dirty builds will be faster when you haven’t rebuilt frameworks with Carthage.

  1. Add the input.xcfilelist to the “Input File Lists” section of the Carthage run script phase
  2. Add the output.xcfilelist to the “Output File Lists” section of the Carthage run script phase

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.

Cartfile

The three supported origins right now are GitHub repositories, Git repositories, and binary-only frameworks served over https.

Carthage supports several kinds of version requirements:

  • >= 1.0 for “at least version 1.0”
  • ~> 1.0 for “compatible with version 1.0”
  • == 1.0 for “exactly version 1.0”
  • “some-branch-or-tag-or-commit” for a specific Git object (anything allowed by git rev-parse). Note: This form of requirement is not supported for binary origins.

If no version requirement is given, any version of the dependency is allowed.

# Require version 2.3.1 or later
github "ReactiveCocoa/ReactiveCocoa" >= 2.3.1

# Require version 1.x
github "Mantle/Mantle" ~> 1.0    # (1.0 or later, but less than 2.0)

# Require exactly version 0.4.1
github "jspahrsummers/libextobjc" == 0.4.1

# Use the latest version
github "jspahrsummers/xcconfigs"

# Use the branch
github "jspahrsummers/xcconfigs" "branch"

# Use a project from GitHub Enterprise
github "https://enterprise.local/ghe/desktop/git-error-translations"

# Use a project from any arbitrary server, on the "development" branch
git "https://enterprise.local/desktop/git-error-translations2.git" "development"

# Use a local project
git "file:///directory/to/project" "branch"

# A binary only framework
binary "https://my.domain.com/release/MyFramework.json" ~> 2.3

# A binary only framework via file: url
binary "file:///some/local/path/MyFramework.json" ~> 2.3

# A binary only framework via local relative path from Current Working Directory to binary project specification
binary "relative/path/MyFramework.json" ~> 2.3

# A binary only framework via absolute path to binary project specification
binary "/absolute/path/MyFramework.json" ~> 2.3

Artifacts

When you run carthage update, Carthage creates a couple of files and directories for you:

  • Cartfile.resolved: This file serves as a companion to the Cartfile. It defines exactly which versions of your dependencies Carthage selected for installation. It’s strongly recommended to commit this file to your version control repository. Its presence ensures that other developers can get started quickly by using the exact same dependency versions.

  • Carthage/Build: This contains the built framework for each dependency. You can integrate these into your project, and you’ll do so shortly. Carthage either builds each framework from source or downloads it from the project’s Releases page on GitHub.

  • Carthage/Checkouts: This is where Carthage checks out the source code for each dependency that’s ready to build into frameworks. Carthage maintains its own internal cache of dependency repositories, so it doesn’t have to clone the same source multiple times for different projects.

Whether you commit the Build and Checkouts directories to your version control repository is up to you. It’s not required, but doing so means that anybody who clones your repository will have the binaries and source for each dependency available.

Having this backup can be a useful insurance policy if GitHub is unavailable or a source repository is removed.

How does Carthage differ from CocoaPods?

CocoaPods is a centralized system that relies on a single repository called Specs that hosts all the metadata and source code of the supported libraries. Carthage is a decentralized system that does not have a central repository, but instead fetches the source code directly from GitHub or other sources.

CocoaPods is an automated system that downloads the libraries, creates a workspace, and modifies the project settings for you. Carthage is a configurable system that only builds the libraries and leaves the integration to you.

CocoaPods creates a new workspace that contains your project and a Pods project that contains all the dependencies. Carthage does not create a new workspace, but instead stores the built frameworks in a Carthage folder that you can link to your project.

CocoaPods supports both static and dynamic linking of frameworks, depending on the library type and the platform. Carthage only supports dynamic linking of frameworks, which means they are loaded at runtime and not embedded in the app bundle.

Some prefer CocoaPods for its ease of use, wide range of libraries, and automated integration. Others prefer Carthage for its simplicity, flexibility, and minimal interference with the project structure.

Can I use Carthage with Swift Package Manager?

Carthage and Swift Package Manager are both decentralized dependency managers, which means they fetch the source code directly from GitHub or other sources. This allows you to use both tools in the same project without conflicts or duplication.

However, Carthage only supports dynamic frameworks, while Swift Package Manager supports both static and dynamic libraries. This means that if you use Carthage to build a framework that depends on a Swift Package Manager library, you have to make sure that the library is also built as a dynamic framework, otherwise you will get linker errors.

To do this, you have to use the --use-xcframeworks flag when running carthage update or carthage build, which will create XCFrameworks instead of regular frameworks. XCFrameworks are a new format introduced in Xcode 11 that allow you to bundle multiple variants of a framework for different platforms and architectures.

Alternatively, you can use the --no-use-binaries flag to disable the use of prebuilt binaries from Carthage, and build all the dependencies from sourceÂč. This will ensure that the Swift Package Manager libraries are built with the same toolchain and settings as your project.

Another option is to use Carthage only for external dependencies that are not available as Swift packages, and use Swift Package Manager for internal dependencies or those that support both tools. This way, you can avoid the hassle of dealing with different formats and linking issues.

However, this option may not work well if you have transitive dependencies, i.e. dependencies that depend on other dependencies. For example, if you use Carthage to build Alamofire, which depends on AlamofireImage, which depends on SDWebImage, which is a Swift package, you will have to make sure that all the intermediate dependencies are also compatible with both tools.

In summary, using Carthage with Swift Package Manager is possible, but it requires some extra work and attention. You have to be careful about the type and format of the libraries you use, and how they interact with each other. You may also encounter some issues with versioning and compatibility between the tools.


Recommended Reading