RFC: Selective Warnings as Errors in tuist generate
Summary
Allow project maintainers to promote specific generation warnings to errors via GenerationOptions in Tuist.swift, so that tuist generate fails immediately when those warnings are detected.
Motivation
Today, tuist generate emits warnings at the end of generation that are easy to miss. For example, the “outdated dependencies” warning tells the user to run tuist install, but since generation succeeds, developers often overlook it and end up debugging subtle breakages caused by stale dependencies.
A real-world example from a Tuist adopter:
I missed the outdated dependencies warning, then had a subtle breakage that took me a moderate amount of time to track down. It would be better if we could just fail immediately.
There was a prior discussion about adding a --strict flag that would treat all warnings as errors, but that approach is too blunt — teams may not be able to address every warning immediately (e.g., static side effects warnings during a migration period). What’s needed is a way to selectively promote specific warnings to errors, similar to Swift 6’s warnings-as-errors model.
Importantly, this should be a manifest-level configuration (in GenerationOptions), not a CLI flag, so that platform teams can enforce policy for all developers rather than relying on each individual to pass the right flags.
Prior Art
- Swift compiler:
-warnings-as-errorsflag and, since Swift 6, selective#warning/-Werror=<category>support. - ESLint: Rules can be individually set to
"off","warn", or"error". - Gradle: Specific deprecation warnings can be promoted to errors via configuration.
- Bazel:
--check_direct_dependencies=errorpromotes a specific warning to an error.
All of these share the same pattern: a default severity that can be overridden per-category.
Proposed Solution
New GenerationOptions Parameter
Add a new warningsAsErrors parameter to Tuist.GenerationOptions in Tuist.swift:
// ProjectDescription
extension Tuist.GenerationOptions {
public enum WarningsAsErrors: Codable, Equatable, Sendable {
/// No warnings are promoted to errors (default, current behavior).
case none
/// All generation warnings are treated as errors.
case all
/// Only the specified warning categories are treated as errors.
case only(Set<GenerationWarning>)
}
}
Warning Categories
Define an enum of known warning categories:
// ProjectDescription
public enum GenerationWarning: String, Codable, Equatable, Hashable, Sendable {
/// Dependencies are outdated compared to the resolved package graph.
case outdatedDependencies
/// A static product is linked from multiple targets, risking side effects.
case staticSideEffects
/// A scheme references targets that cannot be found.
case schemeTargetNotFound
/// Project configurations are mismatched across the graph.
case mismatchedConfigurations
/// Duplicate product names exist among dependencies.
case duplicateProductNames
}
Usage
// Tuist.swift
let tuist = Tuist(
project: .tuist(
generationOptions: .options(
warningsAsErrors: .only([.outdatedDependencies])
)
)
)
Or to enforce all warnings as errors:
let tuist = Tuist(
project: .tuist(
generationOptions: .options(
warningsAsErrors: .all
)
)
)
Behavior
When warningsAsErrors is configured and a matching warning is triggered:
- The warning is displayed as an error (red, not yellow).
- Generation fails with a non-zero exit code.
- The error message includes the same content as the warning, plus guidance on how to resolve it.
When a warning is not in the promoted set, the current behavior is preserved — the warning is printed at the end and generation succeeds.
Implementation Sketch
The core change is in the linting pipeline. Currently, LintingIssue has a fixed severity set at creation time. The proposed approach:
- After collecting all linting issues in
Generator, check thewarningsAsErrorsconfiguration. - For each
.warning-severity issue that matches a promoted category, elevate it to.error. - The existing error-handling path (which already throws
LintingErrorfor.errorissues) takes care of the rest.
This requires adding a category: GenerationWarning? field to LintingIssue so that warnings can be identified by type rather than by string matching on the reason.
Key files to modify:
Sources/ProjectDescription/ConfigGenerationOptions.swift— addwarningsAsErrorsoptionSources/TuistCore/Models/LintingIssue.swift— add optionalcategoryfieldSources/TuistKit/Generator/Generator.swift— elevate matching warnings before printingSources/TuistGenerator/Linter/GraphLinter.swift— tag warnings with categoriesSources/TuistGenerator/Linter/StaticProductsGraphLinter.swift— tag warningsSources/TuistLoader/Loaders/SwiftPackageManagerGraphLoader.swift— tag outdated dependencies warning
Relationship to staticSideEffectsWarningTargets
The existing staticSideEffectsWarningTargets option controls which targets trigger static side effects warnings. The new warningsAsErrors option is orthogonal — it controls what happens when a warning is triggered. Both can coexist:
.options(
staticSideEffectsWarningTargets: .excluding(["LegacyModule"]),
warningsAsErrors: .only([.staticSideEffects, .outdatedDependencies])
)
This means: “Don’t warn about LegacyModule’s static side effects, but if any other target triggers the warning, fail the build.”
Alternatives Considered
1. --strict CLI flag
Treats all warnings as errors. Too blunt — teams in migration periods can’t suppress specific warnings. Also requires each developer to remember to pass the flag, making it unsuitable for enforcing team policy.
2. Per-warning suppression instead of promotion
An .ignoreWarnings([...]) option that suppresses specific warnings. This is the inverse of the proposal and could complement it, but doesn’t solve the core problem of making critical warnings enforceable. Could be added later.
3. CLI flag instead of manifest option
A --warnings-as-errors flag on tuist generate. This puts the burden on individual developers and doesn’t allow platform teams to enforce policy. The manifest-level approach is preferred because it’s checked into source control and applies to everyone.
Open Questions
- Should we also support
--warnings-as-errorsas a CLI flag? It could be useful for CI environments that want stricter checks than local development. The manifest option could serve as the baseline, with the CLI flag providing an override. I’m leaning to no for now and we can add it if this becomes a need.