@@ -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 }
0 commit comments