Skip to main content
Version: 3.x

Adding external dependencies

External dependencies are also represented by a graph where nodes represent frameworks, libraries, or bundles. Dependency managers like CocoaPods integrate it when running pod install leveraging Xcode workspaces, and Swift Package Manager does it at build time leveraging Xcode's closed build system. Both approaches might lead to integration issues that can cause compilation issues down the road. We are aware that's not a great developer experience, and thus we take a different approach to managing external dependencies that allow leveraging Tuist features such as linting and caching. The idea is simple; developers define their Carthage and Package dependencies in a Dependencies.swift file. They are fetched by running tuist fetch and integrated into the generated Xcode project at generation time. Because we merge your project and the external dependencies' graph into a single graph, we validate and fail early if the resulting graph is invalid.

CocoaPods support

As more and more libraries are dropping CocoaPods support, there is no plan to support it in Dependencies.swift at the moment. You can still use it on top of the tuist generated project by just running pod install after tuist generate.

Xcode native Swift Package Manager support

If you prefer, you can use the native Xcode native Swift Package Manager support by defining the packages in the Project.packages property, and adding the dependencies to the target using the TargetDependency.package case.

It can be useful in case some dependencies has problem with the Dependencies.swift integration, or in case you need to use a Swift Package Manager plugin, but doing so you will lose support for important features like caching and package resolution at generation time.

Declaring the dependencies

External dependencies are declared in a Dependencies.swift file in your project's Tuist directory at the project's root. If that file doesn't exist, create an empty file, and run tuist edit to edit its content with Xcode. The snippet below shows an example Dependencies.swift manifest file:

import ProjectDescription

let dependencies = Dependencies(
carthage: [
.github(path: "Alamofire/Alamofire", requirement: .exact("5.0.4")),
],
swiftPackageManager: [
.remote(url: "https://github.com/Alamofire/Alamofire", requirement: .upToNextMajor(from: "5.0.0")),
],
platforms: [.iOS]
)
Standard interface

One of the benefits of using Tuist's built-in support for declaring external dependencies is that the interface is standard across all the supported dependency managers. It makes moving a dependency from a dependency manager to another a non-breaking change.

Fetching dependencies

After dependencies have been declared, you need to fetch them by running tuist fetch. Tuist will use the dependency managers to pull the dependencies under the Tuist/Dependencies directory:

Tuist
|- Dependencies.swift # Manifest
|- Dependencies
|- graph.json # stores the serialized dependencies graph generated by `tuist fetch`
|- Lockfiles # stores the lockfiles generated by the dependencies resolution
|- Carthage.resolved
|- Package.resolved
|- Carthage
|- Build # stores content of `Carthage/Build` directory generated by `Carthage`
|- Alamofire.xcframework
|- .Alamofire.version
|- Cartfile # the generated Cartfile
|- SwiftPackageManager
|- .build # stores content of `.build/` directory generated by `Swift Package Manager`
|- artifacts
|- checkouts
|- repositories
|- manifest.db
|- workspace-state.json
|- Package.swift # the generated Package.swift

We recommend excluding the following files and directories from version control (for example, in your .gitignore file).

Tuist/Dependencies/graph.json # Avoid checking in the serialized dependencies graph generated by Tuist.
Tuist/Dependencies/Carthage # Avoid checking in build artifacts from Carthage dependencies.
Tuist/Dependencies/SwiftPackageManager # Avoid checking in build artifacts from Swift Package Manager dependencies.

Integrating dependencies into your project

Once dependencies have been fetched, you can declare dependencies from your projects' targets. Run tuist edit to edit your project's manifest, and use the .external target dependency option to declare the dependency.

The snippet below shows an example Project.swift manifest file:

import ProjectDescription

let project = Project(
name: "App",
organizationName: "tuist.io",
targets: [
Target(
name: "App",
platform: .iOS,
product: .app,
bundleId: "io.tuist.app",
deploymentTarget: .iOS(targetVersion: "13.0", devices: .iphone),
infoPlist: .default,
sources: ["Targets/App/Sources/**"],
dependencies: [
.external(name: "Alamofire"),
]
),
]
)

Some notes on the integration of Swift packages

  • When Swift packages are integrated as source code into your project's graph, no scheme is created for them, as you usually don't want to build only the dependency explicitly. If you need it (for example, for testing some problem in the library), you can create the scheme directly from Xcode, or you can define it in your Project.swift if you need it to be available across generations.
  • If present, Tuist uses the product type defined in the package manifest file. Otherwise, it defaults to use .staticFramework. You can override the product type using the SwiftPackageManagerDependencies.productTypes property.
  • To use Swift packages from an Objective-C target, add the path to the public headers of the package to the HEADER_SEARCH_PATHS of the target. The path will be something like Tuist/Dependencies/SwiftPackageManager/.build/checkouts/<your_package>/<the_headers> (workaround for issue 4180).