Skip to content

Commit a81ef7d

Browse files
authored
Fix task ordering where stub executor does not depend on debug dylib link task (rdar://171933514) (#1330)
Long standing race bug where the stub executor link task is not properly ordered after the debug dylib link task. That means we've been getting lucky to have them scheduled in the right order most of the time. The fix makes the stub executor depend on the lipo'd debug dylib. Added single arch and multi-arch tests to verify the ordering invariant. Without the fix, the tests fail with this assertion: task 'Ld .../AppTarget normal' has no edge forcing it to follow 'Ld .../AppTarget.debug.dylib normal' We've seen some intermittent code sign issues as well, so added extra assertions to validate that the code signing follows the debug dylib and stub executor link tasks, too.
1 parent bd6f740 commit a81ef7d

2 files changed

Lines changed: 178 additions & 1 deletion

File tree

Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1252,7 +1252,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
12521252
scope: scope,
12531253
inputs: linkerInputs,
12541254
output: output,
1255-
commandOrderingInputs: additionalLinkerOrderingInputs,
1255+
commandOrderingInputs: additionalLinkerOrderingInputs + [linkedBinaryPreviewDylibNode].compactMap { $0 },
12561256
commandOrderingOutputs: commandOrderingOutputs
12571257
),
12581258
delegate,

Tests/SWBTaskConstructionTests/PreviewsTaskConstructionTests.swift

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,4 +1222,181 @@ fileprivate struct PreviewsTaskConstructionTests: CoreBasedTests {
12221222
}
12231223
}
12241224

