Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Tuist
https://github.com/tuist/tuist
Admin
Build better Swift apps faster
Tokens:
396,940
Snippets:
1,983
Trust Score:
9.7
Update:
6 days ago
Context
Skills
Chat
Benchmark
78.9
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Tuist Tuist is an open-source virtual platform team for Swift and Apple-platform app developers. It provides a unified toolchain that sits on top of Xcode and the Swift ecosystem, exposing a Swift-based DSL for declaratively defining Xcode projects, managing dependencies, and orchestrating advanced CI/CD workflows. The platform targets teams who suffer from slow builds, brittle project configurations, frequent merge conflicts in `.pbxproj` files, and lack of tooling around code-sharing conventions. Tuist is organized as a monorepo containing the CLI (Swift), a backend server (Elixir/Phoenix), a macOS/iOS companion app (SwiftUI), a Rust-based cache mesh service (Kura), Gradle integrations, and documentation. The core functionality spans five main areas: (1) **Generated Projects** — declaring Xcode projects and workspaces in type-safe Swift manifest files (`Project.swift`, `Workspace.swift`, `Tuist.swift`) and generating them on-demand with `tuist generate`; (2) **Module Cache** — warming and reusing compiled `.xcframework` binaries across local and CI environments; (3) **Xcode Cache** — sharing Xcode 26+ compilation artifacts via a local socket daemon; (4) **Selective Testing** — running only the test targets whose inputs changed since the last successful run; and (5) **Previews** — building and sharing installable app builds as links via `tuist share`. Every feature integrates with Tuist's content-addressable hashing algorithm and optional cloud backend. --- ## Project manifest — `Project.swift` The `Project` type is the root manifest object for a single Xcode project. It holds targets, schemes, settings, packages, and additional file references. A `Project.swift` file at any directory generates a `.xcodeproj` file in the same location. ```swift // Project.swift import ProjectDescription let project = Project( name: "App", organizationName: "com.example", options: .options( automaticSchemesOptions: .enabled( targetSchemesGrouping: .byNameSuffix(build: [], test: ["Tests"], run: []) ) ), packages: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.9.0"), ], settings: .settings( base: ["SWIFT_VERSION": "5.10"], configurations: [ .debug(name: "Debug", xcconfig: "Config/Debug.xcconfig"), .release(name: "Release", xcconfig: "Config/Release.xcconfig"), ], defaultSettings: .recommended ), targets: [ .target( name: "App", destinations: [.iPhone, .iPad, .mac], product: .app, bundleId: "com.example.App", deploymentTargets: .iOS("16.0"), infoPlist: .extendingDefault(with: ["CFBundleDisplayName": "App"]), sources: ["Sources/App/**"], resources: ["Resources/**"], dependencies: [ .external(name: "Alamofire"), .target(name: "AppKit"), ] ), .target( name: "AppKit", destinations: .iOS, product: .framework, bundleId: "com.example.AppKit", sources: ["Sources/AppKit/**"], dependencies: [] ), .target( name: "AppTests", destinations: .iOS, product: .unitTests, bundleId: "com.example.AppTests", sources: ["Tests/AppTests/**"], dependencies: [.target(name: "App"), .xctest] ), ], schemes: [ .scheme( name: "App", buildAction: .buildAction(targets: ["App"]), testAction: .targets(["AppTests"], arguments: .arguments( environmentVariables: ["CI": .init(value: "1", isEnabled: true)] )), runAction: .runAction(executable: "App") ), ], additionalFiles: ["README.md", "Dangerfile.swift"] ) ``` --- ## Target definition — `Target.target` `Target.target` is the primary factory for describing a compilable build product. It configures sources, resources, headers, entitlements, dependencies, build settings, scripts, and metadata. ```swift import ProjectDescription // iOS + macOS framework with per-configuration entitlements, a run script, and a linked SDK let frameworkTarget = Target.target( name: "Analytics", destinations: [.iPhone, .mac], product: .framework, bundleId: "com.example.Analytics", deploymentTargets: .multiplatform(iOS: "16.0", macOS: "13.0"), infoPlist: .default, sources: ["Sources/Analytics/**/*.swift"], resources: [ "Resources/**", .folderReference(path: "PrivacyInfo.xcprivacy"), ], headers: .headers( public: ["Sources/Analytics/Public/**/*.h"], private: [], project: ["Sources/Analytics/Internal/**/*.h"] ), entitlements: nil, // use per-config CODE_SIGN_ENTITLEMENTS scripts: [ .pre( path: "Scripts/lint.sh", name: "SwiftLint", basedOnDependencyAnalysis: false ), ], dependencies: [ .sdk(name: "Network", type: .framework), .target(name: "Core"), ], settings: .settings( base: [ "OTHER_LDFLAGS": "$(inherited) -ObjC", "CODE_SIGN_ENTITLEMENTS": "", ], configurations: [ .debug(name: "Debug", settings: ["CODE_SIGN_ENTITLEMENTS": "Debug.entitlements"]), .release(name: "Release", settings: ["CODE_SIGN_ENTITLEMENTS": "Release.entitlements"]), ] ), environmentVariables: ["ANALYTICS_ENV": .init(value: "development", isEnabled: true)], mergeable: true // eligible for Mergeable Libraries ) ``` --- ## Foreign build target — `Target.foreignBuild` `Target.foreignBuild` wraps a non-Xcode build system (Kotlin Multiplatform, Rust, CMake) so that its output XCFramework participates in Tuist's binary caching and selective testing hashing. ```swift import ProjectDescription // Kotlin Multiplatform shared library compiled via Gradle let kmpTarget = Target.foreignBuild( name: "SharedKMP", destinations: .iOS, script: """ eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" cd $SRCROOT/SharedKMP && gradle assembleSharedKMPReleaseXCFramework """, inputs: [ .folder("SharedKMP/src"), .file("SharedKMP/build.gradle.kts"), .script("git -C $SRCROOT rev-parse HEAD"), // contributes to cache key ], output: .xcframework( path: "SharedKMP/build/XCFrameworks/release/SharedKMP.xcframework", linking: .dynamic ) ) // Another target can depend on it let appTarget = Target.target( name: "App", destinations: .iOS, product: .app, bundleId: "com.example.App", sources: ["Sources/App/**"], dependencies: [ .target(name: "SharedKMP"), ] ) ``` --- ## Workspace manifest — `Workspace.swift` `Workspace` customizes the Xcode workspace Tuist generates. Use it to aggregate multiple `Project.swift` manifests, add shared schemes, and set generation options. ```swift // Workspace.swift import ProjectDescription let workspace = Workspace( name: "MyApp", projects: [ "App", "Modules/Feature/**", // glob: all Project.swift files under Feature/ "Modules/Core", ], schemes: [ .scheme( name: "CI", shared: true, buildAction: .buildAction(targets: [ .project(path: "App", target: "App"), ]), testAction: .targets([ .testableTarget(target: .project(path: "App", target: "AppTests")), .testableTarget(target: .project(path: "Modules/Core", target: "CoreTests")), ]) ), ], additionalFiles: [".github/workflows/**", "Makefile"], generationOptions: .options( autogeneratedWorkspaceSchemes: .disabled, lastXcodeUpgradeCheck: "1600" ) ) ``` --- ## Tuist configuration — `Tuist.swift` `Tuist.swift` placed at the project root configures global generation options, the cloud backend project handle, plugins, and the Xcode / Swift Package Registry. ```swift // Tuist.swift import ProjectDescription let tuist = Tuist( fullHandle: "my-org/my-app", // links to tuist.dev cloud project xcodeCache: .xcodeCache( upload: Environment.isCI // only upload from CI ), project: .tuist( plugins: [ .git(url: "https://github.com/my-org/tuist-plugin.git", tag: "1.2.0"), ], generationOptions: .options( enforceExplicitDependencies: true, registryEnabled: true, // use Tuist Package Registry enableCaching: true, // enable Xcode cache (Xcode 26+) defaultConfiguration: "Debug" ) ) ) ``` --- ## Settings and build configurations — `Settings` `Settings` encapsulates a project-level or target-level build settings dictionary, optional `.xcconfig` overrides, and a list of `Configuration` objects representing Debug/Release variants or custom configurations. ```swift import ProjectDescription // Custom Beta + AppStore configuration pair with xcconfigs let customSettings = Settings.settings( base: [ "SWIFT_VERSION": "5.10", "ENABLE_TESTABILITY": "YES", "OTHER_LDFLAGS": ["$(inherited)", "-ObjC"], ], configurations: [ .debug( name: "Beta", settings: ["BUNDLE_ID_SUFFIX": ".beta"], xcconfig: "Config/Beta.xcconfig" ), .release( name: "AppStore", settings: [ "BUNDLE_ID_SUFFIX": "", "SWIFT_COMPILATION_MODE": "wholemodule", ], xcconfig: "Config/AppStore.xcconfig" ), ], defaultSettings: .recommended(excluding: ["SWIFT_ACTIVE_COMPILATION_CONDITIONS"]), defaultConfiguration: "Beta" ) let target = Target.target( name: "App", destinations: .iOS, product: .app, bundleId: "com.example.App", sources: ["Sources/**"], settings: customSettings ) ``` --- ## Target dependencies — `TargetDependency` `TargetDependency` is an enum that covers every kind of dependency a target may have: local targets, cross-project targets, Swift packages (Tuist-integrated or Xcode-integrated), prebuilt frameworks/libraries/XCFrameworks, system SDKs, and Tuist external dependencies. ```swift import ProjectDescription let appTarget = Target.target( name: "App", destinations: .iOS, product: .app, bundleId: "com.example.App", sources: ["Sources/App/**"], dependencies: [ // Same-project target .target(name: "AppKit"), // Cross-project target .project(target: "Networking", path: "../Modules/Networking"), // Tuist XcodeProj-based external package (resolved via tuist install) .external(name: "Alamofire"), // Xcode-native SPM package (declared in Project.packages) .package(product: "CryptoSwift", type: .runtime), // Macro package .package(product: "SwiftSyntaxMacros", type: .macro), // Prebuilt XCFramework .xcframework(path: "Prebuilt/Analytics.xcframework", status: .required), // System SDK .sdk(name: "ARKit", type: .framework), .sdk(name: "c++", type: .library, status: .required), // Platform-conditional dependency (iOS only) .target(name: "iOSFeature", condition: .when([.ios])), ] ) ``` --- ## External dependencies via `Package.swift` Tuist-managed external Swift packages are declared in `Tuist/Package.swift` (or root `Package.swift`) and resolved via `tuist install`. The optional `PackageSettings` block (behind `#if TUIST`) controls product types and target settings. ```swift // Tuist/Package.swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // override from staticFramework "ComposableArchitecture": .framework, "FBLPromises": .framework, // required for Firebase ], baseSettings: .settings(configurations: [ .debug(name: "Debug"), .release(name: "AppStore"), ]), targetSettings: [ "ComposableArchitecture": .settings(base: [ "OTHER_SWIFT_FLAGS": ["-module-alias", "Sharing=SwiftSharing"] ]) ] ) #endif let package = Package( name: "PackageName", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.9.0"), .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.15.0"), .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.0.0"), .package(url: "https://github.com/Sentry/sentry-cocoa", from: "8.40.0"), ] ) ``` ```bash # Resolve and pull all declared packages into Tuist/Dependencies tuist install ``` --- ## Swift Package Registry Tuist implements the Swift Package Registry protocol backed by the Swift Package Index, enabling shallow package downloads to replace full Git clones and dramatically speeding up dependency resolution. ```swift // Tuist.swift — enable registry automatically during generation import ProjectDescription let tuist = Tuist( project: .tuist( generationOptions: .options( registryEnabled: true ) ) ) ``` ```bash # Manual setup: generates registry config file that should be committed tuist registry setup # Authenticate for higher rate limit (20 000 req/min vs 1 000 unauthenticated) tuist registry login # Use registry identifiers in Package.swift instead of full URLs # identifier format: {organization}.{repository} # e.g., pointfreeco.swift-composable-architecture ``` ```swift // Tuist/Package.swift using registry identifiers let package = Package( name: "PackageName", dependencies: [ .package(id: "pointfreeco.swift-composable-architecture", from: "1.15.0"), .package(id: "groue.GRDB_swift", from: "6.0.0"), // dots replaced with underscores ] ) ``` --- ## Module cache — `tuist cache` The module cache warms and re-uses compiled `.xcframework` binaries keyed by Tuist's content-addressable target hashes. Warming is performed with `tuist cache` and consumption is automatic when `tuist generate` runs. ```bash # Warm the cache (builds all cacheable targets and uploads artifacts) tuist cache # Warm only for the Release configuration tuist cache --configuration Release # Generate with a specific cache profile tuist generate --cache-profile all-possible # replace all cacheable targets tuist generate --cache-profile only-external # replace external deps only (default) tuist generate --cache-profile none # disable binary replacement # Focus mode: replace everything that App doesn't need to rebuild tuist generate App # Control concurrency when network is constrained export TUIST_CACHE_CONCURRENCY_LIMIT=10 tuist generate ``` ```swift // Tuist/Package.swift — customize how packages are linked (affects cacheability) #if TUIST import ProjectDescription let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // dynamic = cacheable, can be embedded ] ) #endif ``` --- ## Xcode cache — `tuist setup cache` The Xcode cache (Xcode 26+) runs a local socket daemon that Xcode's build system talks to for sharing compilation artifacts across machines via the Tuist cloud backend. ```bash # One-time setup: installs a LaunchAgent and starts the daemon tuist setup cache # On CI, same command — authenticate first tuist auth login # OIDC or TUIST_TOKEN env var tuist setup cache # Tear down daemon + LaunchAgent + socket file tuist teardown cache # Diagnose: check whether the daemon is listening lsof ~/.local/state/tuist/my_org_my_app.sock log stream --predicate 'subsystem == "dev.tuist.cache"' --debug ``` ```swift // Tuist.swift — generated project with CI-only uploads import ProjectDescription let tuist = Tuist( fullHandle: "my-org/my-app", xcodeCache: .xcodeCache( upload: Environment.isCI // only populate from CI, locals read-only ), project: .tuist( generationOptions: .options(enableCaching: true) ) ) ``` ```bash # Manual Xcode project: set build settings on xcodebuild invocations xcodebuild build -project App.xcodeproj -scheme App \ COMPILATION_CACHE_ENABLE_CACHING=YES \ COMPILATION_CACHE_REMOTE_SERVICE_PATH=$HOME/.local/state/tuist/my_org_my_app.sock \ COMPILATION_CACHE_ENABLE_PLUGIN=YES \ COMPILATION_CACHE_ENABLE_DIAGNOSTIC_REMARKS=YES ``` --- ## Selective testing — `tuist test` `tuist test` hashes targets using the same algorithm as the module cache and persists hashes on a successful run. On subsequent runs it transparently skips targets whose hashes haven't changed. ```bash # Run all tests — Tuist skips targets with unchanged hashes automatically tuist test # Run tests for specific schemes tuist test MyApp MyAppTests # Run on a specific device/configuration tuist test --device "iPhone 16 Pro" --os "18.0" --configuration Release # Force a full run (ignore cached hashes) tuist test --clean # CI GitHub Actions example ``` ```yaml # .github/workflows/test.yml name: Tests on: [push, pull_request] jobs: test: runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: jdx/mise-action@v2 - run: tuist auth login # OIDC - run: tuist install # resolve packages - run: tuist test # selective run ``` --- ## Previews — `tuist share` / `tuist run` Previews build and upload an app to Tuist's cloud and produce a shareable link. Recipients run the preview on a simulator or device without building locally. ```bash # Build and share a simulator build from a generated project tuist generate App tuist xcodebuild build -scheme App -workspace App.xcworkspace \ -configuration Debug -sdk iphonesimulator tuist share App # Share a release build with an explicit track tuist share App --configuration Release --track beta # Share an existing .ipa directly (for device installs) tuist share App.ipa # Get JSON output for custom CI automations (Slack bots, etc.) tuist share App --json # Output: {"id": 1234567890, "url": "https://cloud.tuist.io/preview/1234567890", "qrCodeURL": "..."} # Run a preview URL (simulator or device) tuist run https://cloud.tuist.io/preview/1234567890 tuist run --device "My iPhone" https://cloud.tuist.io/preview/1234567890 # Run by specifier tuist run App@latest # latest on default branch tuist run App@my-feature-branch # latest on specific branch tuist run App@00dde7f56b1b8795a26b80 # specific git SHA ``` ```swift // In-app preview update monitoring via Tuist SDK // Package.swift dependency: // .package(url: "https://github.com/tuist/sdk", .upToNextMajor(from: "0.1.0")) import TuistSDK @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() .task { let sdk = TuistSDK(fullHandle: "my-org/my-app", apiKey: "your-api-key") sdk.monitorPreviewUpdates() } } } } // Manual single check let sdk = TuistSDK(fullHandle: "my-org/my-app", apiKey: "your-api-key") if let preview = try await sdk.checkForUpdate() { print("New version available: \(preview.version ?? "unknown")") } ``` --- ## Build insights — `tuist inspect build` `tuist inspect build` reads Xcode result bundles after a build and uploads timing, settings, and machine metrics to the Tuist dashboard for trend analysis. ```bash # One-time: start the machine metrics daemon (samples CPU/memory/disk/network) tuist setup insights # In an Xcode scheme post-action (via Mise): $HOME/.local/bin/mise x -C $SRCROOT -- tuist inspect build # In an Xcode scheme post-action (via Homebrew): export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" tuist inspect build # CI: use tuist xcodebuild with -resultBundlePath tuist xcodebuild build -scheme App -workspace App.xcworkspace \ -resultBundlePath build/App.xcresult tuist inspect build # picks up the result bundle automatically # Attach metadata for dashboard filtering export TUIST_BUILD_TAGS="nightly,release-candidate" export TUIST_BUILD_VALUE_TICKET="PROJ-1234" export TUIST_BUILD_VALUE_PR_URL="https://github.com/my-org/my-app/pull/99" tuist inspect build ``` --- ## Templates and scaffolding — `tuist scaffold` Templates are Swift manifest files that generate boilerplate code for new features, modules, or projects. ```swift // Tuist/Templates/feature/feature.swift import ProjectDescription let nameAttribute: Template.Attribute = .required("name") let template = Template( description: "VIPER feature module", attributes: [ nameAttribute, .optional("platform", default: "ios"), ], items: [ .string( path: "Modules/\(nameAttribute)/Project.swift", contents: """ import ProjectDescription import ProjectDescriptionHelpers let project = Project.featureFramework(name: "\(nameAttribute)") """ ), .file( path: "Modules/\(nameAttribute)/Sources/\(nameAttribute)Presenter.swift", templatePath: "presenter.stencil" ), .directory( path: "Modules/\(nameAttribute)/Resources", sourcePath: "DefaultResources" ), ] ) ``` ```bash # Scaffold a new feature tuist scaffold feature --name Login --platform ios # Scaffold with only required arguments tuist scaffold feature --name Settings ``` --- ## Project description helpers and plugins Project description helpers are Swift files under `Tuist/ProjectDescriptionHelpers/` compiled into a `ProjectDescriptionHelpers` module importable from any manifest. Plugins package helpers, templates, and resource synthesizer templates for reuse across repositories. ```swift // Tuist/ProjectDescriptionHelpers/Project+Templates.swift import ProjectDescription public extension Project { static func featureFramework( name: String, dependencies: [TargetDependency] = [] ) -> Project { Project( name: name, targets: [ .target( name: name, destinations: .iOS, product: .framework, bundleId: "com.example.\(name)", sources: ["Sources/\(name)/**"], resources: ["Resources/\(name)/**"], dependencies: dependencies ), .target( name: "\(name)Tests", destinations: .iOS, product: .unitTests, bundleId: "com.example.\(name)Tests", sources: ["Tests/\(name)Tests/**"], dependencies: [.target(name: name), .xctest] ), ] ) } } ``` ```swift // Modules/Search/Project.swift import ProjectDescription import ProjectDescriptionHelpers let project = Project.featureFramework( name: "Search", dependencies: [.external(name: "Alamofire")] ) ``` ```swift // Tuist.swift — referencing an external plugin from Git import ProjectDescription let tuist = Tuist( project: .tuist( plugins: [ .local(path: "/Plugins/MyPlugin"), .git(url: "https://github.com/my-org/tuist-plugin.git", tag: "2.0.0"), ] ) ) ``` --- ## Schemes — `Scheme` `Scheme` defines a named collection of build, test, run, archive, profile, and analyze actions. Tuist auto-generates default schemes per target; custom schemes give full control over arguments, environment, and code coverage. ```swift import ProjectDescription let integrationScheme = Scheme.scheme( name: "App-Integration", shared: true, buildAction: .buildAction( targets: ["App"], preActions: [ .executionAction(title: "Run SwiftGen", scriptText: "swiftgen config run", target: "App"), ] ), testAction: .targets( ["AppTests", "IntegrationTests"], arguments: .arguments( environmentVariables: [ "BASE_URL": .init(value: "https://staging.example.com", isEnabled: true), ], launchArguments: [ .launchArgument(name: "-UITesting", isEnabled: true), ] ), options: .options( coverage: true, codeCoverageTargets: ["App", "AppKit"] ) ), runAction: .runAction( executable: "App", arguments: .arguments( launchArguments: [.launchArgument(name: "-ForceDarkMode", isEnabled: false)] ) ), archiveAction: .archiveAction( configuration: "AppStore", revealArchiveInOrganizer: true ) ) ``` --- ## CLI installation and authentication ```bash # Install via Mise (recommended) mise x tuist@latest -- tuist init # Install via Homebrew brew install --cask tuist/tuist/tuist # Initialize a new project tuist init # Authenticate with Tuist cloud (OIDC for CI, token for scripts) tuist auth login export TUIST_TOKEN=your-token # for headless/CI # Core workflow tuist install # resolve & fetch external packages tuist generate # generate Xcode project/workspace tuist generate App # focus mode: only App + its source dependencies tuist build # generate + xcodebuild build tuist test # selective test run tuist cache # warm module cache # Access the dashboard tuist project show --web ``` --- Tuist's primary use cases revolve around large-scale Apple platform codebases that need reliable, reproducible builds. Teams adopt it to eliminate `.pbxproj` merge conflicts by never committing generated Xcode projects, to modularize their codebase using an explicit and validated dependency graph, and to dramatically cut build times by combining the module cache and the Xcode 26+ compilation cache. The combination of `tuist generate`, `tuist cache`, and `tuist test` forms a complete CI loop: warm the cache on every `main` commit, use it to generate incremental developer and CI workspaces, and run only the test targets that were actually affected by each change. Integration patterns fall into two categories. In the **generated-project pattern**, all Xcode project files are gitignored, developers run `tuist generate` after cloning or switching branches, and CI runs `tuist install && tuist test`. The `ProjectDescriptionHelpers` module acts as the team's shared platform language, enforcing naming conventions, target structure, and dependency rules across every project in the monorepo. In the **standalone-project pattern**, teams with existing Xcode projects use only the cloud features (Xcode cache, build insights, previews, selective testing) without project generation. Here the integration points are the `tuist xcodebuild` wrapper, `tuist inspect build` in scheme post-actions, and `tuist share` in CI workflows to distribute builds to reviewers automatically in pull request comments.