@@ -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