mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2026-03-18 20:02:25 +00:00
4a54fe6d2a
The `sources` key of the project spec only supported directories and not files. Now it supports both! This commit introduces a `getSourceFiles` overload that doesn't explicitly invoke `path.children()`, but instead accepts `children` as a parameter. This allows us to invoke the `children` overload of getSourceFiles with just the files we want to include (determined by specifying the sources). Now for sourcePaths that are files, we group by parents before invoking getSourceFiles in order to reuse the same groups.
625 lines
30 KiB
Swift
625 lines
30 KiB
Swift
//
|
|
// PBXProjGenerator.swift
|
|
// XcodeGen
|
|
//
|
|
// Created by Yonas Kolb on 23/7/17.
|
|
//
|
|
//
|
|
|
|
import Foundation
|
|
import Foundation
|
|
import PathKit
|
|
import xcproj
|
|
import JSONUtilities
|
|
import Yams
|
|
import ProjectSpec
|
|
|
|
public class PBXProjGenerator {
|
|
|
|
let spec: ProjectSpec
|
|
let currentXcodeVersion: String
|
|
|
|
var fileReferencesByPath: [Path: String] = [:]
|
|
var groupsByPath: [Path: PBXGroup] = [:]
|
|
var variantGroupsByPath: [Path: PBXVariantGroup] = [:]
|
|
|
|
var targetNativeReferences: [String: String] = [:]
|
|
var targetBuildFiles: [String: PBXBuildFile] = [:]
|
|
var targetFileReferences: [String: String] = [:]
|
|
var topLevelGroups: [PBXGroup] = []
|
|
var carthageFrameworksByPlatform: [String: Set<String>] = [:]
|
|
var frameworkFiles: [String] = []
|
|
|
|
var uuids: Set<String> = []
|
|
var project: PBXProj!
|
|
|
|
var carthageBuildPath: String {
|
|
return spec.options.carthageBuildPath ?? "Carthage/Build"
|
|
}
|
|
|
|
public init(spec: ProjectSpec, currentXcodeVersion: String) {
|
|
self.currentXcodeVersion = currentXcodeVersion
|
|
self.spec = spec
|
|
}
|
|
|
|
public func generateUUID<T: PBXObject>(_ element: T.Type, _ id: String) -> String {
|
|
var uuid: String = ""
|
|
var counter: UInt = 0
|
|
let className: String = String(describing: T.self).replacingOccurrences(of: "PBX", with: "")
|
|
let classAcronym = String(className.characters.filter { String($0).lowercased() != String($0) })
|
|
let stringID = String(abs(id.hashValue).description.characters.prefix(10 - classAcronym.characters.count))
|
|
repeat {
|
|
counter += 1
|
|
uuid = "\(classAcronym)\(stringID)\(String(format: "%02d", counter))"
|
|
} while (uuids.contains(uuid))
|
|
uuids.insert(uuid)
|
|
return uuid
|
|
}
|
|
|
|
func addObject(_ object: PBXObject) {
|
|
project.addObject(object)
|
|
}
|
|
|
|
public func generate() throws -> PBXProj {
|
|
uuids = []
|
|
project = PBXProj(objectVersion: 46, rootObject: generateUUID(PBXProject.self, spec.name))
|
|
|
|
for group in spec.fileGroups {
|
|
//TODO: call a seperate function that only creates groups not source files
|
|
let path = spec.basePath + group
|
|
_ = try getSourceFiles(path: path, children: try path.children())
|
|
}
|
|
|
|
let buildConfigs: [XCBuildConfiguration] = spec.configs.map { config in
|
|
let buildSettings = spec.getProjectBuildSettings(config: config)
|
|
var baseConfigurationReference: String?
|
|
if let configPath = spec.configFiles[config.name] {
|
|
baseConfigurationReference = getFileReference(path: spec.basePath + configPath, inPath: spec.basePath)
|
|
}
|
|
return XCBuildConfiguration(reference: generateUUID(XCBuildConfiguration.self, config.name), name: config.name, baseConfigurationReference: baseConfigurationReference, buildSettings: buildSettings)
|
|
}
|
|
|
|
let buildConfigList = XCConfigurationList(reference: generateUUID(XCConfigurationList.self, spec.name), buildConfigurations: buildConfigs.references, defaultConfigurationName: buildConfigs.first?.name ?? "", defaultConfigurationIsVisible: 0)
|
|
|
|
buildConfigs.forEach(addObject)
|
|
addObject(buildConfigList)
|
|
|
|
for target in spec.targets {
|
|
targetNativeReferences[target.name] = generateUUID(PBXNativeTarget.self, target.name)
|
|
|
|
let fileReference = PBXFileReference(reference: generateUUID(PBXFileReference.self, target.name), sourceTree: .buildProductsDir, explicitFileType: target.type.fileExtension, path: target.filename, includeInIndex: 0)
|
|
addObject(fileReference)
|
|
targetFileReferences[target.name] = fileReference.reference
|
|
|
|
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference.reference), fileRef: fileReference.reference)
|
|
addObject(buildFile)
|
|
targetBuildFiles[target.name] = buildFile
|
|
}
|
|
|
|
let targets = try spec.targets.map(generateTarget)
|
|
|
|
let productGroup = PBXGroup(reference: generateUUID(PBXGroup.self, "Products"), children: Array(targetFileReferences.values), sourceTree: .group, name: "Products")
|
|
addObject(productGroup)
|
|
topLevelGroups.append(productGroup)
|
|
|
|
if !carthageFrameworksByPlatform.isEmpty {
|
|
var platforms: [PBXGroup] = []
|
|
for (platform, fileReferences) in carthageFrameworksByPlatform {
|
|
let platformGroup = PBXGroup(reference: generateUUID(PBXGroup.self, platform), children: fileReferences.sorted(), sourceTree: .group, name: platform, path: platform)
|
|
addObject(platformGroup)
|
|
platforms.append(platformGroup)
|
|
}
|
|
let carthageGroup = PBXGroup(reference: generateUUID(PBXGroup.self, "Carthage"), children: platforms.references.sorted(), sourceTree: .group, name: "Carthage", path: carthageBuildPath)
|
|
addObject(carthageGroup)
|
|
frameworkFiles.append(carthageGroup.reference)
|
|
}
|
|
|
|
if !frameworkFiles.isEmpty {
|
|
let group = PBXGroup(reference: generateUUID(PBXGroup.self, "Frameworks"), children: frameworkFiles, sourceTree: .group, name: "Frameworks")
|
|
addObject(group)
|
|
topLevelGroups.append(group)
|
|
}
|
|
|
|
let mainGroup = PBXGroup(reference: generateUUID(PBXGroup.self, "Project"), children: topLevelGroups.references, sourceTree: .group)
|
|
addObject(mainGroup)
|
|
|
|
let knownRegions: [String] = ["en", "Base"]
|
|
let projectAttributes: [String: Any] = ["LastUpgradeCheck": currentXcodeVersion].merged(spec.attributes)
|
|
let root = PBXProject(name: spec.name,
|
|
reference: project.rootObject,
|
|
buildConfigurationList: buildConfigList.reference,
|
|
compatibilityVersion: "Xcode 3.2",
|
|
mainGroup: mainGroup.reference,
|
|
developmentRegion: "English",
|
|
knownRegions: knownRegions,
|
|
targets: targets.references,
|
|
attributes: projectAttributes)
|
|
project.projects.append(root)
|
|
|
|
return project
|
|
}
|
|
|
|
struct SourceFile {
|
|
let path: Path
|
|
let fileReference: String
|
|
let buildFile: PBXBuildFile
|
|
}
|
|
|
|
func generateSourceFile(path: Path) -> SourceFile {
|
|
let fileReference = fileReferencesByPath[path]!
|
|
var settings: [String: Any]?
|
|
if getBuildPhaseForPath(path) == .headers {
|
|
settings = ["ATTRIBUTES": ["Public"]]
|
|
}
|
|
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference), fileRef: fileReference, settings: settings)
|
|
return SourceFile(path: path, fileReference: fileReference, buildFile: buildFile)
|
|
}
|
|
|
|
func generateTarget(_ target: Target) throws -> PBXNativeTarget {
|
|
|
|
let carthageDependencies = getAllCarthageDependencies(target: target)
|
|
|
|
let sourceFiles = try getAllSourceFiles(sources: target.sources)
|
|
|
|
// find all Info.plist files
|
|
let infoPlists: [Path] = target.sources.map { spec.basePath + $0.path }.flatMap { (path) -> [Path] in
|
|
if path.isFile {
|
|
if path.lastComponent == "Info.plist" {
|
|
return [path]
|
|
}
|
|
} else {
|
|
if let children = try? path.recursiveChildren() {
|
|
return children.filter { $0.lastComponent == "Info.plist" }
|
|
}
|
|
}
|
|
return []
|
|
}
|
|
|
|
let configs: [XCBuildConfiguration] = spec.configs.map { config in
|
|
var buildSettings = spec.getTargetBuildSettings(target: target, config: config)
|
|
|
|
// automatically set INFOPLIST_FILE path
|
|
if let plistPath = infoPlists.first,
|
|
!spec.targetHasBuildSetting("INFOPLIST_FILE", basePath: spec.basePath, target: target, config: config) {
|
|
buildSettings["INFOPLIST_FILE"] = plistPath.byRemovingBase(path: spec.basePath)
|
|
}
|
|
|
|
// automatically calculate bundle id
|
|
if let bundleIdPrefix = spec.options.bundleIdPrefix,
|
|
!spec.targetHasBuildSetting("PRODUCT_BUNDLE_IDENTIFIER", basePath: spec.basePath, target: target, config: config) {
|
|
let characterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-.")).inverted
|
|
let escapedTargetName = target.name.replacingOccurrences(of: "_", with: "-").components(separatedBy: characterSet).joined(separator: "")
|
|
buildSettings["PRODUCT_BUNDLE_IDENTIFIER"] = bundleIdPrefix + "." + escapedTargetName
|
|
}
|
|
|
|
// automatically set test target name
|
|
if target.type == .uiTestBundle,
|
|
!spec.targetHasBuildSetting("TEST_TARGET_NAME", basePath: spec.basePath, target: target, config: config) {
|
|
for dependency in target.dependencies {
|
|
if dependency.type == .target,
|
|
let dependencyTarget = spec.getTarget(dependency.reference),
|
|
dependencyTarget.type == .application {
|
|
buildSettings["TEST_TARGET_NAME"] = dependencyTarget.name
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// set Carthage search paths
|
|
if !carthageDependencies.isEmpty {
|
|
let frameworkSearchPaths = "FRAMEWORK_SEARCH_PATHS"
|
|
let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + getCarthageBuildPath(platform: target.platform)
|
|
var newSettings: [String] = []
|
|
if var array = buildSettings[frameworkSearchPaths] as? [String] {
|
|
array.append(carthagePlatformBuildPath)
|
|
buildSettings[frameworkSearchPaths] = array
|
|
} else if let string = buildSettings[frameworkSearchPaths] as? String {
|
|
buildSettings[frameworkSearchPaths] = [string, carthagePlatformBuildPath]
|
|
} else {
|
|
buildSettings[frameworkSearchPaths] = ["$(inherited)", carthagePlatformBuildPath]
|
|
}
|
|
}
|
|
|
|
var baseConfigurationReference: String?
|
|
if let configPath = target.configFiles[config.name] {
|
|
baseConfigurationReference = getFileReference(path: spec.basePath + configPath, inPath: spec.basePath)
|
|
}
|
|
return XCBuildConfiguration(reference: generateUUID(XCBuildConfiguration.self, config.name + target.name), name: config.name, baseConfigurationReference: baseConfigurationReference, buildSettings: buildSettings)
|
|
}
|
|
configs.forEach(addObject)
|
|
let buildConfigList = XCConfigurationList(reference: generateUUID(XCConfigurationList.self, target.name), buildConfigurations: configs.references, defaultConfigurationName: "")
|
|
addObject(buildConfigList)
|
|
|
|
var dependencies: [String] = []
|
|
var targetFrameworkBuildFiles: [String] = []
|
|
var copyFrameworksReferences: [String] = []
|
|
var copyResourcesReferences: [String] = []
|
|
var copyWatchReferences: [String] = []
|
|
var extensions: [String] = []
|
|
|
|
for dependency in target.dependencies {
|
|
|
|
let embed = dependency.embed ?? (target.type.isApp ? true : false)
|
|
switch dependency.type {
|
|
case .target:
|
|
let dependencyTargetName = dependency.reference
|
|
guard let dependencyTarget = spec.getTarget(dependencyTargetName) else { continue }
|
|
let dependencyFileReference = targetFileReferences[dependencyTargetName]!
|
|
|
|
let targetProxy = PBXContainerItemProxy(reference: generateUUID(PBXContainerItemProxy.self, target.name), containerPortal: project.rootObject, remoteGlobalIDString: targetNativeReferences[dependencyTargetName]!, proxyType: .nativeTarget, remoteInfo: dependencyTargetName)
|
|
let targetDependency = PBXTargetDependency(reference: generateUUID(PBXTargetDependency.self, dependencyTargetName + target.name), target: targetNativeReferences[dependencyTargetName]!, targetProxy: targetProxy.reference)
|
|
|
|
addObject(targetProxy)
|
|
addObject(targetDependency)
|
|
dependencies.append(targetDependency.reference)
|
|
|
|
if (dependencyTarget.type.isLibrary || dependencyTarget.type.isFramework) && dependency.link {
|
|
let dependencyBuildFile = targetBuildFiles[dependencyTargetName]!
|
|
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, dependencyBuildFile.reference + target.name), fileRef: dependencyBuildFile.fileRef!)
|
|
addObject(buildFile)
|
|
targetFrameworkBuildFiles.append(buildFile.reference)
|
|
}
|
|
|
|
if embed && !dependencyTarget.type.isLibrary {
|
|
|
|
let embedSettings = dependency.buildSettings
|
|
let embedFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, dependencyFileReference + target.name), fileRef: dependencyFileReference, settings: embedSettings)
|
|
addObject(embedFile)
|
|
|
|
if dependencyTarget.type.isExtension {
|
|
// embed app extension
|
|
extensions.append(embedFile.reference)
|
|
} else if dependencyTarget.type.isFramework {
|
|
copyFrameworksReferences.append(embedFile.reference)
|
|
} else if dependencyTarget.type.isApp && dependencyTarget.platform == .watchOS {
|
|
copyWatchReferences.append(embedFile.reference)
|
|
} else {
|
|
copyResourcesReferences.append(embedFile.reference)
|
|
}
|
|
}
|
|
|
|
case .framework:
|
|
|
|
let fileReference = getFileReference(path: Path(dependency.reference), inPath: spec.basePath)
|
|
|
|
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference + target.name), fileRef: fileReference)
|
|
addObject(buildFile)
|
|
|
|
targetFrameworkBuildFiles.append(buildFile.reference)
|
|
if !frameworkFiles.contains(fileReference) {
|
|
frameworkFiles.append(fileReference)
|
|
}
|
|
|
|
if embed {
|
|
let embedFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference + target.name), fileRef: fileReference, settings: dependency.buildSettings)
|
|
addObject(embedFile)
|
|
copyFrameworksReferences.append(embedFile.reference)
|
|
}
|
|
case .carthage:
|
|
var platformPath = Path(getCarthageBuildPath(platform: target.platform))
|
|
var frameworkPath = platformPath + dependency.reference
|
|
if frameworkPath.extension == nil {
|
|
frameworkPath = Path(frameworkPath.string + ".framework")
|
|
}
|
|
let fileReference = getFileReference(path: frameworkPath, inPath: platformPath)
|
|
|
|
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference + target.name), fileRef: fileReference)
|
|
addObject(buildFile)
|
|
carthageFrameworksByPlatform[target.platform.carthageDirectoryName, default: []].insert(fileReference)
|
|
|
|
targetFrameworkBuildFiles.append(buildFile.reference)
|
|
if target.platform == .macOS && target.type.isApp {
|
|
let embedFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference + target.name), fileRef: fileReference, settings: dependency.buildSettings)
|
|
addObject(embedFile)
|
|
copyFrameworksReferences.append(embedFile.reference)
|
|
}
|
|
}
|
|
}
|
|
|
|
let fileReference = targetFileReferences[target.name]!
|
|
var buildPhases: [String] = []
|
|
|
|
func getBuildFilesForPhase(_ buildPhase: BuildPhase) -> [String] {
|
|
let files = sourceFiles.filter { getBuildPhaseForPath($0.path) == buildPhase }
|
|
files.forEach { addObject($0.buildFile) }
|
|
return files.map { $0.buildFile.reference }
|
|
}
|
|
|
|
func getBuildScript(buildScript: BuildScript) throws -> PBXShellScriptBuildPhase {
|
|
|
|
var shellScript: String
|
|
switch buildScript.script {
|
|
case let .path(path):
|
|
shellScript = try (spec.basePath + path).read()
|
|
case let .script(script):
|
|
shellScript = script
|
|
}
|
|
shellScript = shellScript.replacingOccurrences(of: "\"", with: "\\\"") // TODO: remove when xcodeproj escaped values
|
|
let shellScriptPhase = PBXShellScriptBuildPhase(
|
|
reference: generateUUID(PBXShellScriptBuildPhase.self, String(describing: buildScript.name) + shellScript + target.name),
|
|
files: [],
|
|
name: buildScript.name ?? "Run Script",
|
|
inputPaths: buildScript.inputFiles,
|
|
outputPaths: buildScript.outputFiles,
|
|
shellPath: buildScript.shell ?? "/bin/sh",
|
|
shellScript: shellScript)
|
|
shellScriptPhase.runOnlyForDeploymentPostprocessing = buildScript.runOnlyWhenInstalling ? 1 : 0
|
|
addObject(shellScriptPhase)
|
|
buildPhases.append(shellScriptPhase.reference)
|
|
return shellScriptPhase
|
|
}
|
|
|
|
_ = try target.prebuildScripts.map(getBuildScript)
|
|
|
|
let sourcesBuildPhase = PBXSourcesBuildPhase(reference: generateUUID(PBXSourcesBuildPhase.self, target.name), files: getBuildFilesForPhase(.sources))
|
|
addObject(sourcesBuildPhase)
|
|
buildPhases.append(sourcesBuildPhase.reference)
|
|
|
|
let resourcesBuildPhase = PBXResourcesBuildPhase(reference: generateUUID(PBXResourcesBuildPhase.self, target.name), files: getBuildFilesForPhase(.resources) + copyResourcesReferences)
|
|
addObject(resourcesBuildPhase)
|
|
buildPhases.append(resourcesBuildPhase.reference)
|
|
|
|
if target.type == .framework || target.type == .dynamicLibrary {
|
|
let headersBuildPhase = PBXHeadersBuildPhase(reference: generateUUID(PBXHeadersBuildPhase.self, target.name), files: getBuildFilesForPhase(.headers))
|
|
addObject(headersBuildPhase)
|
|
buildPhases.append(headersBuildPhase.reference)
|
|
}
|
|
|
|
if !targetFrameworkBuildFiles.isEmpty {
|
|
|
|
let frameworkBuildPhase = PBXFrameworksBuildPhase(
|
|
reference: generateUUID(PBXFrameworksBuildPhase.self, target.name),
|
|
files: targetFrameworkBuildFiles,
|
|
runOnlyForDeploymentPostprocessing: 0)
|
|
|
|
addObject(frameworkBuildPhase)
|
|
buildPhases.append(frameworkBuildPhase.reference)
|
|
}
|
|
|
|
if !extensions.isEmpty {
|
|
|
|
let copyFilesPhase = PBXCopyFilesBuildPhase(
|
|
reference: generateUUID(PBXCopyFilesBuildPhase.self, "embed app extensions" + target.name),
|
|
dstPath: "",
|
|
dstSubfolderSpec: .plugins,
|
|
files: extensions)
|
|
|
|
addObject(copyFilesPhase)
|
|
buildPhases.append(copyFilesPhase.reference)
|
|
}
|
|
|
|
if !copyFrameworksReferences.isEmpty {
|
|
|
|
let copyFilesPhase = PBXCopyFilesBuildPhase(
|
|
reference: generateUUID(PBXCopyFilesBuildPhase.self, "embed frameworks" + target.name),
|
|
dstPath: "",
|
|
dstSubfolderSpec: .frameworks,
|
|
files: copyFrameworksReferences)
|
|
|
|
addObject(copyFilesPhase)
|
|
buildPhases.append(copyFilesPhase.reference)
|
|
}
|
|
|
|
if !copyWatchReferences.isEmpty {
|
|
|
|
let copyFilesPhase = PBXCopyFilesBuildPhase(
|
|
reference: generateUUID(PBXCopyFilesBuildPhase.self, "embed watch content" + target.name),
|
|
dstPath: "$(CONTENTS_FOLDER_PATH)/Watch",
|
|
dstSubfolderSpec: .productsDirectory,
|
|
files: copyWatchReferences)
|
|
|
|
addObject(copyFilesPhase)
|
|
buildPhases.append(copyFilesPhase.reference)
|
|
}
|
|
|
|
let carthageFrameworksToEmbed = Array(Set(carthageDependencies
|
|
.filter { $0.embed ?? true }
|
|
.map { $0.reference }))
|
|
.sorted()
|
|
|
|
if !carthageFrameworksToEmbed.isEmpty {
|
|
|
|
if target.type.isApp && target.platform != .macOS {
|
|
let inputPaths = carthageFrameworksToEmbed.map { "$(SRCROOT)/\(carthageBuildPath)/\(target.platform)/\($0)\($0.contains(".") ? "" : ".framework")" }
|
|
let outputPaths = carthageFrameworksToEmbed.map { "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\($0)\($0.contains(".") ? "" : ".framework")" }
|
|
let carthageScript = PBXShellScriptBuildPhase(reference: generateUUID(PBXShellScriptBuildPhase.self, "Carthage" + target.name), files: [], name: "Carthage", inputPaths: inputPaths, outputPaths: outputPaths, shellPath: "/bin/sh", shellScript: "/usr/local/bin/carthage copy-frameworks\n")
|
|
addObject(carthageScript)
|
|
buildPhases.append(carthageScript.reference)
|
|
}
|
|
}
|
|
|
|
_ = try target.postbuildScripts.map(getBuildScript)
|
|
|
|
let nativeTarget = PBXNativeTarget(
|
|
reference: targetNativeReferences[target.name]!,
|
|
name: target.name,
|
|
buildConfigurationList: buildConfigList.reference,
|
|
buildPhases: buildPhases,
|
|
buildRules: [],
|
|
dependencies: dependencies,
|
|
productReference: fileReference,
|
|
productType: target.type)
|
|
addObject(nativeTarget)
|
|
return nativeTarget
|
|
}
|
|
|
|
func getCarthageBuildPath(platform: Platform) -> String {
|
|
|
|
let carthagePath = Path(carthageBuildPath)
|
|
let platformName = platform.carthageDirectoryName
|
|
return "\(carthagePath)/\(platformName)"
|
|
}
|
|
|
|
func getAllCarthageDependencies(target: Target) -> [Dependency] {
|
|
var frameworks: [Dependency] = []
|
|
for dependency in target.dependencies {
|
|
switch dependency.type {
|
|
case .carthage:
|
|
frameworks.append(dependency)
|
|
case .target:
|
|
if let target = spec.getTarget(dependency.reference) {
|
|
frameworks += getAllCarthageDependencies(target: target)
|
|
}
|
|
default: break
|
|
}
|
|
}
|
|
return frameworks
|
|
}
|
|
|
|
func getBuildPhaseForPath(_ path: Path) -> BuildPhase? {
|
|
if path.lastComponent == "Info.plist" {
|
|
return nil
|
|
}
|
|
if let fileExtension = path.extension {
|
|
switch fileExtension {
|
|
case "swift", "m", "cpp": return .sources
|
|
case "h", "hh", "hpp", "ipp", "tpp", "hxx", "def": return .headers
|
|
case "xcconfig", "entitlements", "gpx", "lproj", "apns": return nil
|
|
default: return .resources
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getFileReference(path: Path, inPath: Path) -> String {
|
|
if let fileReference = fileReferencesByPath[path] {
|
|
return fileReference
|
|
} else {
|
|
let fileReference = PBXFileReference(reference: generateUUID(PBXFileReference.self, path.lastComponent), sourceTree: .group, path: path.byRemovingBase(path: inPath).string)
|
|
addObject(fileReference)
|
|
fileReferencesByPath[path] = fileReference.reference
|
|
return fileReference.reference
|
|
}
|
|
}
|
|
|
|
func getAllSourceFiles(sources: [Source]) throws -> [SourceFile] {
|
|
let sourcePaths = sources.map{ spec.basePath + $0.path }
|
|
|
|
let (files, dirs) = (sourcePaths.filter{ $0.isFile }, sourcePaths.filter{ $0.isDirectory })
|
|
let filesByParent: [Path: [Path]] = files.reduce([:]) { acc, file in
|
|
var mut = acc
|
|
let group = file.parent()
|
|
mut[group, default: []].append(file)
|
|
return mut
|
|
}
|
|
|
|
let fromFiles = try filesByParent.map{ parent, files in
|
|
try getSourceFiles(path: parent, children: files)
|
|
}
|
|
let fromDirs = try dirs.map{ dir in
|
|
try getSourceFiles(path: dir, children: try dir.children())
|
|
}
|
|
|
|
return (fromFiles + fromDirs).flatMap{ $0.sourceFiles }
|
|
}
|
|
|
|
func getSourceFiles(path: Path, children: [Path], depth: Int = 0) throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) {
|
|
|
|
let excludedFiles: [String] = [".DS_Store"]
|
|
|
|
let directories = children
|
|
.filter { $0.isDirectory && $0.extension == nil && $0.extension != "lproj" }
|
|
.sorted { $0.lastComponent < $1.lastComponent }
|
|
|
|
let filePaths = children
|
|
.filter { $0.isFile || $0.extension != nil && $0.extension != "lproj" }
|
|
.filter { !excludedFiles.contains($0.lastComponent) }
|
|
.sorted { $0.lastComponent < $1.lastComponent }
|
|
|
|
let localisedDirectories = children
|
|
.filter { $0.extension == "lproj" }
|
|
.sorted { $0.lastComponent < $1.lastComponent }
|
|
|
|
var groupChildren: [String] = filePaths.map { getFileReference(path: $0, inPath: path) }
|
|
var allSourceFiles: [SourceFile] = filePaths.map { generateSourceFile(path: $0) }
|
|
var groups: [PBXGroup] = []
|
|
|
|
for path in directories {
|
|
let subGroups = try getSourceFiles(path: path, children: try path.children(), depth: depth + 1)
|
|
allSourceFiles += subGroups.sourceFiles
|
|
groupChildren.append(subGroups.groups.first!.reference)
|
|
groups += subGroups.groups
|
|
}
|
|
|
|
// create variant groups of the base localisation first
|
|
var baseLocalisationVariantGroups: [PBXVariantGroup] = []
|
|
if let baseLocalisedDirectory = localisedDirectories.first(where: { $0.lastComponent == "Base.lproj" }) {
|
|
for path in try baseLocalisedDirectory.children() {
|
|
let filePath = "\(baseLocalisedDirectory.lastComponent)/\(path.lastComponent)"
|
|
|
|
let variantGroup: PBXVariantGroup
|
|
if let cachedGroup = variantGroupsByPath[path] {
|
|
variantGroup = cachedGroup
|
|
} else {
|
|
variantGroup = PBXVariantGroup(reference: generateUUID(PBXVariantGroup.self, filePath),
|
|
children: [],
|
|
name: path.lastComponent,
|
|
sourceTree: .group)
|
|
variantGroupsByPath[path] = variantGroup
|
|
|
|
addObject(variantGroup)
|
|
groupChildren.append(variantGroup.reference)
|
|
}
|
|
|
|
baseLocalisationVariantGroups.append(variantGroup)
|
|
|
|
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, variantGroup.reference), fileRef: variantGroup.reference, settings: nil)
|
|
allSourceFiles.append(SourceFile(path: path, fileReference: variantGroup.reference, buildFile: buildFile))
|
|
}
|
|
}
|
|
|
|
// add references to localised resources into base localisation variant groups
|
|
for localisedDirectory in localisedDirectories {
|
|
let localisationName = localisedDirectory.lastComponentWithoutExtension
|
|
for path in try localisedDirectory.children().sorted { $0.lastComponent < $1.lastComponent } {
|
|
let filePath = "\(localisedDirectory.lastComponent)/\(path.lastComponent)"
|
|
|
|
// find base localisation variant group
|
|
let name = path.lastComponentWithoutExtension
|
|
let variantGroup = baseLocalisationVariantGroups.first { Path($0.name!).lastComponentWithoutExtension == name }
|
|
|
|
let fileReference: String
|
|
if let cachedFileReference = fileReferencesByPath[path] {
|
|
fileReference = cachedFileReference
|
|
} else {
|
|
let reference = PBXFileReference(reference: generateUUID(PBXFileReference.self, path.lastComponent),
|
|
sourceTree: .group,
|
|
name: variantGroup != nil ? localisationName : path.lastComponent,
|
|
path: filePath)
|
|
addObject(reference)
|
|
fileReference = reference.reference
|
|
fileReferencesByPath[path] = fileReference
|
|
}
|
|
|
|
if let variantGroup = variantGroup {
|
|
if !variantGroup.children.contains(fileReference) {
|
|
variantGroup.children.append(fileReference)
|
|
}
|
|
} else {
|
|
// add SourceFile to group if there is no Base.lproj directory
|
|
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference),
|
|
fileRef: fileReference,
|
|
settings: nil)
|
|
allSourceFiles.append(SourceFile(path: path, fileReference: fileReference, buildFile: buildFile))
|
|
groupChildren.append(fileReference)
|
|
}
|
|
}
|
|
}
|
|
|
|
let groupPath: String = depth == 0 ? path.byRemovingBase(path: spec.basePath).string : path.lastComponent
|
|
let group: PBXGroup
|
|
if let cachedGroup = groupsByPath[path] {
|
|
group = cachedGroup
|
|
} else {
|
|
group = PBXGroup(reference: generateUUID(PBXGroup.self, path.lastComponent), children: groupChildren, sourceTree: .group, name: path.lastComponent, path: groupPath)
|
|
addObject(group)
|
|
if depth == 0 {
|
|
topLevelGroups.append(group)
|
|
}
|
|
groupsByPath[path] = group
|
|
}
|
|
groups.insert(group, at: 0)
|
|
return (allSourceFiles, groups)
|
|
}
|
|
}
|