XcodeGen/Sources/XcodeGenKit/PBXProjGenerator.swift

1041 lines
44 KiB
Swift
Raw Normal View History

2017-07-23 21:58:03 +03:00
import Foundation
2018-01-24 10:22:46 +03:00
import JSONUtilities
2017-07-23 21:58:03 +03:00
import PathKit
2018-01-24 10:22:46 +03:00
import ProjectSpec
2017-10-01 12:42:07 +03:00
import xcproj
2017-07-23 21:58:03 +03:00
import Yams
public class PBXProjGenerator {
2018-04-12 16:13:40 +03:00
let project: Project
2017-07-23 21:58:03 +03:00
2018-04-12 16:13:40 +03:00
let pbxProj: PBXProj
2018-01-24 10:22:46 +03:00
var sourceGenerator: SourceGenerator!
2017-07-23 21:58:03 +03:00
2018-01-24 10:22:46 +03:00
var targetObjects: [String: ObjectReference<PBXTarget>] = [:]
2018-07-22 13:27:58 +03:00
var targetAggregateObjects: [String: ObjectReference<PBXAggregateTarget>] = [:]
2018-01-24 10:22:46 +03:00
var targetBuildFiles: [String: ObjectReference<PBXBuildFile>] = [:]
2017-07-23 21:58:03 +03:00
var targetFileReferences: [String: String] = [:]
var carthageFrameworksByPlatform: [String: Set<String>] = [:]
2017-07-25 02:02:54 +03:00
var frameworkFiles: [String] = []
2017-07-23 21:58:03 +03:00
var generated = false
2017-07-23 21:58:03 +03:00
2017-08-25 17:22:40 +03:00
var carthageBuildPath: String {
2018-04-12 16:13:40 +03:00
return project.options.carthageBuildPath ?? "Carthage/Build"
2017-08-25 17:22:40 +03:00
}
2018-04-12 16:13:40 +03:00
public init(project: Project) {
self.project = project
pbxProj = PBXProj(rootObject: "", objectVersion: 46)
sourceGenerator = SourceGenerator(project: project) { [unowned self] id, object in
2018-01-24 10:22:46 +03:00
self.addObject(id: id, object)
}
2017-07-23 21:58:03 +03:00
}
2018-01-24 10:22:46 +03:00
func addObject(id: String, _ object: PBXObject) -> String {
2018-04-12 16:13:40 +03:00
let reference = pbxProj.objects.generateReference(object, id)
pbxProj.objects.addObject(object, reference: reference)
2018-01-24 10:22:46 +03:00
return reference
}
func createObject<T>(id: String, _ object: T) -> ObjectReference<T> {
let reference = addObject(id: id, object)
return ObjectReference(reference: reference, object: object)
2017-08-25 14:48:07 +03:00
}
2017-07-23 21:58:03 +03:00
public func generate() throws -> PBXProj {
if generated {
fatalError("Cannot use PBXProjGenerator to generate more than once")
}
generated = true
2018-01-24 10:22:46 +03:00
2018-04-12 16:13:40 +03:00
for group in project.fileGroups {
try sourceGenerator.getFileGroups(path: group)
}
2018-04-12 16:13:40 +03:00
let buildConfigs: [ObjectReference<XCBuildConfiguration>] = project.configs.map { config in
let buildSettings = project.getProjectBuildSettings(config: config)
2017-09-23 23:48:50 +03:00
var baseConfigurationReference: String?
2018-04-12 16:13:40 +03:00
if let configPath = project.configFiles[config.name] {
baseConfigurationReference = sourceGenerator.getContainedFileReference(path: project.basePath + configPath)
2017-09-23 23:48:50 +03:00
}
2018-01-24 10:22:46 +03:00
return createObject(
id: config.name,
XCBuildConfiguration(
name: config.name,
baseConfigurationReference: baseConfigurationReference,
buildSettings: buildSettings
)
2017-12-26 13:47:18 +03:00
)
2017-07-23 21:58:03 +03:00
}
2017-07-26 13:51:55 +03:00
2018-04-12 16:13:40 +03:00
let configName = project.options.defaultConfig ?? buildConfigs.first?.object.name ?? ""
2018-01-24 10:22:46 +03:00
let buildConfigList = createObject(
2018-04-12 16:13:40 +03:00
id: project.name,
2018-01-24 10:22:46 +03:00
XCConfigurationList(
buildConfigurations: buildConfigs.map { $0.reference },
defaultConfigurationName: configName
2018-01-24 10:22:46 +03:00
)
)
var derivedGroups: [ObjectReference<PBXGroup>] = []
2018-01-24 10:22:46 +03:00
let mainGroup = createObject(
id: "Project",
PBXGroup(
children: [],
sourceTree: .group,
2018-04-12 16:13:40 +03:00
usesTabs: project.options.usesTabs,
indentWidth: project.options.indentWidth,
tabWidth: project.options.tabWidth
2018-01-24 10:22:46 +03:00
)
)
2018-04-12 16:13:40 +03:00
let pbxProject = createObject(
id: project.name,
2018-01-24 10:22:46 +03:00
PBXProject(
2018-04-12 16:13:40 +03:00
name: project.name,
2018-01-24 10:22:46 +03:00
buildConfigurationList: buildConfigList.reference,
compatibilityVersion: "Xcode 3.2",
mainGroup: mainGroup.reference,
2018-04-12 16:13:40 +03:00
developmentRegion: project.options.developmentLanguage ?? "en"
2018-01-24 10:22:46 +03:00
)
2017-12-26 13:47:18 +03:00
)
2017-07-23 21:58:03 +03:00
2018-04-12 16:13:40 +03:00
pbxProj.rootObject = pbxProject.reference
2017-07-23 21:58:03 +03:00
2018-04-12 16:13:40 +03:00
for target in project.targets {
2018-01-24 10:22:46 +03:00
let targetObject: PBXTarget
if target.isLegacy {
targetObject = PBXLegacyTarget(
name: target.name,
buildToolPath: target.legacy?.toolPath,
buildArgumentsString: target.legacy?.arguments,
passBuildSettingsInEnvironment: target.legacy?.passSettings ?? false,
buildWorkingDirectory: target.legacy?.workingDirectory
)
} else {
targetObject = PBXNativeTarget(name: target.name)
}
targetObjects[target.name] = createObject(id: target.name, targetObject)
2017-12-26 13:47:18 +03:00
var explicitFileType: String?
var lastKnownFileType: String?
let fileType = PBXFileReference.fileType(path: Path(target.filename))
if target.platform == .macOS || target.platform == .watchOS || target.type == .framework {
explicitFileType = fileType
} else {
lastKnownFileType = fileType
}
if !target.isLegacy {
let fileReference = createObject(
id: target.name,
PBXFileReference(
sourceTree: .buildProductsDir,
explicitFileType: explicitFileType,
lastKnownFileType: lastKnownFileType,
path: target.filename,
includeInIndex: false
)
2018-01-24 10:22:46 +03:00
)
2017-07-23 21:58:03 +03:00
targetFileReferences[target.name] = fileReference.reference
targetBuildFiles[target.name] = createObject(
id: fileReference.reference,
2018-06-08 08:21:21 +03:00
PBXBuildFile(fileRef: fileReference.reference)
)
}
2017-07-23 21:58:03 +03:00
}
for target in project.aggregateTargets {
let aggregateTarget = createObject(
id: target.name,
PBXAggregateTarget(
name: target.name,
productName: target.name
)
)
targetAggregateObjects[target.name] = aggregateTarget
}
2018-04-12 16:13:40 +03:00
try project.targets.forEach(generateTarget)
2018-07-22 10:37:16 +03:00
try project.aggregateTargets.forEach(generateAggregateTarget)
2017-07-23 21:58:03 +03:00
2018-01-24 10:22:46 +03:00
let productGroup = createObject(
id: "Products",
PBXGroup(
children: Array(targetFileReferences.values),
sourceTree: .group,
name: "Products"
)
2017-12-26 13:47:18 +03:00
)
derivedGroups.append(productGroup)
2017-07-23 21:58:03 +03:00
2017-07-25 01:32:36 +03:00
if !carthageFrameworksByPlatform.isEmpty {
var platforms: [PBXGroup] = []
2018-01-24 10:22:46 +03:00
var platformReferences: [String] = []
2017-07-25 01:32:36 +03:00
for (platform, fileReferences) in carthageFrameworksByPlatform {
2018-01-24 10:22:46 +03:00
let platformGroup: ObjectReference<PBXGroup> = createObject(
id: "Carthage" + platform,
PBXGroup(
children: fileReferences.sorted(),
sourceTree: .group,
path: platform
)
2017-12-26 13:47:18 +03:00
)
2018-01-24 10:22:46 +03:00
platformReferences.append(platformGroup.reference)
platforms.append(platformGroup.object)
2017-07-25 01:32:36 +03:00
}
2018-01-24 10:22:46 +03:00
let carthageGroup = createObject(
id: "Carthage",
PBXGroup(
children: platformReferences.sorted(),
sourceTree: .group,
name: "Carthage",
path: carthageBuildPath
)
2017-12-26 13:47:18 +03:00
)
2017-08-26 23:15:41 +03:00
frameworkFiles.append(carthageGroup.reference)
2017-07-25 01:32:36 +03:00
}
2017-07-25 02:02:54 +03:00
if !frameworkFiles.isEmpty {
2018-01-24 10:22:46 +03:00
let group = createObject(
id: "Frameworks",
PBXGroup(
children: frameworkFiles,
sourceTree: .group,
name: "Frameworks"
)
2017-12-26 13:47:18 +03:00
)
derivedGroups.append(group)
}
mainGroup.object.children = Array(sourceGenerator.rootGroups)
sortGroups(group: mainGroup)
// add derived groups at the end
derivedGroups.forEach(sortGroups)
mainGroup.object.children += derivedGroups
.sorted { $0.object.nameOrPath.localizedStandardCompare($1.object.nameOrPath) == .orderedAscending }
.map { $0.reference }
2018-04-12 16:13:40 +03:00
let projectAttributes: [String: Any] = ["LastUpgradeCheck": project.xcodeVersion]
.merged(project.attributes)
2018-01-24 10:22:46 +03:00
.merged(generateTargetAttributes() ?? [:])
let knownRegions = sourceGenerator.knownRegions.sorted()
pbxProject.object.knownRegions = knownRegions.isEmpty ? ["en"] : knownRegions
2018-07-24 14:22:06 +03:00
let allTargets: [ObjectReference<PBXTarget>] = Array(targetObjects.values) + Array(targetAggregateObjects.values.map { ObjectReference(reference: $0.reference, object: $0.object) })
2018-08-19 07:22:42 +03:00
pbxProject.object.targets = allTargets
.sorted { $0.object.name < $1.object.name }
.map { $0.reference }
2018-04-12 16:13:40 +03:00
pbxProject.object.attributes = projectAttributes
2017-07-23 21:58:03 +03:00
2018-04-12 16:13:40 +03:00
return pbxProj
2017-07-23 21:58:03 +03:00
}
2018-01-24 10:22:46 +03:00
2018-07-22 13:27:58 +03:00
func generateAggregateTarget(_ target: AggregateTarget) throws {
2018-07-22 10:37:16 +03:00
let aggregateTarget = targetAggregateObjects[target.name]!.object
2018-07-22 10:37:16 +03:00
let configs: [ObjectReference<XCBuildConfiguration>] = project.configs.map { config in
let buildSettings = project.getBuildSettings(settings: target.settings, config: config)
var baseConfigurationReference: String?
if let configPath = target.configFiles[config.name] {
baseConfigurationReference = sourceGenerator.getContainedFileReference(path: project.basePath + configPath)
}
let buildConfig = XCBuildConfiguration(
name: config.name,
baseConfigurationReference: baseConfigurationReference,
buildSettings: buildSettings
)
return createObject(id: config.name + target.name, buildConfig)
}
let dependencies: [String] = target.targets.map { generateTargetDependency(from: target.name, to: $0).reference }
let buildConfigList = createObject(id: target.name, XCConfigurationList(
buildConfigurations: configs.map { $0.reference },
defaultConfigurationName: ""
))
var buildPhases: [String] = []
buildPhases += try target.buildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
aggregateTarget.buildPhases = buildPhases
aggregateTarget.buildConfigurationList = buildConfigList.reference
aggregateTarget.dependencies = dependencies
2018-07-22 10:37:16 +03:00
}
func generateTargetDependency(from: String, to target: String) -> ObjectReference<PBXTargetDependency> {
guard let targetReference = targetObjects[target]?.reference ?? targetAggregateObjects[target]?.reference else {
fatalError("target not found")
}
2018-07-22 10:37:16 +03:00
let targetProxy = createObject(
id: "\(from)-\(target)",
PBXContainerItemProxy(
containerPortal: pbxProj.rootObject,
remoteGlobalIDString: targetReference,
2018-07-22 10:37:16 +03:00
proxyType: .nativeTarget,
remoteInfo: target
)
)
let targetDependency = createObject(
id: "\(from)-\(target)",
PBXTargetDependency(
target: targetReference,
2018-07-22 10:37:16 +03:00
targetProxy: targetProxy.reference
)
)
return targetDependency
}
func generateBuildScript(targetName: String, buildScript: BuildScript) throws -> String {
let shellScript: String
switch buildScript.script {
case let .path(path):
shellScript = try (project.basePath + path).read()
case let .script(script):
shellScript = script
}
let shellScriptPhase = PBXShellScriptBuildPhase(
files: [],
name: buildScript.name ?? "Run Script",
inputPaths: buildScript.inputFiles,
outputPaths: buildScript.outputFiles,
shellPath: buildScript.shell ?? "/bin/sh",
shellScript: shellScript
)
shellScriptPhase.runOnlyForDeploymentPostprocessing = buildScript.runOnlyWhenInstalling
shellScriptPhase.showEnvVarsInLog = buildScript.showEnvVars
return createObject(id: String(describing: buildScript.name) + shellScript + targetName, shellScriptPhase).reference
}
func generateCopyFiles(targetName: String, copyFiles: TargetSource.BuildPhase.CopyFilesSettings, buildPhaseFiles: [String]) -> String {
return createObject(
id: "copy files" + copyFiles.destination.rawValue + copyFiles.subpath + targetName,
PBXCopyFilesBuildPhase(
dstPath: copyFiles.subpath,
dstSubfolderSpec: copyFiles.destination.destination,
files: buildPhaseFiles
)
).reference
}
2018-07-22 10:37:16 +03:00
func generateTargetAttributes() -> [String: Any]? {
2018-01-24 10:22:46 +03:00
2018-03-27 06:21:15 +03:00
var targetAttributes: [String: [String: Any]] = [:]
2018-01-24 10:22:46 +03:00
2018-04-12 16:13:40 +03:00
let uiTestTargets = pbxProj.objects.nativeTargets.objectReferences.filter { $0.object.productType == .uiTestBundle }
for uiTestTarget in uiTestTargets {
2018-03-27 09:18:29 +03:00
2018-03-27 06:21:15 +03:00
// look up TEST_TARGET_NAME build setting
func testTargetName(_ target: PBXTarget) -> String? {
guard let configurationList = target.buildConfigurationList else { return nil }
2018-04-12 16:13:40 +03:00
guard let buildConfigurationReferences = self.pbxProj.objects.configurationLists[configurationList]?.buildConfigurations else { return nil }
2018-03-27 09:18:29 +03:00
2018-03-27 06:21:15 +03:00
let configs = buildConfigurationReferences
2018-05-12 08:10:18 +03:00
.compactMap { ref in self.pbxProj.objects.buildConfigurations[ref] }
2018-03-27 09:18:29 +03:00
2018-03-27 06:21:15 +03:00
return configs
2018-05-12 08:10:18 +03:00
.compactMap { $0.buildSettings["TEST_TARGET_NAME"] as? String }
2018-03-27 06:21:15 +03:00
.first
}
2018-03-27 09:18:29 +03:00
2018-01-24 10:22:46 +03:00
guard let name = testTargetName(uiTestTarget.object) else { continue }
2018-04-12 16:13:40 +03:00
guard let target = self.pbxProj.objects.targets(named: name).first else { continue }
2018-01-24 10:22:46 +03:00
2018-03-27 06:21:15 +03:00
targetAttributes[uiTestTarget.reference, default: [:]].merge(["TestTargetID": target.reference])
}
2018-03-27 09:18:29 +03:00
2018-07-22 13:27:58 +03:00
func generateTargetAttributes(_ target: ProjectTarget, targetReference: String) {
if !target.attributes.isEmpty {
targetAttributes[targetReference, default: [:]].merge(target.attributes)
}
2018-03-27 09:18:29 +03:00
func getSingleBuildSetting(_ setting: String) -> String? {
2018-05-12 08:10:18 +03:00
let settings = project.configs.compactMap {
2018-04-12 16:13:40 +03:00
project.getCombinedBuildSettings(basePath: project.basePath, target: target, config: $0)[setting] as? String
}
2018-04-12 16:13:40 +03:00
guard settings.count == project.configs.count,
let firstSetting = settings.first,
2018-03-27 09:18:29 +03:00
settings.filter({ $0 == firstSetting }).count == settings.count else {
return nil
}
return firstSetting
}
2018-03-27 09:18:29 +03:00
func setTargetAttribute(attribute: String, buildSetting: String) {
if let setting = getSingleBuildSetting(buildSetting) {
targetAttributes[targetReference, default: [:]].merge([attribute: setting])
}
}
2018-03-27 09:18:29 +03:00
setTargetAttribute(attribute: "ProvisioningStyle", buildSetting: "CODE_SIGN_STYLE")
setTargetAttribute(attribute: "DevelopmentTeam", buildSetting: "DEVELOPMENT_TEAM")
2018-01-05 05:02:47 +03:00
}
2018-01-24 10:22:46 +03:00
2018-07-22 13:27:58 +03:00
for target in project.aggregateTargets {
guard let targetReference = targetAggregateObjects[target.name]?.reference else {
continue
}
generateTargetAttributes(target, targetReference: targetReference)
}
for target in project.targets {
guard let targetReference = targetObjects[target.name]?.reference else {
continue
}
generateTargetAttributes(target, targetReference: targetReference)
}
2018-03-27 06:21:15 +03:00
return targetAttributes.isEmpty ? nil : ["TargetAttributes": targetAttributes]
2018-01-05 05:02:47 +03:00
}
2017-07-23 21:58:03 +03:00
2018-01-24 10:22:46 +03:00
func sortGroups(group: ObjectReference<PBXGroup>) {
// sort children
2018-01-24 10:22:46 +03:00
let children = group.object.children
2018-05-12 08:10:18 +03:00
.compactMap { reference -> ObjectReference<PBXFileElement>? in
2018-04-12 16:13:40 +03:00
guard let fileElement = pbxProj.objects.getFileElement(reference: reference) else {
2018-01-24 10:22:46 +03:00
return nil
}
return ObjectReference(reference: reference, object: fileElement)
}
.sorted { child1, child2 in
2018-07-24 15:59:24 +03:00
let sortOrder1 = child1.object.getSortOrder(groupSortPosition: project.options.groupSortPosition)
let sortOrder2 = child2.object.getSortOrder(groupSortPosition: project.options.groupSortPosition)
if sortOrder1 == sortOrder2 {
return child1.object.nameOrPath.localizedStandardCompare(child2.object.nameOrPath) == .orderedAscending
} else {
2018-07-24 15:59:24 +03:00
return sortOrder1 < sortOrder2
}
2018-04-12 16:32:40 +03:00
}
2018-01-24 10:22:46 +03:00
group.object.children = children.map { $0.reference }.filter { $0 != group.reference }
// sort sub groups
2018-05-12 08:10:18 +03:00
let childGroups = group.object.children.compactMap { reference -> ObjectReference<PBXGroup>? in
2018-04-12 16:13:40 +03:00
guard let group = pbxProj.objects.groups[reference] else {
2018-01-24 10:22:46 +03:00
return nil
}
return ObjectReference(reference: reference, object: group) }
childGroups.forEach(sortGroups)
2017-07-23 21:58:03 +03:00
}
2017-12-26 12:56:02 +03:00
2018-01-24 10:22:46 +03:00
func generateTarget(_ target: Target) throws {
2017-07-23 21:58:03 +03:00
2017-11-15 11:46:41 +03:00
sourceGenerator.targetName = target.name
let carthageDependencies = getAllCarthageDependencies(target: target)
let sourceFiles = try sourceGenerator.getAllSourceFiles(targetType: target.type, sources: target.sources)
2017-07-26 13:40:25 +03:00
2017-12-26 12:56:02 +03:00
var plistPath: Path?
2017-12-20 04:10:13 +03:00
var searchForPlist = true
var anyDependencyRequiresObjCLinking = false
2017-08-27 13:28:18 +03:00
2017-10-02 18:08:39 +03:00
var dependencies: [String] = []
2017-07-25 02:02:54 +03:00
var targetFrameworkBuildFiles: [String] = []
var frameworkBuildPaths = Set<String>()
var copyFilesBuildPhasesFiles: [TargetSource.BuildPhase.CopyFilesSettings: [String]] = [:]
var copyFrameworksReferences: [String] = []
var copyResourcesReferences: [String] = []
var copyWatchReferences: [String] = []
2017-08-03 21:03:56 +03:00
var extensions: [String] = []
var carthageFrameworksToEmbed: [String] = []
2018-07-03 14:45:00 +03:00
2018-07-03 21:34:20 +03:00
let targetDependencies = (target.transitivelyLinkDependencies ?? project.options.transitivelyLinkDependencies) ?
getAllDependenciesPlusTransitiveNeedingEmbedding(target: target) : target.dependencies
2018-08-19 07:22:42 +03:00
let directlyEmbedCarthage = target.directlyEmbedCarthageDependencies ?? !(target.platform.requiresSimulatorStripping && target.type.isApp)
2018-08-19 07:22:42 +03:00
func getEmbedSettings(dependency: Dependency, codeSign: Bool) -> [String: Any] {
var embedAttributes: [String] = []
if codeSign {
embedAttributes.append("CodeSignOnCopy")
}
if dependency.removeHeaders {
embedAttributes.append("RemoveHeadersOnCopy")
}
return ["ATTRIBUTES": embedAttributes]
}
2017-08-03 21:03:56 +03:00
for dependency in targetDependencies {
2017-08-24 18:41:55 +03:00
let embed = dependency.embed ?? target.shouldEmbedDependencies
switch dependency.type {
case .target:
let dependencyTargetName = dependency.reference
2018-07-22 10:37:16 +03:00
let targetDependency = generateTargetDependency(from: target.name, to: dependencyTargetName)
2017-10-02 18:08:39 +03:00
dependencies.append(targetDependency.reference)
2017-07-23 21:58:03 +03:00
guard let dependencyTarget = project.getTarget(dependencyTargetName) else { continue }
let dependencyFileReference = targetFileReferences[dependencyTargetName]!
let dependecyLinkage = dependencyTarget.defaultLinkage
2018-08-19 07:22:42 +03:00
let link = dependency.link ?? (
(dependecyLinkage == .dynamic && target.type != .staticLibrary)
|| (dependecyLinkage == .static && target.type.isExecutable)
)
if link {
let dependencyBuildFile = targetBuildFiles[dependencyTargetName]!
2018-01-24 10:22:46 +03:00
let buildFile = createObject(
id: dependencyBuildFile.reference + target.name,
PBXBuildFile(fileRef: dependencyBuildFile.object.fileRef!)
2017-12-26 13:47:18 +03:00
)
targetFrameworkBuildFiles.append(buildFile.reference)
2018-08-19 07:22:42 +03:00
if !anyDependencyRequiresObjCLinking
&& dependencyTarget.requiresObjCLinking ?? (dependencyTarget.type == .staticLibrary) {
anyDependencyRequiresObjCLinking = true
}
}
2018-07-03 14:45:00 +03:00
2018-08-19 07:22:42 +03:00
let embed = dependency.embed ?? (!dependencyTarget.type.isLibrary && (
target.type.isApp
|| (target.type.isTest && (dependencyTarget.type.isFramework || dependencyTarget.type == .bundle))
))
if embed {
2018-01-24 10:22:46 +03:00
let embedFile = createObject(
id: dependencyFileReference + target.name,
PBXBuildFile(
fileRef: dependencyFileReference,
settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? !dependencyTarget.type.isExecutable)
2018-01-24 10:22:46 +03:00
)
2017-12-26 13:47:18 +03:00
)
2017-08-24 18:41:55 +03:00
2017-08-03 21:03:56 +03:00
if dependencyTarget.type.isExtension {
2017-08-24 18:41:55 +03:00
// embed app extension
2017-08-03 21:03:56 +03:00
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 if dependencyTarget.type == .xpcService {
copyFilesBuildPhasesFiles[.xpcServices, default: []].append(embedFile.reference)
2017-08-03 21:03:56 +03:00
} else {
copyResourcesReferences.append(embedFile.reference)
2017-08-03 21:03:56 +03:00
}
}
case .framework:
guard target.type != .staticLibrary else { break }
2018-08-19 07:22:42 +03:00
2017-11-21 09:47:57 +03:00
let fileReference: String
2017-11-21 15:51:04 +03:00
if dependency.implicit {
2017-12-26 13:47:18 +03:00
fileReference = sourceGenerator.getFileReference(
path: Path(dependency.reference),
2018-04-12 16:13:40 +03:00
inPath: project.basePath,
2017-12-26 13:47:18 +03:00
sourceTree: .buildProductsDir
)
2017-11-21 09:47:57 +03:00
} else {
2017-12-26 13:47:18 +03:00
fileReference = sourceGenerator.getFileReference(
path: Path(dependency.reference),
2018-04-12 16:13:40 +03:00
inPath: project.basePath
2017-12-26 13:47:18 +03:00
)
2017-11-21 09:47:57 +03:00
}
2017-08-24 18:41:55 +03:00
2018-01-24 10:22:46 +03:00
let buildFile = createObject(
id: "framework" + fileReference + target.name,
2018-01-24 10:22:46 +03:00
PBXBuildFile(fileRef: fileReference)
2017-12-26 13:47:18 +03:00
)
2017-08-24 18:41:55 +03:00
2017-07-25 02:02:54 +03:00
targetFrameworkBuildFiles.append(buildFile.reference)
if !frameworkFiles.contains(fileReference) {
frameworkFiles.append(fileReference)
}
2017-08-24 18:41:55 +03:00
if embed {
2018-01-24 10:22:46 +03:00
let embedFile = createObject(
id: "framework embed" + fileReference + target.name,
PBXBuildFile(fileRef: fileReference, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? true))
2017-12-26 13:47:18 +03:00
)
copyFrameworksReferences.append(embedFile.reference)
2017-08-24 18:41:55 +03:00
}
2018-08-19 07:22:42 +03:00
2018-08-20 17:11:24 +03:00
let buildPath = Path(dependency.reference).parent().string.quoted
frameworkBuildPaths.insert(buildPath)
2018-07-03 14:45:00 +03:00
case .carthage:
guard target.type != .staticLibrary else { break }
2018-08-19 07:22:42 +03:00
var platformPath = Path(getCarthageBuildPath(platform: target.platform))
var frameworkPath = platformPath + dependency.reference
2017-07-25 02:02:54 +03:00
if frameworkPath.extension == nil {
frameworkPath = Path(frameworkPath.string + ".framework")
}
let fileReference = sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath)
2017-07-25 01:32:36 +03:00
2018-01-24 10:22:46 +03:00
let buildFile = createObject(
id: "carthage" + fileReference + target.name,
2018-01-24 10:22:46 +03:00
PBXBuildFile(fileRef: fileReference)
2017-12-26 13:47:18 +03:00
)
2018-01-24 10:22:46 +03:00
carthageFrameworksByPlatform[target.platform.carthageDirectoryName, default: []].insert(fileReference)
2017-07-25 01:32:36 +03:00
2017-07-25 02:02:54 +03:00
targetFrameworkBuildFiles.append(buildFile.reference)
2018-08-19 07:22:42 +03:00
// Embedding handled by iterating over `carthageDependencies` below
}
}
2018-08-19 07:22:42 +03:00
for dependency in carthageDependencies {
guard target.type != .staticLibrary else { break }
2018-08-19 07:22:42 +03:00
let embed = dependency.embed ?? target.shouldEmbedDependencies
2018-08-19 07:22:42 +03:00
var platformPath = Path(getCarthageBuildPath(platform: target.platform))
var frameworkPath = platformPath + dependency.reference
if frameworkPath.extension == nil {
frameworkPath = Path(frameworkPath.string + ".framework")
}
let fileReference = sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath)
2018-08-19 07:22:42 +03:00
if embed {
if directlyEmbedCarthage {
2018-01-24 10:22:46 +03:00
let embedFile = createObject(
id: "carthage embed" + fileReference + target.name,
PBXBuildFile(fileRef: fileReference, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? true))
2017-12-26 13:47:18 +03:00
)
copyFrameworksReferences.append(embedFile.reference)
2018-08-19 07:22:42 +03:00
} else {
carthageFrameworksToEmbed.append(dependency.reference)
}
2017-07-23 21:58:03 +03:00
}
}
let fileReference = targetFileReferences[target.name]
2017-07-23 21:58:03 +03:00
var buildPhases: [String] = []
2018-07-17 23:37:40 +03:00
func getBuildFilesForSourceFiles(_ sourceFiles: [SourceFile]) -> [String] {
2017-11-03 17:01:53 +03:00
let files = sourceFiles
2018-01-24 10:22:46 +03:00
.reduce(into: [SourceFile]()) { output, sourceFile in
if !output.contains(where: { $0.fileReference == sourceFile.fileReference }) {
output.append(sourceFile)
}
}
2017-11-03 17:01:53 +03:00
.sorted { $0.path.lastComponent < $1.path.lastComponent }
2018-01-24 10:22:46 +03:00
return files.map { createObject(id: $0.fileReference + target.name, $0.buildFile) }
.map { $0.reference }
2017-07-23 21:58:03 +03:00
}
2018-08-19 07:22:42 +03:00
2018-07-17 23:37:40 +03:00
func getBuildFilesForPhase(_ buildPhase: BuildPhase) -> [String] {
let filteredSourceFiles = sourceFiles
.filter { $0.buildPhase?.buildPhase == buildPhase }
return getBuildFilesForSourceFiles(filteredSourceFiles)
}
2018-08-19 07:22:42 +03:00
2018-07-17 23:37:40 +03:00
func getBuildFilesForCopyFilesPhases() -> [TargetSource.BuildPhase.CopyFilesSettings: [String]] {
var sourceFilesByCopyFiles: [TargetSource.BuildPhase.CopyFilesSettings: [SourceFile]] = [:]
for sourceFile in sourceFiles {
guard case let .copyFiles(copyFilesSettings)? = sourceFile.buildPhase else { continue }
sourceFilesByCopyFiles[copyFilesSettings, default: []].append(sourceFile)
}
return sourceFilesByCopyFiles.mapValues { getBuildFilesForSourceFiles($0) }
}
2017-07-23 21:58:03 +03:00
2018-07-22 10:37:16 +03:00
buildPhases += try target.prebuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
2018-09-19 14:10:25 +03:00
let headersBuildPhaseFiles = getBuildFilesForPhase(.headers)
if !headersBuildPhaseFiles.isEmpty && (target.type == .framework || target.type == .dynamicLibrary) {
let headersBuildPhase = createObject(id: target.name, PBXHeadersBuildPhase(files: headersBuildPhaseFiles))
buildPhases.append(headersBuildPhase.reference)
}
2017-08-01 19:40:40 +03:00
2017-11-13 00:17:08 +03:00
let sourcesBuildPhaseFiles = getBuildFilesForPhase(.sources)
2018-05-14 03:38:43 +03:00
let sourcesBuildPhase = createObject(id: target.name, PBXSourcesBuildPhase(files: sourcesBuildPhaseFiles))
buildPhases.append(sourcesBuildPhase.reference)
buildPhases += try target.postCompileScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
2017-07-23 21:58:03 +03:00
2017-11-13 00:17:08 +03:00
let resourcesBuildPhaseFiles = getBuildFilesForPhase(.resources) + copyResourcesReferences
if !resourcesBuildPhaseFiles.isEmpty {
2018-01-24 10:22:46 +03:00
let resourcesBuildPhase = createObject(id: target.name, PBXResourcesBuildPhase(files: resourcesBuildPhaseFiles))
2017-11-13 00:17:08 +03:00
buildPhases.append(resourcesBuildPhase.reference)
}
2018-08-19 07:22:42 +03:00
let buildSettings = project.getCombinedBuildSettings(basePath: project.basePath, target: target, config: project.configs[0])
let swiftObjCInterfaceHeader = buildSettings["SWIFT_OBJC_INTERFACE_HEADER_NAME"] as? String
2018-08-19 07:22:42 +03:00
if target.type == .staticLibrary
&& swiftObjCInterfaceHeader != ""
&& sourceFiles.contains(where: { $0.buildPhase == .sources && $0.path.extension == "swift" }) {
2018-08-19 07:22:42 +03:00
let inputPaths = ["$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"]
let outputPaths = ["$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"]
let script = createObject(
id: "Swift.h" + target.name,
PBXShellScriptBuildPhase(
files: [],
name: "Copy Swift Objective-C Interface Header",
inputPaths: inputPaths,
outputPaths: outputPaths,
shellPath: "/bin/sh",
shellScript: "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\n"
)
)
buildPhases.append(script.reference)
}
2018-08-19 07:22:42 +03:00
copyFilesBuildPhasesFiles.merge(getBuildFilesForCopyFilesPhases()) { $0 + $1 }
buildPhases += copyFilesBuildPhasesFiles.map { generateCopyFiles(targetName: target.name, copyFiles: $0, buildPhaseFiles: $1) }
2017-07-23 21:58:03 +03:00
if !targetFrameworkBuildFiles.isEmpty {
2018-01-24 10:22:46 +03:00
let frameworkBuildPhase = createObject(
id: target.name,
2018-01-28 12:01:47 +03:00
PBXFrameworksBuildPhase(files: targetFrameworkBuildFiles)
2017-12-26 13:47:18 +03:00
)
buildPhases.append(frameworkBuildPhase.reference)
}
if !extensions.isEmpty {
2018-01-24 10:22:46 +03:00
let copyFilesPhase = createObject(
id: "embed app extensions" + target.name,
PBXCopyFilesBuildPhase(
dstPath: "",
dstSubfolderSpec: .plugins,
name: "Embed App Extensions",
2018-01-24 10:22:46 +03:00
files: extensions
)
2017-12-26 13:47:18 +03:00
)
buildPhases.append(copyFilesPhase.reference)
}
2017-07-23 21:58:03 +03:00
copyFrameworksReferences += getBuildFilesForPhase(.frameworks)
if !copyFrameworksReferences.isEmpty {
2018-01-24 10:22:46 +03:00
let copyFilesPhase = createObject(
id: "embed frameworks" + target.name,
PBXCopyFilesBuildPhase(
dstPath: "",
dstSubfolderSpec: .frameworks,
name: "Embed Frameworks",
2018-01-24 10:22:46 +03:00
files: copyFrameworksReferences
)
2017-12-26 13:47:18 +03:00
)
buildPhases.append(copyFilesPhase.reference)
}
2017-07-23 21:58:03 +03:00
if !copyWatchReferences.isEmpty {
2018-01-24 10:22:46 +03:00
let copyFilesPhase = createObject(
id: "embed watch content" + target.name,
PBXCopyFilesBuildPhase(
dstPath: "$(CONTENTS_FOLDER_PATH)/Watch",
dstSubfolderSpec: .productsDirectory,
name: "Embed Watch Content",
2018-01-24 10:22:46 +03:00
files: copyWatchReferences
)
2017-12-26 13:47:18 +03:00
)
buildPhases.append(copyFilesPhase.reference)
}
2018-08-19 07:22:42 +03:00
if !carthageFrameworksToEmbed.isEmpty {
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")" }
2018-04-12 16:13:40 +03:00
let carthageExecutable = project.options.carthageExecutablePath ?? "carthage"
let carthageScript = createObject(
id: "Carthage" + target.name,
PBXShellScriptBuildPhase(
files: [],
name: "Carthage",
inputPaths: inputPaths,
outputPaths: outputPaths,
shellPath: "/bin/sh",
shellScript: "\(carthageExecutable) copy-frameworks\n"
2017-12-26 13:47:18 +03:00
)
)
buildPhases.append(carthageScript.reference)
2017-07-25 02:24:14 +03:00
}
2018-05-13 10:13:48 +03:00
let buildRules = target.buildRules.map { buildRule in
2018-06-08 08:21:21 +03:00
createObject(
id: "\(target.name)-\(buildRule.action)-\(buildRule.fileType)",
2018-05-13 10:13:48 +03:00
PBXBuildRule(
compilerSpec: buildRule.action.compilerSpec,
fileType: buildRule.fileType.fileType,
isEditable: true,
filePatterns: buildRule.fileType.pattern,
name: buildRule.name ?? "Build Rule",
outputFiles: buildRule.outputFiles,
outputFilesCompilerFlags: buildRule.outputFilesCompilerFlags,
script: buildRule.action.script
2018-06-08 08:21:21 +03:00
)
).reference
2018-05-13 10:13:48 +03:00
}
2018-07-22 10:37:16 +03:00
buildPhases += try target.postbuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
2018-08-19 07:22:42 +03:00
let configs: [ObjectReference<XCBuildConfiguration>] = project.configs.map { config in
var buildSettings = project.getTargetBuildSettings(target: target, config: config)
2018-08-19 07:22:42 +03:00
// automatically set INFOPLIST_FILE path
if !project.targetHasBuildSetting("INFOPLIST_FILE", basePath: project.basePath, target: target, config: config) {
if searchForPlist {
plistPath = getInfoPlist(target.sources)
searchForPlist = false
}
if let plistPath = plistPath {
buildSettings["INFOPLIST_FILE"] = plistPath.byRemovingBase(path: project.basePath)
}
}
2018-08-19 07:22:42 +03:00
// automatically calculate bundle id
if let bundleIdPrefix = project.options.bundleIdPrefix,
!project.targetHasBuildSetting("PRODUCT_BUNDLE_IDENTIFIER", basePath: project.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
}
2018-08-19 07:22:42 +03:00
// automatically set test target name
if target.type == .uiTestBundle,
!project.targetHasBuildSetting("TEST_TARGET_NAME", basePath: project.basePath, target: target, config: config) {
for dependency in target.dependencies {
if dependency.type == .target,
let dependencyTarget = project.getTarget(dependency.reference),
dependencyTarget.type == .application {
buildSettings["TEST_TARGET_NAME"] = dependencyTarget.name
break
}
}
}
2018-08-19 07:22:42 +03:00
// objc linkage
if anyDependencyRequiresObjCLinking {
let otherLinkingFlags = "OTHER_LDFLAGS"
let objCLinking = "-ObjC"
if var array = buildSettings[otherLinkingFlags] as? [String] {
array.append(objCLinking)
buildSettings[otherLinkingFlags] = array
} else if let string = buildSettings[otherLinkingFlags] as? String {
buildSettings[otherLinkingFlags] = [string, objCLinking]
} else {
buildSettings[otherLinkingFlags] = ["$(inherited)", objCLinking]
}
}
2018-08-19 07:22:42 +03:00
// set Carthage search paths
let configFrameworkBuildPaths: [String]
if !carthageDependencies.isEmpty {
let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + getCarthageBuildPath(platform: target.platform)
configFrameworkBuildPaths = [carthagePlatformBuildPath] + Array(frameworkBuildPaths).sorted()
} else {
configFrameworkBuildPaths = Array(frameworkBuildPaths).sorted()
}
2018-08-19 07:22:42 +03:00
// set framework search paths
if !configFrameworkBuildPaths.isEmpty {
let frameworkSearchPaths = "FRAMEWORK_SEARCH_PATHS"
if var array = buildSettings[frameworkSearchPaths] as? [String] {
array.append(contentsOf: configFrameworkBuildPaths)
buildSettings[frameworkSearchPaths] = array
} else if let string = buildSettings[frameworkSearchPaths] as? String {
buildSettings[frameworkSearchPaths] = [string] + configFrameworkBuildPaths
} else {
buildSettings[frameworkSearchPaths] = ["$(inherited)"] + configFrameworkBuildPaths
}
}
2018-08-19 07:22:42 +03:00
var baseConfigurationReference: String?
if let configPath = target.configFiles[config.name] {
baseConfigurationReference = sourceGenerator.getContainedFileReference(path: project.basePath + configPath)
}
let buildConfig = XCBuildConfiguration(
name: config.name,
baseConfigurationReference: baseConfigurationReference,
buildSettings: buildSettings
)
return createObject(id: config.name + target.name, buildConfig)
}
2018-08-19 07:22:42 +03:00
let buildConfigList = createObject(id: target.name, XCConfigurationList(
buildConfigurations: configs.map { $0.reference },
defaultConfigurationName: ""
))
2018-01-24 10:22:46 +03:00
let targetObject = targetObjects[target.name]!.object
targetObject.name = target.name
targetObject.buildConfigurationList = buildConfigList.reference
targetObject.buildPhases = buildPhases
targetObject.dependencies = dependencies
targetObject.productName = target.name
2018-05-13 10:13:48 +03:00
targetObject.buildRules = buildRules
2018-01-24 10:22:46 +03:00
targetObject.productReference = fileReference
if !target.isLegacy {
targetObject.productType = target.type
}
2017-07-23 21:58:03 +03:00
}
2017-12-20 20:04:23 +03:00
func getInfoPlist(_ sources: [TargetSource]) -> Path? {
2017-12-20 04:10:13 +03:00
return sources
.lazy
2018-04-12 16:13:40 +03:00
.map { self.project.basePath + $0.path }
2018-09-13 16:05:51 +03:00
.compactMap { (path) -> Path? in
2017-12-20 04:10:13 +03:00
if path.isFile {
return path.lastComponent == "Info.plist" ? path : nil
} else {
return path.first(where: { $0.lastComponent == "Info.plist" })
}
}
.first
}
func getCarthageBuildPath(platform: Platform) -> String {
let carthagePath = Path(carthageBuildPath)
let platformName = platform.carthageDirectoryName
return "\(carthagePath)/\(platformName)"
}
func getAllCarthageDependencies(target topLevelTarget: Target) -> [Dependency] {
2017-11-12 22:39:35 +03:00
// this is used to resolve cyclical target dependencies
var visitedTargets: Set<String> = []
var frameworks: [String: Dependency] = [:]
2017-11-12 22:39:35 +03:00
var queue: [ProjectTarget] = [topLevelTarget]
while !queue.isEmpty {
let projectTarget = queue.removeFirst()
if visitedTargets.contains(projectTarget.name) {
continue
}
2018-09-19 14:10:25 +03:00
if let target = projectTarget as? Target {
for dependency in target.dependencies {
// don't overwrite frameworks, to allow top level ones to rule
if frameworks.contains(reference: dependency.reference) {
continue
}
2018-09-19 14:10:25 +03:00
switch dependency.type {
case .carthage:
frameworks[dependency.reference] = dependency
case .target:
if let projectTarget = project.getProjectTarget(dependency.reference) {
queue.append(projectTarget)
}
default:
break
}
}
} else if let aggregateTarget = projectTarget as? AggregateTarget {
for dependencyName in aggregateTarget.targets {
if let projectTarget = project.getProjectTarget(dependencyName) {
queue.append(projectTarget)
}
}
}
visitedTargets.update(with: projectTarget.name)
}
return frameworks.sorted(by: { $0.key < $1.key }).map { $0.value }
}
2018-07-03 14:45:00 +03:00
2018-07-03 21:34:20 +03:00
func getAllDependenciesPlusTransitiveNeedingEmbedding(target topLevelTarget: Target) -> [Dependency] {
// this is used to resolve cyclical target dependencies
var visitedTargets: Set<String> = []
var dependencies: [String: Dependency] = [:]
var queue: [Target] = [topLevelTarget]
while !queue.isEmpty {
let target = queue.removeFirst()
if visitedTargets.contains(target.name) {
continue
}
2018-07-03 14:45:00 +03:00
let isTopLevel = target == topLevelTarget
2018-07-03 14:45:00 +03:00
for dependency in target.dependencies {
// don't overwrite dependencies, to allow top level ones to rule
if dependencies.contains(reference: dependency.reference) {
continue
}
2018-07-03 14:45:00 +03:00
// don't want a dependency if it's going to be embedded or statically linked in a non-top level target
// in .target check we filter out targets that will embed all of their dependencies
switch dependency.type {
case .framework, .carthage:
if isTopLevel || dependency.embed == nil {
dependencies[dependency.reference] = dependency
}
case .target:
if isTopLevel || dependency.embed == nil {
if let dependencyTarget = project.getTarget(dependency.reference) {
dependencies[dependency.reference] = dependency
if !dependencyTarget.shouldEmbedDependencies {
// traverse target's dependencies if it doesn't embed them itself
queue.append(dependencyTarget)
}
} else if project.getAggregateTarget(dependency.reference) != nil {
// Aggregate targets should be included
dependencies[dependency.reference] = dependency
}
}
}
}
2018-07-03 14:45:00 +03:00
visitedTargets.update(with: target.name)
}
2018-07-03 14:45:00 +03:00
return dependencies.sorted(by: { $0.key < $1.key }).map { $0.value }
}
2017-07-23 21:58:03 +03:00
}
extension Target {
var shouldEmbedDependencies: Bool {
return type.isApp || type.isTest
}
}
2018-07-24 15:59:24 +03:00
extension Platform {
/// - returns: `true` for platforms that the app store requires simulator slices to be stripped.
public var requiresSimulatorStripping: Bool {
switch self {
case .iOS, .tvOS, .watchOS:
return true
case .macOS:
return false
}
}
}
2018-07-24 15:59:24 +03:00
extension PBXFileElement {
public func getSortOrder(groupSortPosition: SpecOptions.GroupSortPosition) -> Int {
if type(of: self).isa == "PBXGroup" {
switch groupSortPosition {
case .top: return -1
case .bottom: return 1
case .none: return 0
}
} else {
return 0
}
}
}