@@ -182,7 +182,6 @@ fileprivate final class ProjectGenerator {
182182 // group there.
183183 if ref. kind == . folder {
184184 guard groups [ path] == nil else {
185- log. warning ( " Skipping blue folder ' \( path) '; already added " )
186185 return nil
187186 }
188187 }
@@ -211,24 +210,35 @@ fileprivate final class ProjectGenerator {
211210 }
212211
213212 func generateBaseTarget(
214- _ name: String , at parentPath: RelativePath ? ,
213+ _ name: String , at parentPath: RelativePath ? , canUseBuildableFolder : Bool ,
215214 productType: Xcode . Target . ProductType ? , includeInAllTarget: Bool
216215 ) -> Xcode . Target ? {
217216 guard targets [ name] == nil else {
218217 log. warning ( " Duplicate target ' \( name) ', skipping " )
219218 return nil
220219 }
221- // Make sure we can create a group for the parent path, otherwise
222- // this is nested in a folder reference and there's nothing we can do.
223- if let parentPath, !parentPath. components. isEmpty,
224- group ( for: repoRelativePath. appending ( parentPath) ) == nil {
225- return nil
220+ var buildableFolder : Xcode . FileReference ?
221+ if let parentPath, !parentPath. components. isEmpty {
222+ // If we've been asked to use buildable folders, see if we can create
223+ // a folder reference at the parent path. Otherwise, create a group at
224+ // the parent path. If we can't create either a folder or group, this is
225+ // nested in a folder reference and there's nothing we can do.
226+ if spec. useBuildableFolders && canUseBuildableFolder {
227+ buildableFolder = getOrCreateRepoRef ( . folder( parentPath) )
228+ }
229+ guard buildableFolder != nil ||
230+ group ( for: repoRelativePath. appending ( parentPath) ) != nil else {
231+ return nil
232+ }
226233 }
227234 let target = project. addTarget ( productType: productType, name: name)
228235 targets [ name] = target
229236 if includeInAllTarget {
230237 allTarget. addDependency ( on: target)
231238 }
239+ if let buildableFolder {
240+ target. addBuildableFolder ( buildableFolder)
241+ }
232242 target. buildSettings. common. ONLY_ACTIVE_ARCH = " YES "
233243 target. buildSettings. common. USE_HEADERMAP = " NO "
234244 // The product name needs to be unique across every project we generate
@@ -274,8 +284,12 @@ fileprivate final class ProjectGenerator {
274284 }
275285 unbuildableSources += targetInfo. unbuildableSources
276286
277- for header in targetInfo. headers {
278- getOrCreateRepoRef ( . file( header) )
287+ // Need to defer the addition of headers since the target may want to use
288+ // a buildable folder.
289+ defer {
290+ for header in targetInfo. headers {
291+ getOrCreateRepoRef ( . file( header) )
292+ }
279293 }
280294
281295 // If we have no sources, we're done.
@@ -289,8 +303,20 @@ fileprivate final class ProjectGenerator {
289303 }
290304 return
291305 }
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+
292317 let target = generateBaseTarget (
293- targetInfo. name, at: targetInfo. parentPath, productType: . staticArchive,
318+ targetInfo. name, at: targetPath,
319+ canUseBuildableFolder: canUseBuildableFolders, productType: . staticArchive,
294320 includeInAllTarget: includeInAllTarget
295321 )
296322 guard let target else { return }
@@ -464,7 +490,7 @@ fileprivate final class ProjectGenerator {
464490 )
465491 }
466492 let target = generateBaseTarget (
467- targetInfo. name, at: nil , productType: nil ,
493+ targetInfo. name, at: nil , canUseBuildableFolder : false , productType: nil ,
468494 includeInAllTarget: includeInAllTarget
469495 )
470496 guard let target else { return nil }
@@ -505,9 +531,11 @@ fileprivate final class ProjectGenerator {
505531 guard checkNotExcluded ( buildRule. parentPath, for: " Swift target " ) else {
506532 return nil
507533 }
534+ // Create the target. Swift targets can always use buildable folders
535+ // since they have a consistent set of arguments.
508536 let target = generateBaseTarget (
509- targetInfo. name, at: buildRule. parentPath, productType : . staticArchive ,
510- includeInAllTarget: includeInAllTarget
537+ targetInfo. name, at: buildRule. parentPath, canUseBuildableFolder : true ,
538+ productType : . staticArchive , includeInAllTarget: includeInAllTarget
511539 )
512540 guard let target else { return nil }
513541
0 commit comments