How to Carthage Efficiently

Daniela P. Riesgo
Wolox
Published in
8 min readOct 19, 2018

--

Handling iOS dependencies with style

First of all: What is Carthage?
If you reached this post, most probably you already know, but if you don’t, I’m glad you got to it! Carthage is a dependency manager written in Swift. It 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.

If you want to learn more about why to use Carthage over other alternatives and how to start using it, I recommend you read this Ray Wenderlich post which can’t go wrong.

Basic installation

3 simple steps:

1. Install Homebrew

2. Run

brew update

3. Run

brew install carthage

(Even if this seems too much, at Wolox we have our script to install several useful things among which we have Carthage.)

Also, we recommend adding the autocomplete feature by following this guide.

Sometimes Carthage will give you a message like "Please update to the latest Carthage version …"
In that case, you need to update it by running:

brew update
brew upgrade carthage

If that raises an error, you can run:

brew update
brew reinstall carthage

Usage in a project

Adding the first dependency to your project

First dependency ever? Just follow these steps.

1. Create a Cartfile and/or Cartfile.private file.

A Cartfile is like the Gemfile, Dockerfile, Podfile and all those others. It’s where you specify which dependencies you want and their compatible versions.

A Cartfile.private is like a Cartfile but for private dependencies that don’t need to be there for being able to run the app (for example, dependencies for testing).

Carthage uses semantic versioning, check this out on how to use it.

2. Run the following command [change platform for the OS you are using]

carthage update --no-use-binaries --platform iOS

After this, dependencies will be downloaded and stored in a Carthage folder. Also a Cartfile.resolved file will be generated, this file stores the exact version downloaded for each dependency.

The update command will search for the latest version that fulfills your version restrictions and download it.

3. Add the downloaded Frameworks to the Link Binary with Libraries Build Phase

In the list, you should add all .framework packages that appear in the Carthage/Builds/<OS> folder. In it, you will find your dependencies stated in the Cartfile and their own dependencies as well.

4. Have a "Carthage" Run Script Build Phase in every target with dependencies [Step only needed if your project is not a framework]

In the script, you should write: `/usr/local/bin/carthage copy-frameworks`.

In the Input Files list, you should add all .framework packages that appear in the Carthage/Builds/<OS> folder and are relevant to the target in question by using: $(SRCROOT)/Carthage/Build/<OS>/MyDependency.framework

