Skip to content

Commit

Permalink
add PBXVariantGroupGenerator, TargetSourceFilterable
Browse files Browse the repository at this point in the history
  • Loading branch information
takeshi-1000 committed Dec 5, 2022
1 parent e7f7537 commit 8a33b01
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 0 deletions.
148 changes: 148 additions & 0 deletions Sources/XcodeGenKit/PBXVariantGroupGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import XcodeProj
import ProjectSpec
import PathKit
import XcodeGenCore

class PBXVariantGroupInfo {
let targetName: String
let variantGroup: PBXVariantGroup
var path: Path

init(targetName: String, variantGroup: PBXVariantGroup, path: Path) {
self.targetName = targetName
self.variantGroup = variantGroup
self.path = path
}
}

class PBXVariantGroupGenerator: TargetSourceFilterable {
let pbxProj: PBXProj
let project: Project

init(pbxProj: PBXProj, project: Project) {
self.pbxProj = pbxProj
self.project = project
}

var alwaysStoredBaseExtensions: [String] {
[".xib", ".storyboard", ".intentdefinition"]
}

func generate() throws -> [PBXVariantGroupInfo] {
var variantGroupInfoList: [PBXVariantGroupInfo] = []

try project.targets.forEach { target in
try target.sources.forEach { targetSource in
let excludePaths = getSourceMatches(targetSource: targetSource,
patterns: targetSource.excludes)
let includePaths = getSourceMatches(targetSource: targetSource,
patterns: targetSource.includes)

let path = project.basePath + targetSource.path

try generateVarientGroup(targetName: target.name,
targetSource: targetSource,
path: path,
excludePaths: excludePaths,
includePaths: SortedArray(includePaths))
}
}

func generateVarientGroup(targetName: String,
targetSource: TargetSource,
path: Path,
excludePaths: Set<Path>,
includePaths: SortedArray<Path>) throws {
guard path.exists && path.isDirectory && !Xcode.isDirectoryFileWrapper(path: path) else {
return
}

let children = try getSourceChildren(targetSource: targetSource,
dirPath: path,
excludePaths: excludePaths,
includePaths: includePaths)

try children.forEach {
let excludePaths = getSourceMatches(targetSource: targetSource,
patterns: targetSource.excludes)
let includePaths = getSourceMatches(targetSource: targetSource,
patterns: targetSource.includes)

try generateVarientGroup(targetName: targetName,
targetSource: targetSource,
path: $0,
excludePaths: excludePaths,
includePaths: SortedArray(includePaths))
}

let localizeDirs: [Path] = children
.filter ({ $0.extension == "lproj" })

guard localizeDirs.count > 0 else {
return
}

try localizeDirs.forEach { localizedDir in
try localizedDir.children()
.filter { self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) }
.sorted()
.forEach { localizedDirChildPath in
let fileReferencePath = try localizedDirChildPath.relativePath(from: path)
let fileRef = PBXFileReference(
sourceTree: .group,
name: localizedDir.lastComponentWithoutExtension,
lastKnownFileType: Xcode.fileType(path: localizedDirChildPath),
path: fileReferencePath.string
)
pbxProj.add(object: fileRef)

let variantGroupInfo = getVariantGroupInfo(targetName: targetName, localizedChildPath: localizedDirChildPath)

if localizedDir.lastComponentWithoutExtension == "Base" || project.options.developmentLanguage == localizedDir.lastComponentWithoutExtension {

variantGroupInfo.path = localizedDirChildPath
variantGroupInfo.variantGroup.name = localizedDirChildPath.lastComponent
}

variantGroupInfo.variantGroup.children.append(fileRef)
}
}
}

func getVariantGroupInfo(targetName: String, localizedChildPath: Path) -> PBXVariantGroupInfo {
let pbxVariantGroupInfo = variantGroupInfoList
.filter { $0.targetName == targetName }
.first {
let existsAlwaysStoredBaseFile = alwaysStoredBaseExtensions
.reduce(into: [Bool]()) { $0.append(localizedChildPath.lastComponent.contains($1)) }
.filter { $0 }
.count > 0

if existsAlwaysStoredBaseFile {
return $0.path.lastComponentWithoutExtension == localizedChildPath.lastComponentWithoutExtension
} else {
return $0.path.lastComponent == localizedChildPath.lastComponent
}
}

if let pbxVariantGroupInfo = pbxVariantGroupInfo {
return pbxVariantGroupInfo
} else {
let variantGroup = PBXVariantGroup(
sourceTree: .group,
name: localizedChildPath.lastComponent
)
pbxProj.add(object: variantGroup)

let pbxVariantGroupInfo = PBXVariantGroupInfo(targetName: targetName,
variantGroup: variantGroup,
path: localizedChildPath)
variantGroupInfoList.append(pbxVariantGroupInfo)

return pbxVariantGroupInfo
}
}

return variantGroupInfoList
}
}
79 changes: 79 additions & 0 deletions Sources/XcodeGenKit/TargetSourceFilterable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import XcodeProj
import ProjectSpec
import PathKit
import XcodeGenCore

protocol TargetSourceFilterable {
var project: Project { get }
var defaultExcludedFiles: [String] { get }
var defaultExcludedExtensions: [String] { get }
}

extension TargetSourceFilterable {

var defaultExcludedFiles: [String] {
[".DS_Store"]
}

var defaultExcludedExtensions: [String] {
["orig"]
}

/// Gets all the children paths that aren't excluded
func getSourceChildren(targetSource: TargetSource, dirPath: Path, excludePaths: Set<Path>, includePaths: SortedArray<Path>) throws -> [Path] {
try dirPath.children()
.filter {
if $0.isDirectory {
let children = try $0.children()

if children.isEmpty {
return project.options.generateEmptyDirectories
}

return !children
.filter { self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) }
.isEmpty
} else if $0.isFile {
return self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths)
} else {
return false
}
}
}

/// Checks whether the path is not in any default or TargetSource excludes
func isIncludedPath(_ path: Path, excludePaths: Set<Path>, includePaths: SortedArray<Path>) -> Bool {
return !defaultExcludedFiles.contains(where: { path.lastComponent == $0 })
&& !(path.extension.map(defaultExcludedExtensions.contains) ?? false)
&& !excludePaths.contains(path)
// 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.
&& (includePaths.value.isEmpty || _isIncludedPathSorted(path, sortedPaths: includePaths))

func _isIncludedPathSorted(_ path: Path, sortedPaths: SortedArray<Path>) -> Bool {
guard let idx = sortedPaths.firstIndex(where: { $0 >= path }) else { return false }
let foundPath = sortedPaths.value[idx]
return foundPath.description.hasPrefix(path.description)
}
}

func getSourceMatches(targetSource: TargetSource, patterns: [String]) -> Set<Path> {
let rootSourcePath = project.basePath + targetSource.path

return Set(
patterns.parallelMap { pattern in
guard !pattern.isEmpty else { return [] }
return Glob(pattern: "\(rootSourcePath)/\(pattern)")
.map { Path($0) }
.map {
guard $0.isDirectory else {
return [$0]
}

return (try? $0.recursiveChildren()) ?? []
}
.reduce([], +)
}
.reduce([], +)
)
}
}

0 comments on commit 8a33b01

Please sign in to comment.