Skip to main content
Tuist generates Xcode projects from Swift manifest files, eliminating manual .pbxproj maintenance and enabling team-wide consistency.

Why Generated Projects?

Manually maintaining Xcode project files creates several challenges:

Merge Conflicts

.pbxproj files are XML-based and conflict-prone when multiple developers modify projects

Inconsistency

Different developers may configure settings differently, leading to build issues

Hard to Review

Changes to .pbxproj are difficult to review in pull requests

Configuration Drift

Project settings can drift over time without clear documentation
Tuist solves these problems by treating manifests as the single source of truth and generating projects on-demand.

Generation Flow

When you run tuist generate, here’s what happens:
1

Manifest Discovery

Tuist searches for manifest files starting from the current directory:
# Directory structure
/MyWorkspace
├── Tuist.swift              # Global config (optional)
├── Workspace.swift          # Workspace manifest (optional)
├── App/
   └── Project.swift        # Project manifest
└── Framework/
    └── Project.swift        # Another project
The CLI walks up the directory tree to find configuration and workspace definitions.
2

Manifest Loading

Each manifest is:
  1. Compiled as a Swift executable
  2. Executed in a sandboxed environment
  3. Decoded from JSON output
// Project.swift is compiled and executed
import ProjectDescription

let project = Project(
    name: "MyApp",
    targets: [
        .target(
            name: "App",
            destinations: .iOS,
            product: .app,
            bundleId: "com.example.app",
            sources: ["Sources/**"],
            dependencies: [
                .project(target: "Framework", path: "../Framework")
            ]
        )
    ]
)
3

Graph Construction

Tuist builds a complete dependency graph:
  • Resolves all target dependencies
  • Validates no circular dependencies exist
  • Determines correct build order
  • Links external packages
Use tuist graph to visualize your project’s dependency graph
4

Project Generation

The TuistGenerator module creates Xcode project files:
  • Writes .xcodeproj package structure
  • Generates project.pbxproj with all targets
  • Creates schemes for each target
  • Applies build settings and configurations
5

Scheme Generation

Schemes are automatically generated or customized:
  • Default scheme per target
  • Custom schemes from manifest
  • Build actions and test targets
  • Run configurations

What Gets Generated?

A complete .xcodeproj package containing:
MyApp.xcodeproj/
├── project.pbxproj          # Main project file
└── xcshareddata/
    └── xcschemes/
        └── MyApp.xcscheme   # Build schemes
Generated elements:
  • All targets (apps, frameworks, tests)
  • Build phases (sources, resources, scripts)
  • Build configurations (Debug, Release)
  • Build settings
  • File references
  • Groups and folder structure

Generation Options

Customize generation behavior in Tuist.swift:
import ProjectDescription

let tuist = Tuist(
    project: .tuist(
        generationOptions: .options(
            // Xcode 15+ only
            xcodeProjectName: "MyCustomProject",
            
            // Don't open Xcode after generation
            autogeneratedWorkspaceSchemes: .disabled,
            
            // Disable sandboxing for manifest execution
            disableSandbox: false
        )
    )
)

Project Options

Per-project generation settings in Project.swift:
let project = Project(
    name: "MyApp",
    options: .options(
        // Disable automatic scheme generation
        automaticSchemesOptions: .disabled,
        
        // Custom text settings
        textSettings: .textSettings(
            usesTabs: false,
            indentWidth: 4,
            tabWidth: 4
        ),
        
        // Development region
        developmentRegion: "en",
        
        // Xcode version compatibility
        xcodeProjectName: "CustomName"
    ),
    targets: [...]
)

Caching Generated Projects

Tuist caches generation results to speed up subsequent runs:
How it works:
  • Tuist hashes manifest content and dependencies
  • Caches compiled manifest executables
  • Reuses cached results when manifests haven’t changed
# First run: compiles manifests
tuist generate  # ~3s

# Subsequent runs: uses cache
tuist generate  # ~0.5s
Cache location:
~/.tuist/Cache/Manifests/

Regeneration Workflow

1

Make Changes

Edit your manifest files:
Project.swift
// Add a new target
.target(
    name: "Analytics",
    destinations: .iOS,
    product: .framework,
    bundleId: "com.example.analytics"
)
2

Regenerate

Run Tuist generate:
tuist generate
Or use the --no-open flag to skip opening Xcode:
tuist generate --no-open
3

Xcode Updates

Xcode automatically detects changes and reloads the project.
Don’t manually edit .xcodeproj files - your changes will be lost on next generation!

Best Practices

Add generated files to .gitignore:
.gitignore
# Tuist generated files
*.xcodeproj
*.xcworkspace
Derived/

# Keep manifests
!*.swift
This ensures:
  • No merge conflicts in project files
  • Smaller repository size
  • Team members generate locally
Create reusable manifest helpers:
Tuist/ProjectDescriptionHelpers/Target+Templates.swift
import ProjectDescription

public extension Target {
    static func feature(
        name: String,
        dependencies: [TargetDependency] = []
    ) -> Target {
        return .target(
            name: name,
            destinations: .iOS,
            product: .framework,
            bundleId: "com.example.\(name.lowercased())",
            sources: ["\(name)/Sources/**"],
            resources: ["\(name)/Resources/**"],
            dependencies: dependencies
        )
    }
}
Project.swift
import ProjectDescription
import ProjectDescriptionHelpers

let project = Project(
    name: "MyApp",
    targets: [
        .feature(name: "Profile"),
        .feature(name: "Settings"),
        .feature(name: "Analytics")
    ]
)
Add a pre-commit hook to validate manifests:
.git/hooks/pre-commit
#!/bin/bash

# Validate Tuist manifests
if ! tuist generate --no-open; then
    echo "❌ Tuist generation failed"
    exit 1
fi

echo "✅ Tuist generation successful"
Add a README explaining your project structure:
README.md
# MyApp

## Project Structure
- `App/` - Main application target
- `Features/` - Feature modules
- `Core/` - Shared utilities

## Building
```bash
tuist generate
xcodebuild -workspace MyApp.xcworkspace -scheme MyApp

Troubleshooting

Symptoms: tuist generate exits with an errorSolutions:
  1. Check manifest syntax:
    tuist edit
    
  2. Clear cache:
    tuist clean
    tuist generate
    
  3. Enable verbose logging:
    tuist generate --verbose
    
Symptoms: Generation takes longer than expectedSolutions:
  1. Check manifest cache:
    ls -lh ~/.tuist/Cache/Manifests/
    
  2. Profile generation:
    time tuist generate
    
  3. Simplify complex globs:
    // Avoid deep recursion
    sources: ["Sources/**/*.swift"]
    
    // Prefer specific patterns
    sources: ["Sources/*.swift", "Sources/Models/*.swift"]
    

Next Steps

Manifests

Learn about Project.swift and manifest files

Workspaces

Organize multiple projects in workspaces

Architecture

Understand Tuist’s internal architecture

Settings

Configure build settings and options