mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2026-03-18 20:02:25 +00:00
97d36fd1d2
* platformFilters on Dependecies * platformFilters on sources * fixed current unit tests * renamed enum to SupportedPlatforms * supportedPlatforms field for target * errors * renamed errors * inferPlatformFiltersByPath flag * changed priority to generate filter * fixed parsing * fixed init * unit test supportedPlatforms * unit tests for errors * fixing build settings and unit tests * added new settingsPresets * new check errors and unit tests * case insensitive match * fixed skipping cross platform target * json decode * unit tests inferPlatformFiltersByPath and platformFilters for sources * mocked files * fixing unit tests * first test on dependecies * unit tests completed * fixed unit tests * changelog * doc changes * doc changes * doc changes * doc changes * doc changes * doc changes * doc changes * doc changes * fixed doc * fixed unti tests style * fixed regex * fixed doc * addressing comments * Added TestProject, moved unit tests resources in another folder * Raising error if platform is an array * unit test on new error * fixed error enum * Integrated in TestProject * committed TestProject * unit test error * fixing spm deps in test project * pushed testProject * pushed testProject * pushed testProject fix * comment on isResolved property * renameing supportedPlatforms to supportedDestinations * renameing supportedPlatforms to supportedDestinations * renameing test app * checked out old file * fixing test app * working on auto baseSDK * fixed deploymentTarget * renamed errors * fixed presets * remamed index to priority * small comments * removed isResolved in target and fixed error check * added unit tests * fixed doc * fixed doc * fixed doc * fixed doc * fixed test app * add visionOS and more error check and testing * fixed supported destinations priority and tests * fixed doc * solved conflicts * fixed conflicts * renamed everything --------- Co-authored-by: Giovanni Amati <giovanni.amati@sky.uk>
417 lines
17 KiB
Swift
417 lines
17 KiB
Swift
import Foundation
|
|
import JSONUtilities
|
|
import XcodeProj
|
|
import Version
|
|
|
|
public struct LegacyTarget: Equatable {
|
|
public static let passSettingsDefault = false
|
|
|
|
public var toolPath: String
|
|
public var arguments: String?
|
|
public var passSettings: Bool
|
|
public var workingDirectory: String?
|
|
|
|
public init(
|
|
toolPath: String,
|
|
passSettings: Bool = passSettingsDefault,
|
|
arguments: String? = nil,
|
|
workingDirectory: String? = nil
|
|
) {
|
|
self.toolPath = toolPath
|
|
self.arguments = arguments
|
|
self.passSettings = passSettings
|
|
self.workingDirectory = workingDirectory
|
|
}
|
|
}
|
|
|
|
extension LegacyTarget: PathContainer {
|
|
|
|
static var pathProperties: [PathProperty] {
|
|
[
|
|
.string("workingDirectory"),
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct Target: ProjectTarget {
|
|
public var name: String
|
|
public var type: PBXProductType
|
|
public var platform: Platform
|
|
public var supportedDestinations: [SupportedDestination]?
|
|
public var settings: Settings
|
|
public var sources: [TargetSource]
|
|
public var dependencies: [Dependency]
|
|
public var info: Plist?
|
|
public var entitlements: Plist?
|
|
public var transitivelyLinkDependencies: Bool?
|
|
public var directlyEmbedCarthageDependencies: Bool?
|
|
public var requiresObjCLinking: Bool?
|
|
public var preBuildScripts: [BuildScript]
|
|
public var buildToolPlugins: [BuildToolPlugin]
|
|
public var postCompileScripts: [BuildScript]
|
|
public var postBuildScripts: [BuildScript]
|
|
public var buildRules: [BuildRule]
|
|
public var configFiles: [String: String]
|
|
public var scheme: TargetScheme?
|
|
public var legacy: LegacyTarget?
|
|
public var deploymentTarget: Version?
|
|
public var attributes: [String: Any]
|
|
public var productName: String
|
|
public var onlyCopyFilesOnInstall: Bool
|
|
public var putResourcesBeforeSourcesBuildPhase: Bool
|
|
|
|
public var isLegacy: Bool {
|
|
legacy != nil
|
|
}
|
|
|
|
public var filename: String {
|
|
var filename = productName
|
|
if let fileExtension = type.fileExtension {
|
|
filename += ".\(fileExtension)"
|
|
}
|
|
if type == .staticLibrary {
|
|
filename = "lib\(filename)"
|
|
}
|
|
return filename
|
|
}
|
|
|
|
public init(
|
|
name: String,
|
|
type: PBXProductType,
|
|
platform: Platform,
|
|
supportedDestinations: [SupportedDestination]? = nil,
|
|
productName: String? = nil,
|
|
deploymentTarget: Version? = nil,
|
|
settings: Settings = .empty,
|
|
configFiles: [String: String] = [:],
|
|
sources: [TargetSource] = [],
|
|
dependencies: [Dependency] = [],
|
|
info: Plist? = nil,
|
|
entitlements: Plist? = nil,
|
|
transitivelyLinkDependencies: Bool? = nil,
|
|
directlyEmbedCarthageDependencies: Bool? = nil,
|
|
requiresObjCLinking: Bool? = nil,
|
|
preBuildScripts: [BuildScript] = [],
|
|
buildToolPlugins: [BuildToolPlugin] = [],
|
|
postCompileScripts: [BuildScript] = [],
|
|
postBuildScripts: [BuildScript] = [],
|
|
buildRules: [BuildRule] = [],
|
|
scheme: TargetScheme? = nil,
|
|
legacy: LegacyTarget? = nil,
|
|
attributes: [String: Any] = [:],
|
|
onlyCopyFilesOnInstall: Bool = false,
|
|
putResourcesBeforeSourcesBuildPhase: Bool = false
|
|
) {
|
|
self.name = name
|
|
self.type = type
|
|
self.platform = platform
|
|
self.supportedDestinations = supportedDestinations
|
|
self.deploymentTarget = deploymentTarget
|
|
self.productName = productName ?? name
|
|
self.settings = settings
|
|
self.configFiles = configFiles
|
|
self.sources = sources
|
|
self.dependencies = dependencies
|
|
self.info = info
|
|
self.entitlements = entitlements
|
|
self.transitivelyLinkDependencies = transitivelyLinkDependencies
|
|
self.directlyEmbedCarthageDependencies = directlyEmbedCarthageDependencies
|
|
self.requiresObjCLinking = requiresObjCLinking
|
|
self.preBuildScripts = preBuildScripts
|
|
self.buildToolPlugins = buildToolPlugins
|
|
self.postCompileScripts = postCompileScripts
|
|
self.postBuildScripts = postBuildScripts
|
|
self.buildRules = buildRules
|
|
self.scheme = scheme
|
|
self.legacy = legacy
|
|
self.attributes = attributes
|
|
self.onlyCopyFilesOnInstall = onlyCopyFilesOnInstall
|
|
self.putResourcesBeforeSourcesBuildPhase = putResourcesBeforeSourcesBuildPhase
|
|
}
|
|
}
|
|
|
|
extension Target: CustomStringConvertible {
|
|
|
|
public var description: String {
|
|
"\(name): \(platform.rawValue) \(type)"
|
|
}
|
|
}
|
|
|
|
extension Target: PathContainer {
|
|
|
|
static var pathProperties: [PathProperty] {
|
|
[
|
|
.dictionary([
|
|
.string("sources"),
|
|
.object("sources", TargetSource.pathProperties),
|
|
.string("configFiles"),
|
|
.object("dependencies", Dependency.pathProperties),
|
|
.object("info", Plist.pathProperties),
|
|
.object("entitlements", Plist.pathProperties),
|
|
.object("preBuildScripts", BuildScript.pathProperties),
|
|
.object("prebuildScripts", BuildScript.pathProperties),
|
|
.object("postCompileScripts", BuildScript.pathProperties),
|
|
.object("postBuildScripts", BuildScript.pathProperties),
|
|
.object("legacy", LegacyTarget.pathProperties),
|
|
.object("scheme", TargetScheme.pathProperties),
|
|
]),
|
|
]
|
|
}
|
|
}
|
|
|
|
extension Target {
|
|
|
|
static func resolveMultiplatformTargets(jsonDictionary: JSONDictionary) -> JSONDictionary {
|
|
guard let targetsDictionary: [String: JSONDictionary] = jsonDictionary["targets"] as? [String: JSONDictionary] else {
|
|
return jsonDictionary
|
|
}
|
|
|
|
var crossPlatformTargets: [String: JSONDictionary] = [:]
|
|
|
|
for (targetName, target) in targetsDictionary {
|
|
if let platforms = target["platform"] as? [String] {
|
|
for platform in platforms {
|
|
var platformTarget = target
|
|
|
|
/// This value is set to help us to check, in Target init, that there are no conflicts in the definition of the platforms. We want to ensure that the user didn't define, at the same time,
|
|
/// the new Xcode 14 supported destinations and the XcodeGen generation of Multiple Platform Targets (when you define the platform field as an array).
|
|
platformTarget["isMultiPlatformTarget"] = true
|
|
|
|
platformTarget = platformTarget.expand(variables: ["platform": platform])
|
|
|
|
platformTarget["platform"] = platform
|
|
let platformSuffix = platformTarget["platformSuffix"] as? String ?? "_\(platform)"
|
|
let platformPrefix = platformTarget["platformPrefix"] as? String ?? ""
|
|
let newTargetName = platformPrefix + targetName + platformSuffix
|
|
|
|
var settings = platformTarget["settings"] as? JSONDictionary ?? [:]
|
|
if settings["configs"] != nil || settings["groups"] != nil || settings["base"] != nil {
|
|
var base = settings["base"] as? JSONDictionary ?? [:]
|
|
if base["PRODUCT_NAME"] == nil {
|
|
base["PRODUCT_NAME"] = targetName
|
|
}
|
|
settings["base"] = base
|
|
} else {
|
|
if settings["PRODUCT_NAME"] == nil {
|
|
settings["PRODUCT_NAME"] = targetName
|
|
}
|
|
}
|
|
platformTarget["productName"] = targetName
|
|
platformTarget["settings"] = settings
|
|
if let deploymentTargets = target["deploymentTarget"] as? [String: Any] {
|
|
platformTarget["deploymentTarget"] = deploymentTargets[platform]
|
|
}
|
|
crossPlatformTargets[newTargetName] = platformTarget
|
|
}
|
|
} else {
|
|
crossPlatformTargets[targetName] = target
|
|
}
|
|
}
|
|
|
|
var merged = jsonDictionary
|
|
merged["targets"] = crossPlatformTargets
|
|
return merged
|
|
}
|
|
}
|
|
|
|
extension Target: Equatable {
|
|
|
|
public static func == (lhs: Target, rhs: Target) -> Bool {
|
|
lhs.name == rhs.name &&
|
|
lhs.type == rhs.type &&
|
|
lhs.platform == rhs.platform &&
|
|
lhs.deploymentTarget == rhs.deploymentTarget &&
|
|
lhs.transitivelyLinkDependencies == rhs.transitivelyLinkDependencies &&
|
|
lhs.requiresObjCLinking == rhs.requiresObjCLinking &&
|
|
lhs.directlyEmbedCarthageDependencies == rhs.directlyEmbedCarthageDependencies &&
|
|
lhs.settings == rhs.settings &&
|
|
lhs.configFiles == rhs.configFiles &&
|
|
lhs.sources == rhs.sources &&
|
|
lhs.info == rhs.info &&
|
|
lhs.entitlements == rhs.entitlements &&
|
|
lhs.dependencies == rhs.dependencies &&
|
|
lhs.preBuildScripts == rhs.preBuildScripts &&
|
|
lhs.buildToolPlugins == rhs.buildToolPlugins &&
|
|
lhs.postCompileScripts == rhs.postCompileScripts &&
|
|
lhs.postBuildScripts == rhs.postBuildScripts &&
|
|
lhs.buildRules == rhs.buildRules &&
|
|
lhs.scheme == rhs.scheme &&
|
|
lhs.legacy == rhs.legacy &&
|
|
NSDictionary(dictionary: lhs.attributes).isEqual(to: rhs.attributes)
|
|
}
|
|
}
|
|
|
|
extension LegacyTarget: JSONObjectConvertible {
|
|
|
|
public init(jsonDictionary: JSONDictionary) throws {
|
|
toolPath = try jsonDictionary.json(atKeyPath: "toolPath")
|
|
arguments = jsonDictionary.json(atKeyPath: "arguments")
|
|
passSettings = jsonDictionary.json(atKeyPath: "passSettings") ?? LegacyTarget.passSettingsDefault
|
|
workingDirectory = jsonDictionary.json(atKeyPath: "workingDirectory")
|
|
}
|
|
}
|
|
|
|
extension LegacyTarget: JSONEncodable {
|
|
public func toJSONValue() -> Any {
|
|
var dict: [String: Any?] = [
|
|
"toolPath": toolPath,
|
|
"arguments": arguments,
|
|
"workingDirectory": workingDirectory,
|
|
]
|
|
|
|
if passSettings != LegacyTarget.passSettingsDefault {
|
|
dict["passSettings"] = passSettings
|
|
}
|
|
|
|
return dict
|
|
}
|
|
}
|
|
|
|
extension Target: NamedJSONDictionaryConvertible {
|
|
|
|
public init(name: String, jsonDictionary: JSONDictionary) throws {
|
|
let resolvedName: String = jsonDictionary.json(atKeyPath: "name") ?? name
|
|
self.name = resolvedName
|
|
productName = jsonDictionary.json(atKeyPath: "productName") ?? resolvedName
|
|
|
|
let typeString: String = jsonDictionary.json(atKeyPath: "type") ?? ""
|
|
if let type = PBXProductType(string: typeString) {
|
|
self.type = type
|
|
} else {
|
|
throw SpecParsingError.unknownTargetType(typeString)
|
|
}
|
|
|
|
if let supportedDestinations: [SupportedDestination] = jsonDictionary.json(atKeyPath: "supportedDestinations") {
|
|
self.supportedDestinations = supportedDestinations
|
|
}
|
|
|
|
let isResolved = jsonDictionary.json(atKeyPath: "isMultiPlatformTarget") ?? false
|
|
if isResolved, supportedDestinations != nil {
|
|
throw SpecParsingError.invalidTargetPlatformAsArray
|
|
}
|
|
|
|
var platformString: String = jsonDictionary.json(atKeyPath: "platform") ?? ""
|
|
// platform defaults to 'auto' if it is empty and we are using supported destinations
|
|
if supportedDestinations != nil, platformString.isEmpty {
|
|
platformString = Platform.auto.rawValue
|
|
}
|
|
// we add 'iOS' in supported destinations if it contains only 'macCatalyst'
|
|
if supportedDestinations?.contains(.macCatalyst) == true,
|
|
supportedDestinations?.contains(.iOS) == false {
|
|
|
|
supportedDestinations?.append(.iOS)
|
|
}
|
|
|
|
if let platform = Platform(rawValue: platformString) {
|
|
self.platform = platform
|
|
} else {
|
|
throw SpecParsingError.unknownTargetPlatform(platformString)
|
|
}
|
|
|
|
if let string: String = jsonDictionary.json(atKeyPath: "deploymentTarget") {
|
|
deploymentTarget = try Version.parse(string)
|
|
} else if let double: Double = jsonDictionary.json(atKeyPath: "deploymentTarget") {
|
|
deploymentTarget = try Version.parse(String(double))
|
|
} else {
|
|
deploymentTarget = nil
|
|
}
|
|
|
|
settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
|
|
configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:]
|
|
if let source: String = jsonDictionary.json(atKeyPath: "sources") {
|
|
sources = [TargetSource(path: source)]
|
|
} else if let array = jsonDictionary["sources"] as? [Any] {
|
|
sources = try array.compactMap { source in
|
|
if let string = source as? String {
|
|
return TargetSource(path: string)
|
|
} else if let dictionary = source as? [String: Any] {
|
|
return try TargetSource(jsonDictionary: dictionary)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
} else {
|
|
sources = []
|
|
}
|
|
if jsonDictionary["dependencies"] == nil {
|
|
dependencies = []
|
|
} else {
|
|
let dependencies: [Dependency] = try jsonDictionary.json(atKeyPath: "dependencies", invalidItemBehaviour: .fail)
|
|
self.dependencies = dependencies.filter { [platform] dependency -> Bool in
|
|
// If unspecified, all platforms are supported
|
|
guard let platforms = dependency.platforms else { return true }
|
|
return platforms.contains(platform)
|
|
}
|
|
}
|
|
|
|
if jsonDictionary["buildToolPlugins"] == nil {
|
|
buildToolPlugins = []
|
|
} else {
|
|
self.buildToolPlugins = try jsonDictionary.json(atKeyPath: "buildToolPlugins", invalidItemBehaviour: .fail)
|
|
}
|
|
|
|
if jsonDictionary["info"] != nil {
|
|
info = try jsonDictionary.json(atKeyPath: "info") as Plist
|
|
}
|
|
if jsonDictionary["entitlements"] != nil {
|
|
entitlements = try jsonDictionary.json(atKeyPath: "entitlements") as Plist
|
|
}
|
|
|
|
transitivelyLinkDependencies = jsonDictionary.json(atKeyPath: "transitivelyLinkDependencies")
|
|
directlyEmbedCarthageDependencies = jsonDictionary.json(atKeyPath: "directlyEmbedCarthageDependencies")
|
|
requiresObjCLinking = jsonDictionary.json(atKeyPath: "requiresObjCLinking")
|
|
|
|
preBuildScripts = jsonDictionary.json(atKeyPath: "preBuildScripts") ?? jsonDictionary.json(atKeyPath: "prebuildScripts") ?? []
|
|
postCompileScripts = jsonDictionary.json(atKeyPath: "postCompileScripts") ?? []
|
|
postBuildScripts = jsonDictionary.json(atKeyPath: "postBuildScripts") ?? jsonDictionary.json(atKeyPath: "postbuildScripts") ?? []
|
|
buildRules = jsonDictionary.json(atKeyPath: "buildRules") ?? []
|
|
scheme = jsonDictionary.json(atKeyPath: "scheme")
|
|
legacy = jsonDictionary.json(atKeyPath: "legacy")
|
|
attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [:]
|
|
onlyCopyFilesOnInstall = jsonDictionary.json(atKeyPath: "onlyCopyFilesOnInstall") ?? false
|
|
putResourcesBeforeSourcesBuildPhase = jsonDictionary.json(atKeyPath: "putResourcesBeforeSourcesBuildPhase") ?? false
|
|
}
|
|
}
|
|
|
|
extension Target: JSONEncodable {
|
|
public func toJSONValue() -> Any {
|
|
var dict: [String: Any?] = [
|
|
"type": type.name,
|
|
"platform": platform.rawValue,
|
|
"supportedDestinations": supportedDestinations?.map { $0.rawValue },
|
|
"settings": settings.toJSONValue(),
|
|
"configFiles": configFiles,
|
|
"attributes": attributes,
|
|
"sources": sources.map { $0.toJSONValue() },
|
|
"dependencies": dependencies.map { $0.toJSONValue() },
|
|
"postCompileScripts": postCompileScripts.map { $0.toJSONValue() },
|
|
"prebuildScripts": preBuildScripts.map { $0.toJSONValue() },
|
|
"buildToolPlugins": buildToolPlugins.map { $0.toJSONValue() },
|
|
"postbuildScripts": postBuildScripts.map { $0.toJSONValue() },
|
|
"buildRules": buildRules.map { $0.toJSONValue() },
|
|
"deploymentTarget": deploymentTarget?.deploymentTarget,
|
|
"info": info?.toJSONValue(),
|
|
"entitlements": entitlements?.toJSONValue(),
|
|
"transitivelyLinkDependencies": transitivelyLinkDependencies,
|
|
"directlyEmbedCarthageDependencies": directlyEmbedCarthageDependencies,
|
|
"requiresObjCLinking": requiresObjCLinking,
|
|
"scheme": scheme?.toJSONValue(),
|
|
"legacy": legacy?.toJSONValue(),
|
|
]
|
|
|
|
if productName != name {
|
|
dict["productName"] = productName
|
|
}
|
|
|
|
if onlyCopyFilesOnInstall {
|
|
dict["onlyCopyFilesOnInstall"] = true
|
|
}
|
|
|
|
if putResourcesBeforeSourcesBuildPhase {
|
|
dict["putResourcesBeforeSourcesBuildPhase"] = true
|
|
}
|
|
|
|
return dict
|
|
}
|
|
}
|