Overview
A well-structured Tuist project improves maintainability, reduces build times, and makes collaboration easier. This guide covers best practices for organizing your projects, targets, and dependencies.
Understanding Tuist Directory Structure
Standard Project Layout
A typical Tuist project follows this structure:
MyProject/
├── Tuist/
│ ├── ProjectDescriptionHelpers/
│ │ └── Project+Templates.swift
│ └── Package.swift
├── Tuist.swift
├── Workspace.swift
├── Projects/
│ ├── App/
│ │ ├── Project.swift
│ │ ├── Sources/
│ │ ├── Resources/
│ │ └── Tests/
│ └── Features/
│ ├── FeatureA/
│ │ └── Project.swift
│ └── FeatureB/
│ └── Project.swift
└── xcconfigs/
├── Project.xcconfig
└── Targets/
Key Directories Explained
Tuist Directory
The Tuist/ directory serves two critical purposes:
Signals the project root : Allows running Tuist commands from any subdirectory
Contains shared configuration :
ProjectDescriptionHelpers/: Reusable Swift code for manifest files
Package.swift: External dependency definitions
Place the Tuist/ directory at your repository root. This allows you to run Tuist commands from anywhere within the project.
Root Directory Files
Tuist.swift : Project-wide configuration
import ProjectDescription
let tuist = Tuist (
fullHandle : "your-org/your-project" ,
project : . tuist (
generationOptions : . options (
enableCaching : true ,
disableSandbox : false
)
)
)
Workspace.swift : Groups multiple projects (optional)
import ProjectDescription
let workspace = Workspace (
name : "MyWorkspace" ,
projects : [
"Projects/**"
]
)
Project.swift : Defines individual project targets
import ProjectDescription
let project = Project (
name : "MyApp" ,
targets : [
// Target definitions
]
)
Xcode workspaces in Tuist are optional. Tuist auto-generates a workspace containing your project and its dependencies. Only use Workspace.swift if you need custom workspace configuration.
Organization Strategies
Single Project Structure
Best for small to medium apps with straightforward requirements:
MyApp/
├── Tuist/
│ └── Package.swift
├── Tuist.swift
├── Project.swift
├── Sources/
│ ├── App/
│ ├── Features/
│ └── Core/
├── Resources/
└── Tests/
When to use :
Single application target
Limited feature complexity
Small team (1-5 developers)
Multi-Project Structure
Best for larger applications with distinct feature modules:
MyApp/
├── Tuist/
├── Tuist.swift
├── Workspace.swift
├── Projects/
│ ├── App/
│ │ └── Project.swift
│ ├── FeatureA/
│ │ └── Project.swift
│ ├── FeatureB/
│ │ └── Project.swift
│ └── Core/
│ └── Project.swift
└── xcconfigs/
When to use :
Multiple feature teams
Need for clear module boundaries
Desire for parallel development
Teams of 5+ developers
Micro-Framework Architecture
Best for large-scale apps with strict separation of concerns:
MyApp/
├── Tuist/
├── Workspace.swift
├── Projects/
│ ├── App/
│ ├── Features/
│ │ ├── Authentication/
│ │ │ ├── Project.swift
│ │ │ ├── Sources/
│ │ │ ├── Tests/
│ │ │ └── Example/
│ │ ├── Dashboard/
│ │ └── Settings/
│ ├── Core/
│ │ ├── Networking/
│ │ ├── Database/
│ │ └── UI/
│ └── Platform/
│ ├── Analytics/
│ └── Logging/
When to use :
Very large applications
Multiple teams working independently
Need for module reusability
Desire for selective compilation and testing
Target Organization Best Practices
Target Types
Organize targets by their purpose:
Application Target
Framework Target
Test Target
. target (
name : "MyApp" ,
destinations : [. iPhone , . iPad ],
product : . app ,
bundleId : "com.example.myapp" ,
sources : [ "Sources/**" ],
resources : [ "Resources/**" ],
dependencies : [
. target ( name : "FeatureA" ),
. target ( name : "Core" )
]
)
Source File Organization
Using Buildable Folders (Recommended)
Tuist 4.62.0+ supports buildable folders (Xcode 16+), which automatically sync with the file system:
let target = Target (
name : "App" ,
buildableFolders : [
"App/Sources" ,
"App/Resources"
]
)
Benefits :
No regeneration needed when adding/removing files
AI-friendly (coding assistants can modify files freely)
Eliminates merge conflicts in project files
Simpler configuration
Buildable folders are recommended for all new projects. They provide a better developer experience and work seamlessly with AI coding tools.
Using Wildcard Patterns (Traditional)
For older Xcode versions or more control:
let target = Target (
name : "App" ,
sources : [ "App/Sources/**" ],
resources : [ "App/Resources/**" ]
)
Dependency Organization
Layered Architecture
Organize dependencies in layers:
// App layer - depends on features
App → Features
// Feature layer - depends on core
Features → Core
// Core layer - depends on external packages
Core → External Dependencies
Avoid circular dependencies. Tuist validates your dependency graph and will error on cycles.
Example Layered Structure
import ProjectDescription
import ProjectDescriptionHelpers
let project = Project (
name : "MyApp" ,
targets : [
// App Target - Top Layer
. target (
name : "MyApp" ,
product : . app ,
dependencies : [
. target ( name : "FeatureAuthentication" ),
. target ( name : "FeatureDashboard" )
]
),
// Feature Targets - Middle Layer
. target (
name : "FeatureAuthentication" ,
product : . framework ,
dependencies : [
. target ( name : "CoreNetworking" ),
. target ( name : "CoreUI" )
]
),
// Core Targets - Bottom Layer
. target (
name : "CoreNetworking" ,
product : . framework ,
dependencies : [
. external ( name : "Alamofire" )
]
),
. target (
name : "CoreUI" ,
product : . framework ,
dependencies : []
)
]
)
Build Settings Organization
Using XCConfig Files
Extract build settings to xcconfig files for better maintainability:
xcconfigs/
├── Project.xcconfig # Project-level settings
├── Targets/
│ ├── App.xcconfig # App target settings
│ ├── FeatureA.xcconfig # Feature settings
│ └── Tests.xcconfig # Test settings
└── Shared/
├── Debug.xcconfig # Debug configuration
└── Release.xcconfig # Release configuration
xcconfigs/Project.xcconfig
xcconfigs/Shared/Debug.xcconfig
xcconfigs/Shared/Release.xcconfig
// Project-level settings
IPHONEOS_DEPLOYMENT_TARGET = 15.0
SWIFT_VERSION = 5.9
MARKETING_VERSION = 1.0.0
Reference in your manifest:
let project = Project (
name : "MyApp" ,
settings : . settings ( configurations : [
. debug ( name : "Debug" , xcconfig : "./xcconfigs/Shared/Debug.xcconfig" ),
. release ( name : "Release" , xcconfig : "./xcconfigs/Shared/Release.xcconfig" )
]),
targets : [
. target (
name : "MyApp" ,
settings : . settings ( configurations : [
. debug ( name : "Debug" , xcconfig : "./xcconfigs/Targets/App.xcconfig" ),
. release ( name : "Release" , xcconfig : "./xcconfigs/Targets/App.xcconfig" )
])
)
]
)
Using ProjectDescriptionHelpers
Create reusable code to reduce duplication across manifests:
Tuist/ProjectDescriptionHelpers/Project+Templates.swift
import ProjectDescription
public extension Project {
static func feature (
name : String ,
dependencies : [TargetDependency] = []
) -> Project {
return Project (
name : name,
targets : [
. target (
name : name,
destinations : [. iPhone , . iPad ],
product : . framework ,
bundleId : "com.example. \( name. lowercased () ) " ,
sources : [ "Sources/**" ],
resources : [ "Resources/**" ],
dependencies : dependencies
),
. target (
name : " \( name ) Tests" ,
destinations : [. iPhone ],
product : . unitTests ,
bundleId : "com.example. \( name. lowercased () ) .tests" ,
sources : [ "Tests/**" ],
dependencies : [
. target ( name : name),
. xctest
]
)
]
)
}
}
Use in your manifests:
Projects/FeatureA/Project.swift
import ProjectDescription
import ProjectDescriptionHelpers
let project = Project. feature (
name : "FeatureA" ,
dependencies : [
. target ( name : "Core" )
]
)
Best Practices
Avoid Complex Build Configurations
Stick to standard Debug and Release configurations:
Avoid using build configurations to model remote environments (e.g., Debug-Production, Release-Staging). This leads to complexity and potential inconsistencies.
Instead :
Use environment variables at runtime for different environments
Use compiler directives to conditionally compile code
Keep configurations simple and consistent
// Good: Runtime environment switching
let environment: Environment = {
# if DEBUG
return ProcessInfo. processInfo . environment [ "API_ENV" ] == "production"
? . production
: . development
# else
return . production
# endif
}()
Validate Your Graph
Regularly check your dependency graph:
# Visualize the graph
tuist graph
# Check for issues
tuist generate
Keep Projects Focused
Each project should have a single, clear responsibility:
App : Composition and app-specific code
Features : User-facing functionality
Core : Shared utilities and infrastructure
Platform : Third-party integrations
Troubleshooting
Project Generation Failures
If tuist generate fails:
Check for circular dependencies: tuist graph
Validate manifest syntax
Ensure all referenced files exist
Check that target names are unique
Slow Build Times
If builds are slow:
Break large targets into smaller modules
Use static linking in release builds
Enable binary caching (see Build Optimization )
Review dependency graph depth
Merge Conflicts
If you still experience merge conflicts:
Use buildable folders instead of explicit file lists
Keep manifest files simple and focused
Use ProjectDescriptionHelpers for shared logic
Have each team work in separate feature directories
Next Steps
Manage Dependencies Learn how to handle external and internal dependencies
Optimize Builds Speed up compilation with caching and optimization techniques
Set Up CI/CD Configure continuous integration for your structured project
Migrate from Xcode Migrate an existing project to this structure