@@ -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
@@ -167,7 +186,9 @@ fileprivate final class ProjectGenerator {
167
186
return nil
168
187
}
169
188
}
170
- let ( parentGroup, childPath) = parentGroup ( for: path)
189
+ guard let ( parentGroup, childPath) = parentGroup ( for: path) else {
190
+ return nil
191
+ }
171
192
let file = parentGroup. addFileReference (
172
193
path: childPath. rawPath, isDirectory: ref. kind == . folder,
173
194
pathBase: . groupDir, name: path. fileName
@@ -190,13 +211,19 @@ fileprivate final class ProjectGenerator {
190
211
}
191
212
192
213
func generateBaseTarget(
193
- _ name: String , productType : Xcode . Target . ProductType ? ,
194
- includeInAllTarget: Bool
214
+ _ name: String , at parentPath : RelativePath ? ,
215
+ productType : Xcode . Target . ProductType ? , includeInAllTarget: Bool
195
216
) -> Xcode . Target ? {
196
217
guard targets [ name] == nil else {
197
218
log. warning ( " Duplicate target ' \( name) ', skipping " )
198
219
return nil
199
220
}
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
226
+ }
200
227
let target = project. addTarget ( productType: productType, name: name)
201
228
targets [ name] = target
202
229
if includeInAllTarget {
@@ -263,7 +290,7 @@ fileprivate final class ProjectGenerator {
263
290
return
264
291
}
265
292
let target = generateBaseTarget (
266
- targetInfo. name, productType: . staticArchive,
293
+ targetInfo. name, at : targetInfo . parentPath , productType: . staticArchive,
267
294
includeInAllTarget: includeInAllTarget
268
295
)
269
296
guard let target else { return }
@@ -437,7 +464,8 @@ fileprivate final class ProjectGenerator {
437
464
)
438
465
}
439
466
let target = generateBaseTarget (
440
- targetInfo. name, productType: nil , includeInAllTarget: includeInAllTarget
467
+ targetInfo. name, at: nil , productType: nil ,
468
+ includeInAllTarget: includeInAllTarget
441
469
)
442
470
guard let target else { return nil }
443
471
@@ -478,7 +506,7 @@ fileprivate final class ProjectGenerator {
478
506
return nil
479
507
}
480
508
let target = generateBaseTarget (
481
- targetInfo. name, productType: . staticArchive,
509
+ targetInfo. name, at : buildRule . parentPath , productType: . staticArchive,
482
510
includeInAllTarget: includeInAllTarget
483
511
)
484
512
guard let target else { return nil }
@@ -599,6 +627,12 @@ fileprivate final class ProjectGenerator {
599
627
guard !generated else { return }
600
628
generated = true
601
629
630
+ // First add file/folder references.
631
+ for ref in spec. referencesToAdd {
632
+ // Allow important references to bypass exclusion checks.
633
+ getOrCreateRepoRef ( ref, allowExcluded: ref. isImportant)
634
+ }
635
+
602
636
// Gather the Swift targets to generate, including any dependencies.
603
637
var swiftTargets : Set < SwiftTarget > = [ ]
604
638
for targetSource in spec. swiftTargetSources {
@@ -681,11 +715,6 @@ fileprivate final class ProjectGenerator {
681
715
}
682
716
}
683
717
684
- for ref in spec. referencesToAdd {
685
- // Allow important references to bypass exclusion checks.
686
- getOrCreateRepoRef ( ref, allowExcluded: ref. isImportant)
687
- }
688
-
689
718
// Sort the groups.
690
719
sortGroupChildren( project. mainGroup)
691
720
}
0 commit comments