Dependency Management Using Git Submodules

In this Dependency Management tutorial you’ll learn how to use Git Submodules to manage both internal and external dependencies for your project. By Andy Obusek.

Leave a rating/review
Save for later

As an iOS developer, dependency management is something you’ll eventually encounter in your coding adventures.

Whether you want to integrate someone else’s open source project, add a library from a third party service, or even reuse code across your own projects, dependency management helps you manage these complex code relationships — and guard against some messy problems.

In this iOS dependency management tutorial, you’ll learn how to use Git Submodules to manage dependencies for your iOS application. This will include both a private dependency for something like shared code between your own code bases, as well as a separate example where you pull in an outside third party dependency as a Git Submodule.

Getting Started

Download the starter project for this tutorial. Build and run, and you should see the following:

Screen Shot 2017-02-10 at 9.20.38 PM

You can try to select a photo, but the app won’t do much in response. Throughout the rest of this tutorial, you’ll add behavior to the app while integrating other dependencies with Git Submodules.

First — a little bit of background on dependency management.

What Is Dependency Management?

Dependency management is a concept that spans all software development disciplines, not just iOS development. It’s the practice of using configuration mechanisms to add extra code, and therefore extra features, to your software.

Probably the most basic form of dependency management is to simply copy and paste code into your own app. There are several problems with this approach though:

  1. The original reference is lost. When you copy and paste code, there’s no reference back to the original spot where the code was found, and it’s easily forgotten about.
  2. Updates aren’t easily integrated. When changes are made to the original code you copied, it becomes very hard to track what’s changed so you can apply those changes back to your cut and pasted code. Some third party libraries can have thousands of lines of code, spread across hundreds of files, and it’s impossible to keep things synchronized manually.
  3. Version information isn’t maintained. Proper software development practices call for versioning releases of your code. You’ll find this consistent in third party libraries you use in your projects. When you copy and paste code, there’s no easy way to know you’re using version 1.2.2 of library XYZ, and how will you remember to update your code when version 1.2.3 is released?

I’m sure it wasn’t hard to convince you copy and pasting code is a terrible idea. :]

Dependency Management Tools

There are several great tools to manage dependencies in iOS development, but it can be confusing to know which one to use.

CocoaPods might be the most popular. It certainly has a large number of libraries available for use.

Carthage is the younger cousin to CocoaPods. While newer, it’s written in Swift and some find it easier to use than CocoaPods.

Then there’s the Swift Package Manager, which is even newer to the scene and is stewarded by Apple through the open source community.

These are just some of the big players in the iOS dependency management game — and there’s even more options beyond those.

But what if I told you you didn’t need to use an additional tool to manage your dependencies? Would you Git excited? :]

If you’re already using Git for version management for your iOS project, you can use Git itself to manage your dependencies.

In the next section, you’ll see how to manage dependencies using Git Submodules.

Let’s Git started!

Working With A Private Dependency

As an iOS developer, you’ll often work on more than one project. You’ll also find yourself repeatedly using the same pieces of code to solve similar problems. You can easily use Git Submodules to create a dependency from one project (the main project) to another personal project (the private dependency).

Note: This tutorial will focus on using Git from the command line. For a reference on using Git from Xcode, check out How To Use Git Source Control with Xcode.

Connecting a Private Dependency

Open Terminal and navigate to the folder of your sample project. Execute ls to see the contents of the folder. You’ll know you’re in the right place when it looks like this:

AndyRW|⇒ cd PhotoTagger 
PhotoTagger|master ⇒ ls
PhotoTagger           PhotoTagger.xcodeproj
PhotoTagger|master ⇒ 

The first thing you need to do is initialize the project as a Git repository. Execute git init:

PhotoTagger|⇒ git init
Initialized empty Git repository in /Users/andyo/Documents/AndyRW/PhotoTagger/.git/
PhotoTagger|master⚡ ⇒ 

This sets up the current folder and its contents as a Git repository, though nothing is actually version managed yet.

Next, execute git add . followed by git commit -m "Initial project":