1225+
/// rdar://171933514 — The stub executor link task must be ordered after the debug.dylib link task.
1226+
/// Without this, llbuild can schedule them concurrently, causing "no such file or directory" for
1227+
/// the debug.dylib when the stub executor link starts before the debug.dylib link completes.
1228+
@Test(.requireSDKs(.iOS))
1229+
func previewsDylibStubExecutorDependsOnDebugDylib() async throws {
1230+
let core = try await getCore()
1231+
1232+
let testProject = try await TestProject(
1233+
"ProjectName",
1234+
groupTree: TestGroup(
1235+
"Sources", path: "Sources",
1236+
children: [
1237+
TestFile("File.swift"),
1238+
TestFile("Info.plist"),
1239+
]),
1240+
targets: [
1241+
TestStandardTarget(
1242+
"AppTarget",
1243+
type: .application,
1244+
buildConfigurations: [
1245+
TestBuildConfiguration("Debug", buildSettings: [
1246+
"SDKROOT": "iphoneos",
1247+
"PRODUCT_NAME": "$(TARGET_NAME)",
1248+
"SWIFT_OPTIMIZATION_LEVEL": "-Onone",
1249+
"SWIFT_EXEC": swiftCompilerPath.str,
1250+
"SWIFT_VERSION": swiftVersion,
1251+
"ENABLE_PREVIEWS": "NO",
1252+
"ENABLE_XOJIT_PREVIEWS": "YES",
1253+
"ENABLE_DEBUG_DYLIB": "YES",
1254+
"INFOPLIST_FILE": "Sources/Info.plist",
1255+
"CODE_SIGN_IDENTITY": "-",
1256+
]),
1257+
],
1258+
buildPhases: [
1259+
TestSourcesBuildPhase([
1260+
TestBuildFile("File.swift"),
1261+
]),
1262+
])
1263+
])
1264+
let tester = try TaskConstructionTester(core, testProject)
1265+
1266+
let fs = PseudoFS()
1267+
try fs.writeSimulatedPreviewsJITStubExecutorLibraries(sdk: core.loadSDK(.iOSSimulator))
1268+
1269+
await tester.checkBuild(runDestination: .iOSSimulator, fs: fs) { results in
1270+
results.checkNoDiagnostics()
1271+
1272+
let ldDebugDylib = results.checkTask(.matchRuleType("Ld"), .matchRuleItemPattern(.suffix(".debug.dylib"))) { $0 }
1273+
let ldPreviewDylib = results.checkTask(.matchRuleType("Ld"), .matchRuleItemPattern(.suffix("__preview.dylib"))) { $0 }
1274+
let ldStubExecutor = results.checkTask(.matchRuleType("Ld"), .matchRuleItemPattern(.suffix("/AppTarget"))) { $0 }
1275+
1276+
let signDebugDylib = results.checkTask(.matchRuleType("CodeSign"), .matchRuleItemPattern(.suffix(".debug.dylib"))) { $0 }
1277+
let signPreviewDylib = results.checkTask(.matchRuleType("CodeSign"), .matchRuleItemPattern(.suffix("__preview.dylib"))) { $0 }
1278+
let signApp = results.checkTask(.matchRuleType("CodeSign"), .matchRuleItemPattern(.suffix(".app"))) { $0 }
1279+
1280+
// Stub executor must wait for debug.dylib to be linked
1281+
if let ldStubExecutor, let ldDebugDylib {
1282+
results.checkTaskFollows(ldStubExecutor, antecedent: ldDebugDylib)
1283+
}
1284+
1285+
// CodeSign must wait for all three linking tasks
1286+
if let ldDebugDylib, let signDebugDylib {
1287+
results.checkTaskFollows(signDebugDylib, antecedent: ldDebugDylib)
1288+
}
1289+
if let ldPreviewDylib, let signPreviewDylib {
1290+
results.checkTaskFollows(signPreviewDylib, antecedent: ldPreviewDylib)
1291+
}
1292+
if let ldStubExecutor, let signApp {
1293+
results.checkTaskFollows(signApp, antecedent: ldStubExecutor)
1294+
}
1295+
if let ldDebugDylib, let signApp {
1296+
results.checkTaskFollows(signApp, antecedent: ldDebugDylib)
1297+
}
1298+
if let ldPreviewDylib, let signApp {
1299+
results.checkTaskFollows(signApp, antecedent: ldPreviewDylib)
1300+
}
1301+
}
1302+
}
1303+
1304+
/// rdar://171933514 — Same ordering invariant as above, but for multi-arch builds where
1305+
/// per-arch link tasks go through lipo. The per-arch stub executor link must be ordered
1306+
/// after the lipo'd debug.dylib.
1307+
@Test(.requireSDKs(.macOS))
1308+
func previewsDylibStubExecutorDependsOnDebugDylibMultiArch() async throws {
1309+
let core = try await getCore()
1310+
let archs = ["arm64", "x86_64"]
1311+
let archsJoined = archs.joined(separator: " ")
1312+
1313+
let testProject = try await TestProject(
1314+
"ProjectName",
1315+
groupTree: TestGroup(
1316+
"Sources", path: "Sources",
1317+
children: [
1318+
TestFile("File.swift"),
1319+
TestFile("Info.plist"),
1320+
TestFile("Entitlements.plist"),
1321+
]),
1322+
targets: [
1323+
TestStandardTarget(
1324+
"AppTarget",
1325+
type: .application,
1326+
buildConfigurations: [
1327+
TestBuildConfiguration("Debug", buildSettings: [
1328+
"PRODUCT_NAME": "$(TARGET_NAME)",
1329+
"SWIFT_OPTIMIZATION_LEVEL": "-Onone",
1330+
"SWIFT_EXEC": swiftCompilerPath.str,
1331+
"SWIFT_VERSION": swiftVersion,
1332+
"ARCHS": archsJoined,
1333+
"ENABLE_PREVIEWS": "NO",
1334+
"ENABLE_XOJIT_PREVIEWS": "YES",
1335+
"ENABLE_DEBUG_DYLIB": "YES",
1336+
"ENABLE_DEBUG_DYLIB_OVERRIDE": "YES",
1337+
"DEFINES_MODULE": "NO",
1338+
"INFOPLIST_FILE": "Sources/Info.plist",
1339+
"CODE_SIGN_ENTITLEMENTS": "Sources/Entitlements.plist",
1340+
"CODE_SIGN_IDENTITY": "-",
1341+
]),
1342+
],
1343+
buildPhases: [
1344+
TestSourcesBuildPhase([
1345+
TestBuildFile("File.swift"),
1346+
]),
1347+
])
1348+
])
1349+
let tester = try TaskConstructionTester(core, testProject)
1350+
1351+
await tester.checkBuild(runDestination: .anyMac, fs: localFS) { results in
1352+
results.checkNoDiagnostics()
1353+
1354+
var ldDebugDylibs: [any PlannedTask] = []
1355+
var ldPreviewDylibs: [any PlannedTask] = []
1356+
var ldStubExecutors: [any PlannedTask] = []
1357+
1358+
for arch in archs {
1359+
let ldDebugDylib = results.checkTask(.matchRuleType("Ld"), .matchRuleItemPattern(.suffix(".debug.dylib")), .matchRuleItem(arch)) { $0 }
1360+
let ldPreviewDylib = results.checkTask(.matchRuleType("Ld"), .matchRuleItemPattern(.suffix("__preview.dylib")), .matchRuleItem(arch)) { $0 }
1361+
let ldStubExecutor = results.checkTask(.matchRuleType("Ld"), .matchRuleItemPattern(.suffix("/AppTarget")), .matchRuleItem(arch)) { $0 }
1362+
1363+
// Stub executor must wait for lipo'd debug.dylib
1364+
if let ldStubExecutor, let ldDebugDylib {
1365+
results.checkTaskFollows(ldStubExecutor, antecedent: ldDebugDylib)
1366+
}
1367+
1368+
if let ldDebugDylib { ldDebugDylibs.append(ldDebugDylib) }
1369+
if let ldPreviewDylib { ldPreviewDylibs.append(ldPreviewDylib) }
1370+
if let ldStubExecutor { ldStubExecutors.append(ldStubExecutor) }
1371+
}
1372+
1373+
let signDebugDylib = results.checkTask(.matchRuleType("CodeSign"), .matchRuleItemPattern(.suffix(".debug.dylib"))) { $0 }
1374+
let signPreviewDylib = results.checkTask(.matchRuleType("CodeSign"), .matchRuleItemPattern(.suffix("__preview.dylib"))) { $0 }
1375+
let signApp = results.checkTask(.matchRuleType("CodeSign"), .matchRuleItemPattern(.suffix(".app"))) { $0 }
1376+
1377+
// CodeSign must wait for all linking tasks
1378+
for ldDebugDylib in ldDebugDylibs {
1379+
if let signDebugDylib {
1380+
results.checkTaskFollows(signDebugDylib, antecedent: ldDebugDylib)
1381+
}
1382+
if let signApp {
1383+
results.checkTaskFollows(signApp, antecedent: ldDebugDylib)
1384+
}
1385+
}
1386+
for ldPreviewDylib in ldPreviewDylibs {
1387+
if let signPreviewDylib {
1388+
results.checkTaskFollows(signPreviewDylib, antecedent: ldPreviewDylib)
1389+
}
1390+
if let signApp {
1391+
results.checkTaskFollows(signApp, antecedent: ldPreviewDylib)
1392+
}
1393+
}
1394+
for ldStubExecutor in ldStubExecutors {
1395+
if let signApp {
1396+
results.checkTaskFollows(signApp, antecedent: ldStubExecutor)
1397+
}
1398+
}
1399+
}
1400+
}
1401+
12251402
}

0 commit comments

Comments
 (0)