Skip to content

Commit cb28c35

Browse files
committed
update SourceGenerator, PBXProjGenerator, SourceType
## SourceGenerator - adapt SourceGenerator to TargetSourceFilterable - refactor variant group logic - add logic where you can add target membership to another target ## PBXProjGenerator - apply. new PBXProjGenerator ## SourceType - add new sourceType
1 parent 8a33b01 commit cb28c35

File tree

3 files changed

+61
-165
lines changed

3 files changed

+61
-165
lines changed

Sources/ProjectSpec/SourceType.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ public enum SourceType: String {
1111
case group
1212
case file
1313
case folder
14+
case variantGroup
1415
}

Sources/XcodeGenKit/PBXProjGenerator.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ public class PBXProjGenerator {
222222
pbxProject.projects = subprojects
223223
}
224224

225-
try project.targets.forEach(generateTarget)
225+
let pbxVariantGroupInfoList = try PBXVariantGroupGenerator(pbxProj: pbxProj, project: project).generate()
226+
try project.targets.forEach { try generateTarget($0, pbxVariantGroupInfoList: pbxVariantGroupInfoList) }
226227
try project.aggregateTargets.forEach(generateAggregateTarget)
227228

228229
if !carthageFrameworksByPlatform.isEmpty {
@@ -648,13 +649,13 @@ public class PBXProjGenerator {
648649
return pbxproj
649650
}
650651

651-
func generateTarget(_ target: Target) throws {
652+
func generateTarget(_ target: Target, pbxVariantGroupInfoList: [PBXVariantGroupInfo]) throws {
652653
let carthageDependencies = carthageResolver.dependencies(for: target)
653654

654655
let infoPlistFiles: [Config: String] = getInfoPlists(for: target)
655656
let sourceFileBuildPhaseOverrideSequence: [(Path, BuildPhaseSpec)] = Set(infoPlistFiles.values).map({ (project.basePath + $0, .none) })
656657
let sourceFileBuildPhaseOverrides = Dictionary(uniqueKeysWithValues: sourceFileBuildPhaseOverrideSequence)
657-
let sourceFiles = try sourceGenerator.getAllSourceFiles(targetType: target.type, sources: target.sources, buildPhases: sourceFileBuildPhaseOverrides)
658+
let sourceFiles = try sourceGenerator.getAllSourceFiles(targetName: target.name, targetType: target.type, sources: target.sources, buildPhases: sourceFileBuildPhaseOverrides, pbxVariantGroupInfoList: pbxVariantGroupInfoList)
658659
.sorted { $0.path.lastComponent < $1.path.lastComponent }
659660

660661
var anyDependencyRequiresObjCLinking = false

Sources/XcodeGenKit/SourceGenerator.swift

Lines changed: 56 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,18 @@ struct SourceFile {
1111
let buildPhase: BuildPhaseSpec?
1212
}
1313

14-
class SourceGenerator {
14+
class SourceGenerator: TargetSourceFilterable {
1515

1616
var rootGroups: Set<PBXFileElement> = []
1717
private let projectDirectory: Path?
1818
private var fileReferencesByPath: [String: PBXFileElement] = [:]
1919
private var groupsByPath: [Path: PBXGroup] = [:]
20-
private var variantGroupsByPath: [Path: PBXVariantGroup] = [:]
20+
private var pbxVariantGroupInfoList: [PBXVariantGroupInfo] = []
2121
private var localPackageGroup: PBXGroup?
2222

23-
private let project: Project
23+
let project: Project
2424
let pbxProj: PBXProj
2525

26-
private var defaultExcludedFiles = [
27-
".DS_Store",
28-
]
29-
private let defaultExcludedExtensions = [
30-
"orig",
31-
]
32-
3326
private(set) var knownRegions: Set<String> = []
3427

3528
init(project: Project, pbxProj: PBXProj, projectDirectory: Path?) {
@@ -90,16 +83,19 @@ class SourceGenerator {
9083
/// Collects an array complete of all `SourceFile` objects that make up the target based on the provided `TargetSource` definitions.
9184
///
9285
/// - Parameters:
93-
/// - targetType: The type of target that the source files should belong to.
94-
/// - sources: The array of sources defined as part of the targets spec.
95-
/// - buildPhases: A dictionary containing any build phases that should be applied to source files at specific paths in the event that the associated `TargetSource` didn't already define a `buildPhase`. Values from this dictionary are used in cases where the project generator knows more about a file than the spec/filesystem does (i.e if the file should be treated as the targets Info.plist and so on).
96-
func getAllSourceFiles(targetType: PBXProductType, sources: [TargetSource], buildPhases: [Path : BuildPhaseSpec]) throws -> [SourceFile] {
97-
try sources.flatMap { try getSourceFiles(targetType: targetType, targetSource: $0, buildPhases: buildPhases) }
86+
/// - targetName: The name of target that the source files should belong to.
87+
/// - targetType: The type of target that the source files should belong to.
88+
/// - sources: The array of sources defined as part of the targets spec.
89+
/// - buildPhases: A dictionary containing any build phases that should be applied to source files at specific paths in the event that the associated `TargetSource` didn't already define a `buildPhase`. Values from this dictionary are used in cases where the project generator knows more about a file than the spec/filesystem does (i.e if the file should be treated as the targets Info.plist and so on).
90+
/// - pbxVariantGroupInfoList: An array of all PBXVariantGroup information expected to be added to the target
91+
func getAllSourceFiles(targetName: String, targetType: PBXProductType, sources: [TargetSource], buildPhases: [Path : BuildPhaseSpec], pbxVariantGroupInfoList: [PBXVariantGroupInfo]) throws -> [SourceFile] {
92+
self.pbxVariantGroupInfoList = pbxVariantGroupInfoList
93+
return try sources.flatMap { try getSourceFiles(targetName: targetName, targetType: targetType, targetSource: $0, buildPhases: buildPhases) }
9894
}
9995

10096
// get groups without build files. Use for Project.fileGroups
10197
func getFileGroups(path: String) throws {
102-
_ = try getSourceFiles(targetType: .none, targetSource: TargetSource(path: path), buildPhases: [:])
98+
_ = try getSourceFiles(targetName: "", targetType: .none, targetSource: TargetSource(path: path), buildPhases: [:])
10399
}
104100

105101
func getFileType(path: Path) -> FileType? {
@@ -338,84 +334,9 @@ class SourceGenerator {
338334
return groupReference
339335
}
340336

341-
/// Creates a variant group or returns an existing one at the path
342-
private func getVariantGroup(path: Path, inPath: Path) -> PBXVariantGroup {
343-
let variantGroup: PBXVariantGroup
344-
if let cachedGroup = variantGroupsByPath[path] {
345-
variantGroup = cachedGroup
346-
} else {
347-
let group = PBXVariantGroup(
348-
sourceTree: .group,
349-
name: path.lastComponent
350-
)
351-
variantGroup = addObject(group)
352-
variantGroupsByPath[path] = variantGroup
353-
}
354-
return variantGroup
355-
}
356-
357-
/// Collects all the excluded paths within the targetSource
358-
private func getSourceMatches(targetSource: TargetSource, patterns: [String]) -> Set<Path> {
359-
let rootSourcePath = project.basePath + targetSource.path
360-
361-
return Set(
362-
patterns.parallelMap { pattern in
363-
guard !pattern.isEmpty else { return [] }
364-
return Glob(pattern: "\(rootSourcePath)/\(pattern)")
365-
.map { Path($0) }
366-
.map {
367-
guard $0.isDirectory else {
368-
return [$0]
369-
}
370-
371-
return (try? $0.recursiveChildren()) ?? []
372-
}
373-
.reduce([], +)
374-
}
375-
.reduce([], +)
376-
)
377-
}
378-
379-
/// Checks whether the path is not in any default or TargetSource excludes
380-
func isIncludedPath(_ path: Path, excludePaths: Set<Path>, includePaths: SortedArray<Path>) -> Bool {
381-
return !defaultExcludedFiles.contains(where: { path.lastComponent == $0 })
382-
&& !(path.extension.map(defaultExcludedExtensions.contains) ?? false)
383-
&& !excludePaths.contains(path)
384-
// If includes is empty, it's included. If it's not empty, the path either needs to match exactly, or it needs to be a direct parent of an included path.
385-
&& (includePaths.value.isEmpty || _isIncludedPathSorted(path, sortedPaths: includePaths))
386-
}
387-
388-
private func _isIncludedPathSorted(_ path: Path, sortedPaths: SortedArray<Path>) -> Bool {
389-
guard let idx = sortedPaths.firstIndex(where: { $0 >= path }) else { return false }
390-
let foundPath = sortedPaths.value[idx]
391-
return foundPath.description.hasPrefix(path.description)
392-
}
393-
394-
395-
/// Gets all the children paths that aren't excluded
396-
private func getSourceChildren(targetSource: TargetSource, dirPath: Path, excludePaths: Set<Path>, includePaths: SortedArray<Path>) throws -> [Path] {
397-
try dirPath.children()
398-
.filter {
399-
if $0.isDirectory {
400-
let children = try $0.children()
401-
402-
if children.isEmpty {
403-
return project.options.generateEmptyDirectories
404-
}
405-
406-
return !children
407-
.filter { self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) }
408-
.isEmpty
409-
} else if $0.isFile {
410-
return self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths)
411-
} else {
412-
return false
413-
}
414-
}
415-
}
416-
417337
/// creates all the source files and groups they belong to for a given targetSource
418338
private func getGroupSources(
339+
targetName: String,
419340
targetType: PBXProductType,
420341
targetSource: TargetSource,
421342
path: Path,
@@ -449,9 +370,6 @@ class SourceGenerator {
449370
}
450371
}
451372

452-
let localisedDirectories = children
453-
.filter { $0.extension == "lproj" }
454-
455373
var groupChildren: [PBXFileElement] = filePaths.map { getFileReference(path: $0, inPath: path) }
456374
var allSourceFiles: [SourceFile] = filePaths.map {
457375
generateSourceFile(targetType: targetType, targetSource: targetSource, path: $0, buildPhases: buildPhases)
@@ -461,6 +379,7 @@ class SourceGenerator {
461379
for path in directories {
462380

463381
let subGroups = try getGroupSources(
382+
targetName: targetName,
464383
targetType: targetType,
465384
targetSource: targetSource,
466385
path: path,
@@ -484,79 +403,36 @@ class SourceGenerator {
484403
groups += subGroups.groups
485404
}
486405
}
487-
488-
// find the base localised directory
489-
let baseLocalisedDirectory: Path? = {
490-
func findLocalisedDirectory(by languageId: String) -> Path? {
491-
localisedDirectories.first { $0.lastComponent == "\(languageId).lproj" }
492-
}
493-
return findLocalisedDirectory(by: "Base") ??
494-
findLocalisedDirectory(by: NSLocale.canonicalLanguageIdentifier(from: project.options.developmentLanguage ?? "en"))
495-
}()
496-
497-
knownRegions.formUnion(localisedDirectories.map { $0.lastComponentWithoutExtension })
498-
499-
// create variant groups of the base localisation first
500-
var baseLocalisationVariantGroups: [PBXVariantGroup] = []
501-
502-
if let baseLocalisedDirectory = baseLocalisedDirectory {
503-
let filePaths = try baseLocalisedDirectory.children()
406+
407+
let localisedDirectories = children
408+
.filter { $0.extension == "lproj" }
409+
410+
for localizedDir in localisedDirectories {
411+
412+
let localizedDirChildren = try localizedDir.children()
504413
.filter { self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) }
505414
.sorted()
506-
for filePath in filePaths {
507-
let variantGroup = getVariantGroup(path: filePath, inPath: path)
508-
groupChildren.append(variantGroup)
509-
baseLocalisationVariantGroups.append(variantGroup)
510-
415+
416+
for localizedDirChildPath in localizedDirChildren {
417+
418+
guard let variantGroupInfo = pbxVariantGroupInfoList
419+
.filter({ $0.targetName == targetName })
420+
.first(where: { $0.path == localizedDirChildPath }) else {
421+
break
422+
}
423+
groupChildren.append(variantGroupInfo.variantGroup)
424+
511425
let sourceFile = generateSourceFile(targetType: targetType,
512426
targetSource: targetSource,
513-
path: filePath,
514-
fileReference: variantGroup,
427+
path: localizedDirChildPath,
428+
fileReference: variantGroupInfo.variantGroup,
515429
buildPhases: buildPhases)
516430
allSourceFiles.append(sourceFile)
517431
}
518432
}
519-
520-
// add references to localised resources into base localisation variant groups
521-
for localisedDirectory in localisedDirectories {
522-
let localisationName = localisedDirectory.lastComponentWithoutExtension
523-
let filePaths = try localisedDirectory.children()
524-
.filter { self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) }
525-
.sorted { $0.lastComponent < $1.lastComponent }
526-
for filePath in filePaths {
527-
// find base localisation variant group
528-
// ex: Foo.strings will be added to Foo.strings or Foo.storyboard variant group
529-
let variantGroup = baseLocalisationVariantGroups
530-
.first {
531-
Path($0.name!).lastComponent == filePath.lastComponent
532-
533-
} ?? baseLocalisationVariantGroups.first {
534-
Path($0.name!).lastComponentWithoutExtension == filePath.lastComponentWithoutExtension
535-
}
536-
537-
let fileReference = getFileReference(
538-
path: filePath,
539-
inPath: path,
540-
name: variantGroup != nil ? localisationName : filePath.lastComponent
541-
)
542-
543-
if let variantGroup = variantGroup {
544-
if !variantGroup.children.contains(fileReference) {
545-
variantGroup.children.append(fileReference)
546-
}
547-
} else {
548-
// add SourceFile to group if there is no Base.lproj directory
549-
let sourceFile = generateSourceFile(targetType: targetType,
550-
targetSource: targetSource,
551-
path: filePath,
552-
fileReference: fileReference,
553-
buildPhases: buildPhases)
554-
allSourceFiles.append(sourceFile)
555-
groupChildren.append(fileReference)
556-
}
557-
}
558-
}
559-
433+
434+
knownRegions.formUnion(localisedDirectories.map { $0.lastComponentWithoutExtension })
435+
560436
let group = getGroup(
561437
path: path,
562438
mergingChildren: groupChildren,
@@ -573,7 +449,7 @@ class SourceGenerator {
573449
}
574450

575451
/// creates source files
576-
private func getSourceFiles(targetType: PBXProductType, targetSource: TargetSource, buildPhases: [Path: BuildPhaseSpec]) throws -> [SourceFile] {
452+
private func getSourceFiles(targetName: String, targetType: PBXProductType, targetSource: TargetSource, buildPhases: [Path: BuildPhaseSpec]) throws -> [SourceFile] {
577453

578454
// generate excluded paths
579455
let path = project.basePath + targetSource.path
@@ -642,6 +518,7 @@ class SourceGenerator {
642518
}
643519

644520
let (groupSourceFiles, groups) = try getGroupSources(
521+
targetName: targetName,
645522
targetType: targetType,
646523
targetSource: targetSource,
647524
path: path,
@@ -659,6 +536,19 @@ class SourceGenerator {
659536

660537
sourceFiles += groupSourceFiles
661538
sourceReference = group
539+
540+
case .variantGroup:
541+
let variantGroup: PBXVariantGroup? = pbxVariantGroupInfoList
542+
.first { $0.path == path }?.variantGroup
543+
544+
let sourceFile = generateSourceFile(targetType: targetType,
545+
targetSource: targetSource,
546+
path: path,
547+
fileReference: variantGroup,
548+
buildPhases: buildPhases)
549+
550+
sourceFiles.append(sourceFile)
551+
sourceReference = variantGroup!
662552
}
663553

664554
if hasCustomParent {
@@ -675,7 +565,11 @@ class SourceGenerator {
675565
///
676566
/// While `TargetSource` declares `type`, its optional and in the event that the value is not defined then we must resolve a sensible default based on the path of the source.
677567
private func resolvedTargetSourceType(for targetSource: TargetSource, at path: Path) -> SourceType {
678-
return targetSource.type ?? (path.isFile || path.extension != nil ? .file : .group)
568+
if targetSource.path.contains(".lproj") {
569+
return .variantGroup
570+
} else {
571+
return targetSource.type ?? (path.isFile || path.extension != nil ? .file : .group)
572+
}
679573
}
680574

681575
private func createParentGroups(_ parentGroups: [String], for fileElement: PBXFileElement) {

0 commit comments

Comments
 (0)