Hive Hive
Sign in

fix(cli): re-run foreign build script when inputs cannot be tracked

GitHub issue · Closed

Metadata
Source
tuist/tuist #10872
Updated
Jun 24, 2026
Domains
CLI Generated projects
Details

Purpose

Reported in the Tuist Community Slack: a user wired up foreignBuild() for a KMP module, but editing the Kotlin sources didn’t rebuild the framework — the iOS app kept linking the stale xcframework.

The root cause is a silent failure in input expansion. When a .glob or .folder input resolves to zero files at generation time (most commonly because .relativeToRoot("../...") aims at the wrong path), the Xcode shell script build phase is written with an empty inputPaths. Combined with an existing outputPaths xcframework, Xcode’s dependency analysis decides the script is up-to-date forever — it runs once on the first build, then never again. Source changes on the non-Xcode side are dropped on the floor without any feedback.

Reproduced statically by inspecting the generated project.pbxproj against a fixture whose .glob aims at a non-existent path:

inputPaths = (
);
outputPaths = (
../../../../myAwesomeKmpProject/build/SharedKMP.xcframework,
);

No alwaysOutOfDate, no warning, no way for the user to notice.

Fix

  • Warn when a .glob input matches zero files (in the manifest mapper).
  • Warn when a .folder input expands to no files (in ForeignBuildGraphMapper).
  • When the final inputPaths for a foreign build script is empty, set basedOnDependencyAnalysis = false, which the build-phase generator translates to alwaysOutOfDate = 1 in the pbxproj. The script will then re-run on every build instead of caching the stale output indefinitely.
  • Document the failure mode on ForeignBuild.Input so it’s discoverable from autocomplete.

When inputs do resolve correctly, behavior is unchanged: inputPaths is populated, alwaysOutOfDate is not set, and Xcode does its usual incremental analysis.

How to test locally

  1. Set up a fixture where the foreign build’s .glob / .folder input resolves to no files (e.g. a wrong .. count in .relativeToRoot):
.foreignBuild(
name: "SharedKMP",
destinations: .iOS,
script: "...",
inputs: [.glob(.relativeToRoot("does-not-exist/**/*.kt"))],
output: .xcframework(path: ..., linking: .dynamic)
)
  1. tuist generate — observe the warning:
Foreign build glob input '.../does-not-exist/**/*.kt' matched no files. ...
  1. Inspect the generated project.pbxproj and confirm the script phase has alwaysOutOfDate = 1.

  2. Build, then build again without changing anything — the script should re-run (whereas before this fix it would have been silently skipped).

  3. Run ForeignBuildGraphMapperTests for unit coverage.

Comments
T
tuist[bot] May 19, 2026

🛠️ Tuist Run Report 🛠️

Tests 🧪
Scheme Status Cache hit rate Tests Skipped Ran Commit
TuistAcceptanceTests 0 % 0 0 0 a03dd3ec4
TuistUnitTests 43 % 2863 4 2859 a03dd3ec4
Flaky Tests ⚠️
  • TuistUnitTests: 1 flaky test (View all)
Test case Module Suite
parseTestStatuses_returnsCorrectStatuses() TuistXCResultServiceTests XCResultServiceTests
Builds 🔨
Scheme Status Duration Commit
TuistAcceptanceTests 3m 48s a03dd3ec4
TuistUnitTests 4m 22s a03dd3ec4