@@ -35,6 +35,7 @@ fileprivate final class ProjectGenerator {
35
35
private var targets : [ String : Xcode . Target ] = [ : ]
36
36
private var unbuildableSources : [ RelativePath ] = [ ]
37
37
private var runnableBuildTargets : [ RunnableTarget : Xcode . Target ] = [ : ]
38
+ private var buildableFolders : [ RelativePath : Xcode . BuildableFolder ] = [ : ]
38
39
39
40
/// The group in which external files are stored.
40
41
private var externalsGroup : Xcode . Group {
@@ -54,6 +55,13 @@ fileprivate final class ProjectGenerator {
54
55
} ( )
55
56
private var includeSubstitutions : Set < BuildArgs . PathSubstitution > = [ ]
56
57
58
+ private lazy var unbuildablesTarget : Xcode . Target = {
59
+ generateBaseTarget (
60
+ " Unbuildables " , at: " . " , canUseBuildableFolder: false ,
61
+ productType: . staticArchive, includeInAllTarget: false
62
+ ) !
63
+ } ( )
64
+
57
65
/// The main repo dir relative to the project.
58
66
private lazy var mainRepoDirInProject : RelativePath ? =
59
67
spec. mainRepoDir. map { repoRelativePath. appending ( $0) }
@@ -205,6 +213,42 @@ fileprivate final class ProjectGenerator {
205
213
return getOrCreateProjectRef ( ref. withPath ( repoRelativePath. appending ( path) ) )
206
214
}
207
215
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
+
208
252
func getAllRepoSubpaths( of parent: RelativePath ) throws -> [ RelativePath ] {
209
253
try buildDir. getAllRepoSubpaths ( of: parent)
210
254
}
@@ -225,14 +269,15 @@ fileprivate final class ProjectGenerator {
225
269
}
226
270
return newName
227
271
} ( )
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 {
230
275
// If we've been asked to use buildable folders, see if we can create
231
276
// a folder reference at the parent path. Otherwise, create a group at
232
277
// the parent path. If we can't create either a folder or group, this is
233
278
// nested in a folder reference and there's nothing we can do.
234
279
if spec. useBuildableFolders && canUseBuildableFolder {
235
- buildableFolder = getOrCreateRepoRef ( . folder ( parentPath) )
280
+ buildableFolder = getOrCreateRepoBuildableFolder ( at : parentPath)
236
281
}
237
282
guard buildableFolder != nil ||
238
283
group ( for: repoRelativePath. appending ( parentPath) ) != nil else {
@@ -262,6 +307,13 @@ fileprivate final class ProjectGenerator {
262
307
// The product name needs to be unique across every project we generate
263
308
// (to allow the combined workspaces to work), so add in the project name.
264
309
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
+
265
317
return target
266
318
}
267
319
@@ -298,10 +350,9 @@ fileprivate final class ProjectGenerator {
298
350
at parentPath: RelativePath , sources: [ RelativePath ]
299
351
) throws -> Bool {
300
352
// 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.
305
356
guard spec. useBuildableFolders, !parentPath. isEmpty else { return false }
306
357
let sources = Set ( sources)
307
358
return try getAllRepoSubpaths ( of: parentPath)
@@ -311,20 +362,10 @@ fileprivate final class ProjectGenerator {
311
362
/// Checks whether a given Clang target can be represented using a buildable
312
363
/// folder.
313
364
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
+ )
328
369
}
329
370
330
371
func canUseBuildableFolder(
@@ -336,21 +377,62 @@ fileprivate final class ProjectGenerator {
336
377
)
337
378
}
338
379
339
- func generateClangTarget (
340
- _ targetInfo : ClangTarget , includeInAllTarget : Bool = true
380
+ func addSourcesPhaseToClangTarget (
381
+ _ target : Xcode . Target , sources : [ RelativePath ] , targetPath : RelativePath
341
382
) 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 {
342
414
let targetPath = targetInfo. parentPath
343
415
guard checkNotExcluded ( targetPath, for: " Clang target " ) else {
344
416
return
345
417
}
346
- unbuildableSources += targetInfo. unbuildableSources
347
418
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.
350
421
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
+ }
353
432
}
433
+ // Add the unbuildable sources regardless of buildable folder since
434
+ // we still need the compiler arguments to be set.
435
+ unbuildableSources += targetInfo. unbuildableSources
354
436
}
355
437
356
438
// If we have no sources, we're done.
@@ -362,22 +444,20 @@ fileprivate final class ProjectGenerator {
362
444
build args
363
445
""" )
364
446
}
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
+ }
365
452
return
366
453
}
367
454
let target = generateBaseTarget (
368
455
targetInfo. name, at: targetPath,
369
456
canUseBuildableFolder: try canUseBuildableFolder ( for: targetInfo) ,
370
- productType: . staticArchive,
371
- includeInAllTarget: includeInAllTarget
457
+ productType: . staticArchive, includeInAllTarget: true
372
458
)
373
459
guard let target else { return }
374
460
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
-
381
461
var libBuildArgs = try buildDir. clangArgs. getArgs ( for: targetPath)
382
462
applyBaseSubstitutions ( to: & libBuildArgs)
383
463
@@ -389,23 +469,9 @@ fileprivate final class ProjectGenerator {
389
469
390
470
target. buildSettings. common. OTHER_CPLUSPLUSFLAGS = libBuildArgs. printedArgs
391
471
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
+ )
409
475
}
410
476
411
477
/// Record path substitutions for a given target.
@@ -759,14 +825,11 @@ fileprivate final class ProjectGenerator {
759
825
try generateClangTarget ( target)
760
826
}
761
827
828
+ // Add any unbuildable sources to the special 'Unbuildables' target.
762
829
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: " . "
768
832
)
769
- try generateClangTarget ( target, includeInAllTarget: false )
770
833
}
771
834
772
835
// Add targets for runnable targets if needed.
0 commit comments