PhotoTagger|master⚡ ⇒ git add .
PhotoTagger|master⚡ ⇒ git commit -m "Initial project"
[master (root-commit) 1388581] Initial project
 13 files changed, 1050 insertions(+)
 create mode 100755 .gitignore
 create mode 100755 PhotoTagger.xcodeproj/project.pbxproj
 create mode 100755 PhotoTagger.xcodeproj/project.xcworkspace/contents.xcworkspacedata
 create mode 100755 PhotoTagger/AppDelegate.swift
 create mode 100755 PhotoTagger/Assets.xcassets/AppIcon.appiconset/Contents.json
 create mode 100755 PhotoTagger/Base.lproj/LaunchScreen.storyboard
 create mode 100755 PhotoTagger/Base.lproj/Main.storyboard
 create mode 100755 PhotoTagger/Info.plist
 create mode 100755 PhotoTagger/PhotoColor.swift
 create mode 100644 PhotoTagger/TagsColorTableData.swift
 create mode 100755 PhotoTagger/TagsColorsTableViewController.swift
 create mode 100755 PhotoTagger/TagsColorsViewController.swift
 create mode 100755 PhotoTagger/ViewController.swift
PhotoTagger|master ⇒ 

This adds the contents of the project to version management and takes a snapshot of the contents as a commit.

Note: Does your Terminal prompt look different than this? Examples in this tutorial are shown from Oh My Zsh shell with a custom theme. If you normally use Git, try one of those themes out — you’ll love them!

Execute git status to confirm the state of the local repository; i.e. to confirm there are no outstanding changes that haven’t been committed:

PhotoTagger|master ⇒ git status
On branch master
nothing to commit, working tree clean
PhotoTagger|master ⇒ 

This means your local Git repository sees no local changes. That’s a good thing, since you haven’t changed anything in the code base.

Now you’ve confirmed the state of the local Git repository for the project, it’s time to create your private dependency.

For this tutorial, the private dependency you create will be a reusable project that helps specify URL paths to the Imagga API. Not only will it be useful for this project, but any other future projects you create that use the Imagga API will be able to reuse this private dependency.

In Xcode, select File\New\Project…. Select Cocoa Touch Framework\Next.

Screen Shot 2017-02-11 at 2.51.47 PM

Enter ImaggaRouter as the Product Name.

Screen Shot 2017-02-11 at 2.51.54 PM

Click Next, and navigate to the parent of the PhotoTagger project folder. Then click Create to create the new project.

You should now be looking at an empty Xcode project representing your new project. Folder-wise, this project should be in the same parent folder as the PhotoTagger folder.

Now you have your private dependency project created, you’re ready to designate it as your first Git Submodule.

First, the private dependency project needs to be initialized as a Git repository itself. In Terminal, navigate into the ImaggaRouter folder and execute git init:

AndyRW|⇒ cd ImaggaRouter 
ImaggaRouter|⇒ git init
Initialized empty Git repository in /Users/andyo/Documents/AndyRW/ImaggaRouter/.git/

This initializes the ImaggaRouter project as a Git repository.

Next you need to add and commit the empty project. Execute git add . followed by git commit -m "Initial ImaggaRouter":

ImaggaRouter|master⚡ ⇒ git add .
git %                                                                                                                                 ImaggaRouter|master⚡ ⇒ git commit -m "Initial ImaggaRouter"
[master (root-commit) 554d7a1] Initial ImaggaRouter
 36 files changed, 517 insertions(+)

This tells Git to be aware of the files from the empty project. Committing them marks a “snapshot” of the state of the files.

This concludes setting up ImaggaRouter as a Git repository with an initial set of files. Now you need to add it as a submodule of PhotoTagger.

First, create a folder hierarchy to store your dependencies. Navigate to the root folder of PhotoTagger and execute mkdir Frameworks; mkdir Frameworks/Internal:

AndyRW|⇒ cd PhotoTagger 
PhotoTagger|⇒ mkdir Frameworks; mkdir Frameworks/Internal

This step isn’t technically necessary for working with Git Submodules, but this folder hierarchy is a good way to keep track of the locations of dependencies in your project.

