@@ -228,6 +228,14 @@ fileprivate final class ProjectGenerator {
228
228
}
229
229
guard buildableFolder != nil ||
230
230
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
+ }
231
239
return nil
232
240
}
233
241
}
@@ -275,6 +283,50 @@ fileprivate final class ProjectGenerator {
275
283
}
276
284
}
277
285
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
+
278
330
func generateClangTarget(
279
331
_ targetInfo: ClangTarget , includeInAllTarget: Bool = true
280
332
) throws {
@@ -303,20 +355,10 @@ fileprivate final class ProjectGenerator {
303
355
}
304
356
return
305
357
}
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
-
317
358
let target = generateBaseTarget (
318
359
targetInfo. name, at: targetPath,
319
- canUseBuildableFolder: canUseBuildableFolders, productType: . staticArchive,
360
+ canUseBuildableFolder: try canUseBuildableFolder ( for: targetInfo) ,
361
+ productType: . staticArchive,
320
362
includeInAllTarget: includeInAllTarget
321
363
)
322
364
guard let target else { return }
@@ -531,19 +573,11 @@ fileprivate final class ProjectGenerator {
531
573
guard checkNotExcluded ( buildRule. parentPath, for: " Swift target " ) else {
532
574
return nil
533
575
}
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
- } ( )
543
576
// Create the target.
544
577
let target = generateBaseTarget (
545
578
targetInfo. name, at: buildRule. parentPath,
546
- canUseBuildableFolder: canUseBuildableFolder, productType: . staticArchive,
579
+ canUseBuildableFolder: try canUseBuildableFolder ( for: buildRule) ,
580
+ productType: . staticArchive,
547
581
includeInAllTarget: includeInAllTarget
548
582
)
549
583
guard let target else { return nil }
0 commit comments