@@ -25,7 +25,13 @@ fileprivate final class ProjectGenerator {
25
25
private var project = Xcode . Project ( )
26
26
private let allTarget : Xcode . Target
27
27
28
- private var groups : [ RelativePath : Xcode . Group ] = [ : ]
28
+ enum CachedGroup {
29
+ /// Covered by a parent folder reference.
30
+ case covered
31
+ /// Present in the project.
32
+ case present( Xcode . Group )
33
+ }
34
+ private var groups : [ RelativePath : CachedGroup ] = [ : ]
29
35
private var files : [ RelativePath : Xcode . FileReference ] = [ : ]
30
36
private var targets : [ String : Xcode . Target ] = [ : ]
31
37
private var unbuildableSources : [ ClangTarget . Source ] = [ ]
@@ -103,7 +109,7 @@ fileprivate final class ProjectGenerator {
103
109
/// for a file path relative to the project root.
104
110
private func parentGroup(
105
111
for path: RelativePath
106
- ) -> ( parentGroup: Xcode . Group , childPath: RelativePath ) {
112
+ ) -> ( parentGroup: Xcode . Group , childPath: RelativePath ) ? {
107
113
guard let parent = path. parentDir else {
108
114
// We've already handled paths under the repo, so this must be for
109
115
// paths outside the repo.
@@ -114,18 +120,31 @@ fileprivate final class ProjectGenerator {
114
120
if parent == repoRelativePath || parent == mainRepoDirInProject {
115
121
return ( project. mainGroup, path)
116
122
}
117
- return ( group ( for: parent) , RelativePath ( path. fileName) )
123
+ guard let parentGroup = group ( for: parent) else { return nil }
124
+ return ( parentGroup, RelativePath ( path. fileName) )
118
125
}
119
126
120
- private func group( for path: RelativePath ) -> Xcode . Group {
121
- if let group = groups [ path] {
122
- return group
127
+ /// Returns the group for a given path, or `nil` if the path is covered
128
+ /// by a parent folder reference.
129
+ private func group( for path: RelativePath ) -> Xcode . Group ? {
130
+ if let result = groups [ path] {
131
+ switch result {
132
+ case . covered:
133
+ return nil
134
+ case . present( let g) :
135
+ return g
136
+ }
137
+ }
138
+ guard
139
+ files [ path] == nil , let ( parentGroup, childPath) = parentGroup ( for: path)
140
+ else {
141
+ groups [ path] = . covered
142
+ return nil
123
143
}
124
- let ( parentGroup, childPath) = parentGroup ( for: path)
125
144
let group = parentGroup. addGroup (
126
145
path: childPath. rawPath, pathBase: . groupDir, name: path. fileName
127
146
)
128
- groups [ path] = group
147
+ groups [ path] = . present ( group)
129
148
return group
130
149
}
131
150
@@ -163,11 +182,12 @@ fileprivate final class ProjectGenerator {
163
182
// group there.
164
183
if ref. kind == . folder {
165
184
guard groups [ path] == nil else {
166
- log. warning ( " Skipping blue folder ' \( path) '; already added " )
167
185
return nil
168
186
}
169
187
}
170
- let ( parentGroup, childPath) = parentGroup ( for: path)
188
+ guard let ( parentGroup, childPath) = parentGroup ( for: path) else {
189
+ return nil
190
+ }
171
191
let file = parentGroup. addFileReference (
172
192
path: childPath. rawPath, isDirectory: ref. kind == . folder,
173
193
pathBase: . groupDir, name: path. fileName
@@ -178,10 +198,10 @@ fileprivate final class ProjectGenerator {
178
198
179
199
@discardableResult
180
200
private func getOrCreateRepoRef(
181
- _ ref: ProjectSpec . PathReference , allowExcluded : Bool = false
201
+ _ ref: ProjectSpec . PathReference
182
202
) -> Xcode . FileReference ? {
183
203
let path = ref. path
184
- guard allowExcluded || checkNotExcluded ( path) else { return nil }
204
+ guard checkNotExcluded ( path) else { return nil }
185
205
return getOrCreateProjectRef ( ref. withPath ( repoRelativePath. appending ( path) ) )
186
206
}
187
207
@@ -190,18 +210,35 @@ fileprivate final class ProjectGenerator {
190
210
}
191
211
192
212
func generateBaseTarget(
193
- _ name: String , productType : Xcode . Target . ProductType ? ,
194
- includeInAllTarget: Bool
213
+ _ name: String , at parentPath : RelativePath ? , canUseBuildableFolder : Bool ,
214
+ productType : Xcode . Target . ProductType ? , includeInAllTarget: Bool
195
215
) -> Xcode . Target ? {
196
216
guard targets [ name] == nil else {
197
217
log. warning ( " Duplicate target ' \( name) ', skipping " )
198
218
return nil
199
219
}
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
+ }
233
+ }
200
234
let target = project. addTarget ( productType: productType, name: name)
201
235
targets [ name] = target
202
236
if includeInAllTarget {
203
237
allTarget. addDependency ( on: target)
204
238
}
239
+ if let buildableFolder {
240
+ target. addBuildableFolder ( buildableFolder)
241
+ }
205
242
target. buildSettings. common. ONLY_ACTIVE_ARCH = " YES "
206
243
target. buildSettings. common. USE_HEADERMAP = " NO "
207
244
// The product name needs to be unique across every project we generate
@@ -247,8 +284,12 @@ fileprivate final class ProjectGenerator {
247
284
}
248
285
unbuildableSources += targetInfo. unbuildableSources
249
286
250
- for header in targetInfo. headers {
251
- 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
+ }
252
293
}
253
294
254
295
// If we have no sources, we're done.
@@ -262,8 +303,20 @@ fileprivate final class ProjectGenerator {
262
303
}
263
304
return
264
305
}
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
+
265
317
let target = generateBaseTarget (
266
- targetInfo. name, productType: . staticArchive,
318
+ targetInfo. name, at: targetPath,
319
+ canUseBuildableFolder: canUseBuildableFolders, productType: . staticArchive,
267
320
includeInAllTarget: includeInAllTarget
268
321
)
269
322
guard let target else { return }
@@ -437,7 +490,8 @@ fileprivate final class ProjectGenerator {
437
490
)
438
491
}
439
492
let target = generateBaseTarget (
440
- targetInfo. name, productType: nil , includeInAllTarget: includeInAllTarget
493
+ targetInfo. name, at: nil , canUseBuildableFolder: false , productType: nil ,
494
+ includeInAllTarget: includeInAllTarget
441
495
)
442
496
guard let target else { return nil }
443
497
@@ -477,9 +531,11 @@ fileprivate final class ProjectGenerator {
477
531
guard checkNotExcluded ( buildRule. parentPath, for: " Swift target " ) else {
478
532
return nil
479
533
}
534
+ // Create the target. Swift targets can always use buildable folders
535
+ // since they have a consistent set of arguments.
480
536
let target = generateBaseTarget (
481
- targetInfo. name, productType : . staticArchive ,
482
- includeInAllTarget: includeInAllTarget
537
+ targetInfo. name, at : buildRule . parentPath , canUseBuildableFolder : true ,
538
+ productType : . staticArchive , includeInAllTarget: includeInAllTarget
483
539
)
484
540
guard let target else { return nil }
485
541
@@ -599,6 +655,11 @@ fileprivate final class ProjectGenerator {
599
655
guard !generated else { return }
600
656
generated = true
601
657
658
+ // First add file/folder references.
659
+ for ref in spec. referencesToAdd {
660
+ getOrCreateRepoRef ( ref)
661
+ }
662
+
602
663
// Gather the Swift targets to generate, including any dependencies.
603
664
var swiftTargets : Set < SwiftTarget > = [ ]
604
665
for targetSource in spec. swiftTargetSources {
@@ -681,11 +742,6 @@ fileprivate final class ProjectGenerator {
681
742
}
682
743
}
683
744
684
- for ref in spec. referencesToAdd {
685
- // Allow important references to bypass exclusion checks.
686
- getOrCreateRepoRef ( ref, allowExcluded: ref. isImportant)
687
- }
688
-
689
745
// Sort the groups.
690
746
sortGroupChildren( project. mainGroup)
691
747
}
0 commit comments