Setting up dependencies is not easy. When dependencies have transitive dependencies things get complicated because it requires changes in the targets that are part of the branch where the transitive dependency is. To illustrate that, think about an app, depending on a dynamic framework Search, which has no dependencies. If at some point in the future we add a new dynamic framework Core, on which Search depends, well need to update not only Search, but the app to embed the framework into the product.

Imagine a modular app made of 8 projects, with at least two targets each of them (to compile the framework/library and run the tests), with dependencies between them. That’s a very common setup in large projects, especially if you need to reuse code across different targets. With 16 targets to set up, there’s much knowledge that the developers need to keep in mind to do things the right way. Who is depending on this target? Where do I need to embed this dynamic framework? Which build settings should I update to make the public interface of the library available?

Fortunately, Tuist takes care of all that work for you. It allows you to define dependencies and it uses that knowledge to set up the targets with the right build phases and build settings.

If you noticed when we first introduced the manifest file, there isn’t any public model for defining linking build phases. We made that on purpose because we’d like to figure out all those things for you.

Defining dependencies

The Target model that we use from the manifest has a property, dependencies, that allows you to define the dependencies of the target. A dependency can have any of the following values:

  • .target("App"): It defines a dependency with another target in the same project. For instance, a tests target depends on the target that is being tested.
  • .project(target: "Core", path: "../Core"): It defines a dependency with a target in another project. When the workspace gets generated, the other project is also included so that Xcode knows how to compile that other target.
  • .framework(path: "Carthage/Build/iOS/Alamofire.framework"): It defines a dependency with a precompiled framework, for example, a framework that has been compiled by Carthage. If the framework contains multiple architectures, Tuist will add an extra build phase to strip them.
  • .library(path: "Vendor/Library.a", publicHeaders: nil, swiftModuleMap: "Vendor/Library.modulemap"): It defines a dependency with a precompiled library. It allows specifying the path where the public headers or Swift module map is.

As we mentioned, the beauty of defining your dependencies with Tuist is that when you generate the project, things are set up and ready for you to successfully compile your targets.

Still not supported

  • Although you can use CocoaPods and Carthage, you have to run the pod install, and carthage update commands yourself. We’re working on making that easier so that you don’t need to run commands other than Tuist’s to work on your projects.
  • We are also working on supporting dependencies between app extensions and apps. Those dependencies require an additional build phase that copies the extension into a folder in the app.
  • Static transitive dependencies are not supported yet but there’s already an ongoing discussion to add support for it.

Note: If you come across any dependencies scenario that is not well supported, it’d be great if can open an issue so that we can tackle it.

Got improvements? Help improve this document via sending PRs.