@@ -35,6 +35,7 @@ fileprivate final class ProjectGenerator {
3535 private var targets : [ String : Xcode . Target ] = [ : ]
3636 private var unbuildableSources : [ RelativePath ] = [ ]
3737 private var runnableBuildTargets : [ RunnableTarget : Xcode . Target ] = [ : ]
38+ private var buildableFolders : [ RelativePath : Xcode . BuildableFolder ] = [ : ]
3839
3940 /// The group in which external files are stored.
4041 private var externalsGroup : Xcode . Group {
@@ -54,6 +55,13 @@ fileprivate final class ProjectGenerator {
5455 } ( )
5556 private var includeSubstitutions : Set < BuildArgs . PathSubstitution > = [ ]
5657
58+ private lazy var unbuildablesTarget : Xcode . Target = {
59+ generateBaseTarget (
60+ " Unbuildables " , at: " . " , canUseBuildableFolder: false ,
61+ productType: . staticArchive, includeInAllTarget: false
62+ ) !
63+ } ( )
64+
5765 /// The main repo dir relative to the project.
5866 private lazy var mainRepoDirInProject : RelativePath ? =
5967 spec. mainRepoDir. map { repoRelativePath. appending ( $0) }
@@ -205,6 +213,42 @@ fileprivate final class ProjectGenerator {
205213 return getOrCreateProjectRef ( ref. withPath ( repoRelativePath. appending ( path) ) )
206214 }
207215
216+ @discardableResult
217+ func getOrCreateRepoBuildableFolder(
218+ at path: RelativePath
219+ ) -> Xcode . BuildableFolder ? {
220+ guard let ref = getOrCreateRepoRef ( . folder( path) ) else { return nil }
221+ let folder = ref. getOrCreateBuildableFolder ( at: path)
222+ buildableFolders [ path] = folder
223+
224+ // Exclude any sources we don't want to handle.
225+ do {
226+ let excluded = try buildDir. getAllRepoSubpaths ( of: path)
227+ . filter ( \. isExcludedSource)
228+ folder. setTargets ( [ ] , for: excluded)
229+ } catch {
230+ log. error ( " \( error) " )
231+ }
232+
233+ return folder
234+ }
235+
236+ private func getParentBuildableFolder(
237+ _ path: RelativePath
238+ ) -> Xcode . BuildableFolder ? {
239+ // First check the mapping directly.
240+ if let buildableFolder = buildableFolders [ path] {
241+ return buildableFolder
242+ }
243+ // Then check the parent.
244+ if let parent = path. parentDir,
245+ let buildableFolder = getParentBuildableFolder ( parent) {
246+ buildableFolders [ path] = buildableFolder
247+ return buildableFolder
248+ }
249+ return nil
250+ }
251+
208252 func getAllRepoSubpaths( of parent: RelativePath ) throws -> [ RelativePath ] {
209253 try buildDir. getAllRepoSubpaths ( of: parent)
210254 }
@@ -225,14 +269,15 @@ fileprivate final class ProjectGenerator {
225269 }
226270 return newName
227271 } ( )
228- var buildableFolder : Xcode . FileReference ?
229- if let parentPath, !parentPath. components. isEmpty {
272+ var buildableFolder : Xcode . BuildableFolder ?
273+ // Note that special targets like "Unbuildables" have an empty parent path.
274+ if let parentPath, !parentPath. isEmpty {
230275 // If we've been asked to use buildable folders, see if we can create
231276 // a folder reference at the parent path. Otherwise, create a group at
232277 // the parent path. If we can't create either a folder or group, this is
233278 // nested in a folder reference and there's nothing we can do.
234279 if spec. useBuildableFolders && canUseBuildableFolder {
235- buildableFolder = getOrCreateRepoRef ( . folder ( parentPath) )
280+ buildableFolder = getOrCreateRepoBuildableFolder ( at : parentPath)
236281 }
237282 guard buildableFolder != nil ||
238283 group ( for: repoRelativePath. appending ( parentPath) ) != nil else {
@@ -262,6 +307,13 @@ fileprivate final class ProjectGenerator {
262307 // The product name needs to be unique across every project we generate
263308 // (to allow the combined workspaces to work), so add in the project name.
264309 target. buildSettings. common. PRODUCT_NAME = " \( self . name) _ \( name) "
310+
311+ // Don't optimize or generate debug info, that will only slow down
312+ // compilation; we don't actually care about the binary.
313+ target. buildSettings. common. GCC_OPTIMIZATION_LEVEL = " 0 "
314+ target. buildSettings. common. GCC_GENERATE_DEBUGGING_SYMBOLS = " NO "
315+ target. buildSettings. common. GCC_WARN_64_TO_32_BIT_CONVERSION = " NO "
316+
265317 return target
266318 }
267319
@@ -298,10 +350,9 @@ fileprivate final class ProjectGenerator {
298350 at parentPath: RelativePath , sources: [ RelativePath ]
299351 ) throws -> Bool {
300352 // To use a buildable folder, all child sources need to be accounted for
301- // in the target. If we have any stray sources not part of the target,
302- // attempting to use a buildable folder would incorrectly include them.
303- // Additionally, special targets like "Unbuildables" have an empty parent
304- // path, avoid buildable folders for them.
353+ // in the target. Ignore special targets like "Unbuildables" which have an
354+ // empty parent path.
355+ // TODO: We ought to be able to add stray sources as exclusions.
305356 guard spec. useBuildableFolders, !parentPath. isEmpty else { return false }
306357 let sources = Set ( sources)
307358 return try getAllRepoSubpaths ( of: parentPath)
@@ -311,20 +362,10 @@ fileprivate final class ProjectGenerator {
311362 /// Checks whether a given Clang target can be represented using a buildable
312363 /// folder.
313364 func canUseBuildableFolder( for clangTarget: ClangTarget ) throws -> Bool {
314- // In addition to the standard checking, we also must not have any
315- // unbuildable sources or sources with unique arguments.
316- // TODO: To improve the coverage of buildable folders, we ought to start
317- // automatically splitting umbrella Clang targets like 'stdlib', since
318- // they currently always have files with unique args.
319- guard spec. useBuildableFolders, clangTarget. unbuildableSources. isEmpty else {
320- return false
321- }
322- let parent = clangTarget. parentPath
323- let hasConsistentArgs = try clangTarget. sources. allSatisfy {
324- try ! buildDir. clangArgs. hasUniqueArgs ( for: $0, parent: parent)
325- }
326- guard hasConsistentArgs else { return false }
327- return try canUseBuildableFolder ( at: parent, sources: clangTarget. sources)
365+ try canUseBuildableFolder (
366+ at: clangTarget. parentPath,
367+ sources: clangTarget. sources + clangTarget. unbuildableSources
368+ )
328369 }
329370
330371 func canUseBuildableFolder(
@@ -336,21 +377,62 @@ fileprivate final class ProjectGenerator {
336377 )
337378 }
338379
339- func generateClangTarget (
340- _ targetInfo : ClangTarget , includeInAllTarget : Bool = true
380+ func addSourcesPhaseToClangTarget (
381+ _ target : Xcode . Target , sources : [ RelativePath ] , targetPath : RelativePath
341382 ) throws {
383+ let sourcesToBuild = target. addSourcesBuildPhase ( )
384+ for source in sources {
385+ var fileArgs = try buildDir. clangArgs. getUniqueArgs (
386+ for: source, parent: targetPath, infer: spec. inferArgs
387+ )
388+ if !fileArgs. isEmpty {
389+ applyBaseSubstitutions ( to: & fileArgs)
390+ }
391+ // If we're using a buildable folder, the extra arguments are added to it
392+ // directly.
393+ if let buildableFolder = getParentBuildableFolder ( source) {
394+ if !fileArgs. isEmpty {
395+ buildableFolder. setExtraCompilerArgs (
396+ fileArgs. printedArgs, for: source, in: target
397+ )
398+ }
399+ continue
400+ }
401+ // Otherwise we add as a file reference and add the arguments to the
402+ // target.
403+ guard let sourceRef = getOrCreateRepoRef ( . file( source) ) else {
404+ continue
405+ }
406+ let buildFile = sourcesToBuild. addBuildFile ( fileRef: sourceRef)
407+
408+ // Add any per-file settings.
409+ buildFile. settings. COMPILER_FLAGS = fileArgs. printed
410+ }
411+ }
412+
413+ func generateClangTarget( _ targetInfo: ClangTarget ) throws {
342414 let targetPath = targetInfo. parentPath
343415 guard checkNotExcluded ( targetPath, for: " Clang target " ) else {
344416 return
345417 }
346- unbuildableSources += targetInfo. unbuildableSources
347418
348- // Need to defer the addition of headers since the target may want to use
349- // a buildable folder.
419+ // Need to defer the addition of headers and unbuildable sources since the
420+ // target may want to use a buildable folder.
350421 defer {
351- for header in targetInfo. headers {
352- getOrCreateRepoRef ( . file( header) )
422+ // If we're using a buildable folder, the headers are automatically
423+ // included.
424+ if let buildableFolder = getParentBuildableFolder ( targetPath) {
425+ buildableFolder. setTargets (
426+ [ unbuildablesTarget] , for: targetInfo. unbuildableSources
427+ )
428+ } else {
429+ for header in targetInfo. headers {
430+ getOrCreateRepoRef ( . file( header) )
431+ }
353432 }
433+ // Add the unbuildable sources regardless of buildable folder since
434+ // we still need the compiler arguments to be set.
435+ unbuildableSources += targetInfo. unbuildableSources
354436 }
355437
356438 // If we have no sources, we're done.
@@ -362,22 +444,20 @@ fileprivate final class ProjectGenerator {
362444 build args
363445 """ )
364446 }
447+ // Still create a buildable folder if we can. It won't have an associated
448+ // target, but unbuildable sources may still be added as exceptions.
449+ if try canUseBuildableFolder ( for: targetInfo) {
450+ getOrCreateRepoBuildableFolder ( at: targetPath)
451+ }
365452 return
366453 }
367454 let target = generateBaseTarget (
368455 targetInfo. name, at: targetPath,
369456 canUseBuildableFolder: try canUseBuildableFolder ( for: targetInfo) ,
370- productType: . staticArchive,
371- includeInAllTarget: includeInAllTarget
457+ productType: . staticArchive, includeInAllTarget: true
372458 )
373459 guard let target else { return }
374460
375- // Don't optimize or generate debug info, that will only slow down
376- // compilation; we don't actually care about the binary.
377- target. buildSettings. common. GCC_OPTIMIZATION_LEVEL = " 0 "
378- target. buildSettings. common. GCC_GENERATE_DEBUGGING_SYMBOLS = " NO "
379- target. buildSettings. common. GCC_WARN_64_TO_32_BIT_CONVERSION = " NO "
380-
381461 var libBuildArgs = try buildDir. clangArgs. getArgs ( for: targetPath)
382462 applyBaseSubstitutions ( to: & libBuildArgs)
383463
@@ -389,23 +469,9 @@ fileprivate final class ProjectGenerator {
389469
390470 target. buildSettings. common. OTHER_CPLUSPLUSFLAGS = libBuildArgs. printedArgs
391471
392- let sourcesToBuild = target. addSourcesBuildPhase ( )
393-
394- for source in targetInfo. sources {
395- guard let sourceRef = getOrCreateRepoRef ( . file( source) ) else {
396- continue
397- }
398- let buildFile = sourcesToBuild. addBuildFile ( fileRef: sourceRef)
399-
400- // Add any per-file settings.
401- var fileArgs = try buildDir. clangArgs. getUniqueArgs (
402- for: source, parent: targetPath, infer: spec. inferArgs
403- )
404- if !fileArgs. isEmpty {
405- applyBaseSubstitutions ( to: & fileArgs)
406- buildFile. settings. COMPILER_FLAGS = fileArgs. printed
407- }
408- }
472+ try addSourcesPhaseToClangTarget (
473+ target, sources: targetInfo. sources, targetPath: targetPath
474+ )
409475 }
410476
411477 /// Record path substitutions for a given target.
@@ -759,14 +825,11 @@ fileprivate final class ProjectGenerator {
759825 try generateClangTarget ( target)
760826 }
761827
828+ // Add any unbuildable sources to the special 'Unbuildables' target.
762829 if !unbuildableSources. isEmpty {
763- let target = ClangTarget (
764- name: " Unbuildables " ,
765- parentPath: " . " ,
766- sources: unbuildableSources,
767- headers: [ ]
830+ try addSourcesPhaseToClangTarget (
831+ unbuildablesTarget, sources: unbuildableSources, targetPath: " . "
768832 )
769- try generateClangTarget ( target, includeInAllTarget: false )
770833 }
771834
772835 // Add targets for runnable targets if needed.
0 commit comments