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.
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
.
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]
)
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 theSwiftPackageManagerDependencies.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 likeTuist/Dependencies/SwiftPackageManager/.build/checkouts/<your_package>/<the_headers>
(workaround for issue 4180).