In the Output Files list, you should add all .framework packages that appear in the Carthage/Builds/<OS> folder and are relevant to the target in question by using: $(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MyDependency.framework

This will take care of installing all frameworks on devices when installing your app.

5. Add a "Carthage Outdated?" Run Script Build Phase in every target with dependencies

In the script, write:

/usr/local/bin/carthage outdated --xcode-warnings

What do I have to commit? The Cartfile, Cartfile.private, Cartfile.resolved and changes to the .xcodeproj file. Don’t commit the Carthage folder, add it to the .gitignore file.

Bootstrapping a project

A dependency is added by one member of the team once, and then everybody uses it.

As we don’t commit the Carthage folder, because it would take up a lot of space and we can reproduce it locally, the information we have on it is the Cartfile (and/or Cartfile.private) and the Cartfile.resolved someone else created, which specify the versions we need on everything.

To bootstrap the dependencies using the same versions that worked for my teammate, we want to run:

carthage bootstrap --platform <OS> --no-use-binaries --cache-builds

or even better:

carthage bootstrap <new dependency> --platform <OS> --no-use-binaries --cache-builds

(so you only build the new dependency and leave the other ones as they are, which we know are already correct).

The bootstrap command will take the version last used in your project, by taking it from the Cartfile.resolved. In that way, you make sure that if it worked for your teammates, it will work for you.

Need a new dependency?

Have one of your teammates add the new dependency and have the other ones bootstrap it as in the previous section.

For adding the new dependency, the steps are almost the same as when you added the first dependency:

  1. Add it to the Cartfile or Cartfile.private
  2. Run
carthage update <new dependency’s name> --no-use-binaries --platform iOS
  1. Add it to the Link Binary with Libraries Build Phase of the relevant targets
  2. Add it to the Carthage Run Script Build Phase of the relevant targets (both in Input Files and Output Files sections)
  3. Commit once more the relevant changes

Updating the project and its dependencies

Sometimes, we have a working project but we want to update it so we have all the state-of-the-art features. This includes updating our dependencies.

In this case, you may want to change the versions you specified in the Cartfile (and/or Cartfile.private), but if you just want the latest version compatible to yours so your project won’t fail, you don’t need that.

You just need to run:

carthage update --platform <OS> --no-use-binaries --cache-builds

(You can run the same for a specific dependency if you like, and avoid updating all of your dependencies.)

Removing a dependency

This is the most tricky thing in Carthage since there isn’t a command for it. You need to take into account that you need to perform the next steps for the dependency and the dependency’s dependencies, and so on.

  1. Remove mentions on it from Cartfile, Cartfile.private and Cartfile.resolved
  2. Remove it from Link Binary with Libraries Build Phase
  3. Remove it from the Input Files and Output Files lists of the Carthage Run Script Build Phase
  4. Commit changes

If after these steps you or your teammates run some Carthage commands and the old dependency appears, you should delete all appearances of it and its dependencies from the Carthage folder:

  • the .framework and the .dSYM from the Carthage/Builds folder
  • the Dependency folder from the Carthage/Checkouts folder

Why?

Why do things like this? How do things work? If you wanna become a Carthage expert, of course, the best place to go learn is Carthage itself. Especially if you are building for OS X because there are some small differences from what I've explained.

In the meantime, here you have some small explanations of the things we named in this post.

Why use those commands?

Let’s recap.

  • update: searches for the latest version that fulfills your version restrictions and downloads it
  • bootstrap: searches for the version last used in your project in the Cartfile.resolved file and downloads it

Both commands can optionally receive one or more dependencies' names so Carthage only works on that dependencies.

What will you see each time you run these commands? The commands have phases:

  1. Fetching: [only present in update] Checks the versions available to decide which one to use
  2. Checking out && Cloning: Gets the commit, branch or tag the version relates to and clones the repository at that commit if it wasn’t done before in your computer.
  3. Building: Builds the .framework files. Remember carthage uses your current XCode version to build so be aware of the team's XCode versions.

Carthage has more commands than just this, go check them out.

Why use those flags?

Let’s make another recap.

  • --platform <OS>: Carthage supports, OS X, iOS, tvOS, and watchOS. If you don’t specify which one you want, it will build for all platforms. So don’t forget it!
  • --no-use-binaries: This flag indicates Carthage to not use the pre-built frameworks released in GitHub, but build from scratch. Why would we want that if it takes more time? Because pre-built frameworks do not always work well for your project (it is corrupted or it was built with another Swift or XCode version) and they don’t support step-by-step debugging. So again, don’t forget it!
  • --cache-builds: If you have the exact same build you want, already built by Carthage in your project before, then Carthage will use that instead of rebuilding. So use it whenever you can!

Again, Carthage has even more flags to use, so go check them out.

Other whys

More questions? Hope I get to answer them, if not feel free to comment!

  • Why do I need the "Carthage Outdated?" Build Phase? If you have any dependency out of sync with the expected ones (Cartfile.resolved), XCode will give you a warning!
  • Why do I need the "Carthage" Build Phase? The binaries must be copied to the device when the app is installed, and as they are not part of a workspace, you need to tell xcode that by hand, so this takes care of it for you.
  • Why do I need to complete the Input Files and Output Files sections? You need to tell Carthage which frameworks he should copy since it’s not “all the frameworks you find”. For example, if you are using Quick and Nimble for testing, as we do at Wolox, you wouldn’t want them to be bundled with your app, just your tests. So that’s why you need to specify the Input Files. The Output Files are indeed optional, but if you complete it, Carthage will be able to do optimizations and take less time to install frameworks on a device, and less building time is all we want!
  • My project has CocoaPods, can I use Carthage? Of course! Carthage is completely compatible with CocoaPods since it doesn’t affect your project structure.
  • My project is a Framework and I want it to be Carthage compatible, what do I have to do? You just need to make your principal Scheme a Shared one (in the Manage Schemes view). It’s that easy!
  • The CI takes a long time to build carthage dependencies, since we aren’t committing them to the repo. What can I do? This is true. So a partner at Wolox developed this Carthage Cache tool, in order for you to publish the just built frameworks to an AWS S3 bucket and the CI can download them from there instead of compiling and building the dependencies.
  • Why don’t I commit the Carthage folder? You could, but you would be pestering your repository with a lot of binaries you could just download, since all the really necessary information is in the Cartfiles. Also, Carthage builds the dependencies with the XCode you are using, so if different teammates have different XCodes and the project builds with both, they could have different .frameworks in each local machine but the same Cartfile. (Although most of the times, project don’t work with different XCodes and so the same dependency version doesn’t work with different XCodes.)

--

--