Skip to content

Commit 33e8c2f

Browse files
authored
Merge pull request #78204 from hamishknight/buildable-folder-cleanup
[xcodegen] Clean up buildable folder checking a bit
2 parents ebc98f4 + 14aec1b commit 33e8c2f

File tree

2 files changed

+60
-22
lines changed

2 files changed

+60
-22
lines changed

utils/swift-xcodegen/Sources/SwiftXcodeGen/Generator/ProjectGenerator.swift

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ fileprivate final class ProjectGenerator {
228228
}
229229
guard buildableFolder != nil ||
230230
group(for: repoRelativePath.appending(parentPath)) != nil else {
231+
// If this isn't a child of an explicitly added reference, something
232+
// has probably gone wrong.
233+
if !spec.referencesToAdd.contains(where: { parentPath.hasPrefix($0.path) }) {
234+
log.warning("""
235+
Target '\(name)' at '\(repoRelativePath.appending(parentPath))' is \
236+
nested in a folder reference; skipping. This is likely an xcodegen bug.
237+
""")
238+
}
231239
return nil
232240
}
233241
}
@@ -275,6 +283,50 @@ fileprivate final class ProjectGenerator {
275283
}
276284
}
277285

286+
/// Checks whether a target can be represented using a buildable folder.
287+
func canUseBuildableFolder(
288+
at parentPath: RelativePath, sources: [RelativePath]
289+
) throws -> Bool {
290+
// To use a buildable folder, all child sources need to be accounted for
291+
// in the target. If we have any stray sources not part of the target,
292+
// attempting to use a buildable folder would incorrectly include them.
293+
// Additionally, special targets like "Unbuildables" have an empty parent
294+
// path, avoid buildable folders for them.
295+
guard spec.useBuildableFolders, !parentPath.isEmpty else { return false }
296+
let sources = Set(sources)
297+
return try getAllRepoSubpaths(of: parentPath)
298+
.allSatisfy { !$0.isSourceLike || sources.contains($0) }
299+
}
300+
301+
/// Checks whether a given Clang target can be represented using a buildable
302+
/// folder.
303+
func canUseBuildableFolder(for clangTarget: ClangTarget) throws -> Bool {
304+
// In addition to the standard checking, we also must not have any
305+
// unbuildable sources or sources with unique arguments.
306+
// TODO: To improve the coverage of buildable folders, we ought to start
307+
// automatically splitting umbrella Clang targets like 'stdlib', since
308+
// they currently always have files with unique args.
309+
guard spec.useBuildableFolders, clangTarget.unbuildableSources.isEmpty else {
310+
return false
311+
}
312+
let parent = clangTarget.parentPath
313+
let sources = clangTarget.sources.map(\.path)
314+
let hasConsistentArgs = try sources.allSatisfy {
315+
try !buildDir.clangArgs.hasUniqueArgs(for: $0, parent: parent)
316+
}
317+
guard hasConsistentArgs else { return false }
318+
return try canUseBuildableFolder(at: parent, sources: sources)
319+
}
320+
321+
func canUseBuildableFolder(
322+
for buildRule: SwiftTarget.BuildRule
323+
) throws -> Bool {
324+
guard let parentPath = buildRule.parentPath else { return false }
325+
return try canUseBuildableFolder(
326+
at: parentPath, sources: buildRule.sources.repoSources
327+
)
328+
}
329+
278330
func generateClangTarget(
279331
_ targetInfo: ClangTarget, includeInAllTarget: Bool = true
280332
) throws {
@@ -303,20 +355,10 @@ fileprivate final class ProjectGenerator {
303355
}
304356
return
305357
}
306-
// Can only use buildable folders if there are no unique arguments and no
307-
// unbuildable sources.
308-
// TODO: To improve the coverage of buildable folders, we ought to start
309-
// automatically splitting umbrella Clang targets like 'stdlib', since
310-
// they always have files with unique args.
311-
let canUseBuildableFolders =
312-
try spec.useBuildableFolders && targetInfo.unbuildableSources.isEmpty &&
313-
targetInfo.sources.allSatisfy {
314-
try !buildDir.clangArgs.hasUniqueArgs(for: $0.path, parent: targetPath)
315-
}
316-
317358
let target = generateBaseTarget(
318359
targetInfo.name, at: targetPath,
319-
canUseBuildableFolder: canUseBuildableFolders, productType: .staticArchive,
360+
canUseBuildableFolder: try canUseBuildableFolder(for: targetInfo),
361+
productType: .staticArchive,
320362
includeInAllTarget: includeInAllTarget
321363
)
322364
guard let target else { return }
@@ -531,19 +573,11 @@ fileprivate final class ProjectGenerator {
531573
guard checkNotExcluded(buildRule.parentPath, for: "Swift target") else {
532574
return nil
533575
}
534-
// Swift targets can almost always use buildable folders since they have
535-
// a consistent set of arguments, but we need to ensure we don't have any
536-
// child source files that aren't part of the target.
537-
let canUseBuildableFolder = try {
538-
guard let parent = buildRule.parentPath else { return false }
539-
let repoSources = Set(buildRule.sources.repoSources)
540-
return try getAllRepoSubpaths(of: parent)
541-
.allSatisfy { !$0.isSourceLike || repoSources.contains($0) }
542-
}()
543576
// Create the target.
544577
let target = generateBaseTarget(
545578
targetInfo.name, at: buildRule.parentPath,
546-
canUseBuildableFolder: canUseBuildableFolder, productType: .staticArchive,
579+
canUseBuildableFolder: try canUseBuildableFolder(for: buildRule),
580+
productType: .staticArchive,
547581
includeInAllTarget: includeInAllTarget
548582
)
549583
guard let target else { return nil }

utils/swift-xcodegen/Sources/SwiftXcodeGen/Path/PathProtocol.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public extension PathProtocol {
3838
storage.lastComponent?.string ?? ""
3939
}
4040

41+
var isEmpty: Bool {
42+
storage.isEmpty
43+
}
44+
4145
func appending(_ relPath: RelativePath) -> Self {
4246
Self(storage.pushing(relPath.storage))
4347
}

0 commit comments

Comments
 (0)