Now to finally identify ImaggaRouter as a dependency!

From the root folder of PhotoTagger, execute git submodule add ../ImaggaRouter Frameworks/Internal/ImaggaRouter/:

PhotoTagger|master ⇒ git submodule add ../ImaggaRouter Frameworks/Internal/ImaggaRouter/
Cloning into '/Users/andyo/Documents/AndyRW/PhotoTagger/Frameworks/Internal/ImaggaRouter'...
PhotoTagger|master⚡ ⇒ 

This command tells the Git repository for PhotoTagger about the dependency on another Git repository (the one for ImaggaRouter). You’ll see this step creates a new file as well: .gitmodules.

[submodule "Frameworks/Internal/ImaggaRouter"]
        path = Frameworks/Internal/ImaggaRouter
        url = ../ImaggaRouter

This file contains the actual definition for the submodule.

You’ll also notice this file is marked as a new file from Git’s perspective.

Execute git status to see the current state of the local repository:

PhotoTagger|master⚡ ⇒ git status                                       
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   .gitmodules
	new file:   Frameworks/Internal/ImaggaRouter

PhotoTagger|master⚡ ⇒ 

It’s nice git status is reporting changes to both the .gitmodules file and the new directory for ImaggaRouter.

On the other hand, it’s not so nice git status leaves out any information about the submodule itself. Since submodules are treated like nested repositories, git status will not report on submodules by default.

Luckily, this can be changed. Execute git config --global status.submoduleSummary true to change this default:

PhotoTagger|master⚡ ⇒ git config --global status.submoduleSummary true
PhotoTagger|master⚡ ⇒ 

Check the output of git status again:

PhotoTagger|master⚡ ⇒ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   .gitmodules
	new file:   Frameworks/Internal

Submodule changes to be committed:

* Frameworks/Internal 0000000...554d7a1 (1):
  > Initial ImaggaRouter

PhotoTagger|master⚡ ⇒ 

Awesome! git status now reports on the state of the submodule as well as the main project, and it indicates to you specifically what will be committed.

At this point, Git is aware of the new submodule for the project, but hasn’t actually marked a snapshot of the state. To do that, you’ll repeat the same steps from earlier.

Execute git add ., followed by git commit -m "Add ImaggaRouter dependency":

PhotoTagger|master⚡ ⇒ git add .
PhotoTagger|master⚡ ⇒ git commit -m "Add ImaggaRouter dependency"
[master 6a0d257] Add ImaggaRouter dependency
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 Frameworks/Internal
PhotoTagger|master ⇒ 

Now the local Git repository for the PhotoTagger project has taken a snapshot of the current project and its configuration with a dependency on ImaggaRouter.

Now you need to add the ImaggaRouter project to the Xcode project for PhotoTagger.

In Finder, navigate within the PhotoTagger folder to Frameworks/Internal/ImaggaRouter and drag ImaggaRouter.xcodeproj into the root of the PhotoTagger Xcode project.

Private dependency management

Adding the ImaggaRouter project to Xcode makes the code within it (although there’s none yet) available for use within the PhotoTagger project.

You also need to link the framework with the target. Do this in the General settings for the PhotoTagger target:

Private dependency management

Note: If you don’t immediately see ImaggaRouter.framework in the list of frameworks, try building the project, or closing the project and reopening it.

This will result in a change to PhotoTagger.xcodeproj/project.pbxproj which will need to be committed.

First, you can verify there is a local change that needs to be committed by executing git status.

PhotoTagger|master⚡ ⇒ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   PhotoTagger.xcodeproj/project.pbxproj

no changes added to commit (use "git add" and/or "git commit -a")

To commit the change, execute git add . followed by git commit -m "Add ImaggaRouter project to Xcode":

PhotoTagger|master⚡ ⇒ git add .
PhotoTagger|master⚡ ⇒ git commit -m "Add ImaggaRouter project to Xcode"
[master 911dee9] Add ImaggaRouter project to Xcode
 1 file changed, 36 insertions(+)
PhotoTagger|master ⇒ 

Congratulations – you’ve successfully connected two private projects via Git Submodules! :]