XcodeGen is a tool for improving the generation of .xcodeproj files and their maintenance over time. By defining a simple YAML manifest you get a whole .xcodeproj in seconds. But that’s not everything! There is one more built-in feature — dependency graphs. In this article, I would like to cover possible use cases of a such graph.
Example project
For this article, I’m going to use a simple project with 2 modules called Cart and Payments where Cart depends on Payments. Each of the modules has some external dependencies as well. Here is a simple diagram drawn by hand:
Our project has a structure where every module has its project.yml file so in this example there are 2 files overall.
Generating the graph
The simplest way to generate is to run the following command in the directory with the project.yml file
xcodegen dump --type graphviz
So here is the output for both modules:
Let’s visualize it
The graphs are generated in Graphviz format (.viz) and guess what - it’s a more than 30 years old piece of software! Luckily, there are online tools that can do the visualization for us in seconds…
Using a https://dreampuf.github.io/GraphvizOnline for a Cart module results in the following diagram:
Merging graphs
Rendering each module would not give any valuable knowledge… But what if you could merge graphs? To do that I am going to use even older software. Cue m4 entering the stage which is 45 years old 😃. It’s an ideal language for text replacement. A combination of this simple script (below) and a file that lists all subdiagrams results in a fully merged graph.
digraph dependency_graph {
define(`digraph',`subgraph')dnl
include(`viz_includes')dnl
}
As you can notice it looks the same as the diagram drawn by myself, much prettier actually 😃. Of course, it doesn’t matter for 2 modules with only several dependencies but the great benefit comes when you use it for a larger project with dozens or even hundreds of modules…
Running tests for changes only
I want to share with you a real practical example of having a modular application graph. The idea is to run tests only for changed modules rather than the whole project. The more modules you have the more time saving that is. For instance, in one of the products I developed - such change reduced an average CI job from 20 to 5 minutes, so it’s worth exploring.
I don’t usually share a lot of code in the articles so here is the simplest possible Ruby method that gets modules to test based on git diff.
def get_modules_to_test
modules_to_test = []
changed_modules_by_git_diff = `git diff --name-only source ... destination`.scan(/(?<=ModulesDirectory\/)[^\/]*/).uniq
changed_modules_by_git_diff.each do |changed_module|
modules_to_test.append(changed_module)
File.open('merged_dependencies_graph.viz').each do |graph_line|
graphviz_dependant_regexp = ".*(?=-\> \"#{changed_module}\")"
next unless graph_line.match?(graphviz_dependant_regexp)
modules_to_test.append(graph_line.match(graphviz_dependant_regexp).to_s.strip)
end
end
modules_to_test.uniq
end
The critical moment in such diff-based detection is to detect dependants as well. For instance, in our simplest architecture above if the module Payments changes then Xcode should run tests for both Payments and Cart because Cart depends on Payments. This is where the graph gives us everything we need. When all subgraphs of each module are merged all you need to do is just look for some specific text in a final graph. A -> "B"
means module A depends on B. So when module “B” is changed the script is looking for all ? -> "B"
occurrences and treats these modules as dependants to be tested as well with “B”.
Other use cases
It appears that having such a graph representation might be useful in other cases too. I am a power user of Sourcery, a tool for generating Swift code from templates. However, it knows nothing about Xcode targets and dependencies so with a graph it’s easy to use Sourcery in a modular architecture. I can easily grab module dependencies and use them as ‚imports’ statements in generated files.
The same goes with any other tooling you have around modular projects… At some point, your automations will need to be module relationship aware. If .xcodeproj generation is based on XcodeGen then you should use its graph. It’s super cheap and helpful!