From 74f0986ffb88a1eff81fe2d8f7b30a45f59896c5 Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Sun, 23 Jul 2017 20:58:03 +0200 Subject: [PATCH] better build phases --- .../spec.xcodeproj/project.pbxproj | 164 ++++++------ Fixtures/TestProject/spec.yml | 1 + Sources/XcodeGen/main.swift | 1 - Sources/XcodeGenKit/BuildSettingsPreset.swift | 50 ++++ Sources/XcodeGenKit/Decoding.swift | 14 + Sources/XcodeGenKit/PBXProjGenerator.swift | 242 ++++++++++++++++++ Sources/XcodeGenKit/ProjectExtensions.swift | 2 + Sources/XcodeGenKit/ProjectGenerator.swift | 207 +-------------- 8 files changed, 402 insertions(+), 279 deletions(-) create mode 100644 Sources/XcodeGenKit/BuildSettingsPreset.swift create mode 100644 Sources/XcodeGenKit/PBXProjGenerator.swift diff --git a/Fixtures/TestProject/spec.xcodeproj/project.pbxproj b/Fixtures/TestProject/spec.xcodeproj/project.pbxproj index f0a248d5..541cf2d5 100644 --- a/Fixtures/TestProject/spec.xcodeproj/project.pbxproj +++ b/Fixtures/TestProject/spec.xcodeproj/project.pbxproj @@ -8,20 +8,20 @@ /* Begin PBXBuildFile section */ OBJECT_10 = {isa = PBXBuildFile; fileRef = OBJECT_9 /* TestProject.app */; }; - OBJECT_15 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_11 /* FrameworkFile.swift */; }; - OBJECT_16 /* Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_12 /* Info.plist */; }; - OBJECT_17 /* MyFramework.h in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_13 /* MyFramework.h */; }; - OBJECT_30 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_24 /* AppDelegate.swift */; }; - OBJECT_31 /* Assets.xcassets in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_25 /* Assets.xcassets */; }; - OBJECT_32 /* Base.lproj in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_26 /* Base.lproj */; }; - OBJECT_33 /* Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_27 /* Info.plist */; }; - OBJECT_34 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_28 /* ViewController.swift */; }; + OBJECT_15 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_11 /* FrameworkFile.swift */; settings = {}; }; + OBJECT_16 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = OBJECT_12 /* Info.plist */; settings = {}; }; + OBJECT_17 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = OBJECT_13 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; + OBJECT_31 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_25 /* AppDelegate.swift */; settings = {}; }; + OBJECT_32 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = OBJECT_26 /* Assets.xcassets */; settings = {}; }; + OBJECT_33 /* Base.lproj in Resources */ = {isa = PBXBuildFile; fileRef = OBJECT_27 /* Base.lproj */; settings = {}; }; + OBJECT_34 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = OBJECT_28 /* Info.plist */; settings = {}; }; + OBJECT_35 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_29 /* ViewController.swift */; settings = {}; }; OBJECT_7 /* MyFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = OBJECT_6 /* MyFramework.framework */; }; OBJECT_7 /* MyFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = OBJECT_6 /* MyFramework.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - OBJECT_38 /* PBXContainerItemProxy */ = { + OBJECT_39 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = OBJECT_1 /* Project object */; proxyType = 1; @@ -30,50 +30,28 @@ }; /* End PBXContainerItemProxy section */ -/* Begin PBXCopyFilesBuildPhase section */ - OBJECT_23 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - OBJECT_42 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - OBJECT_7 /* MyFramework.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ - OBJECT_11 /* FrameworkFile.swift */ = {isa = PBXFileReference; path = FrameworkFile.swift; sourceTree = ""; }; - OBJECT_12 /* Info.plist */ = {isa = PBXFileReference; path = Info.plist; sourceTree = ""; }; - OBJECT_13 /* MyFramework.h */ = {isa = PBXFileReference; path = MyFramework.h; sourceTree = ""; }; - OBJECT_24 /* AppDelegate.swift */ = {isa = PBXFileReference; path = AppDelegate.swift; sourceTree = ""; }; - OBJECT_25 /* Assets.xcassets */ = {isa = PBXFileReference; path = Assets.xcassets; sourceTree = ""; }; - OBJECT_26 /* Base.lproj */ = {isa = PBXFileReference; path = Base.lproj; sourceTree = ""; }; - OBJECT_27 /* Info.plist */ = {isa = PBXFileReference; path = Info.plist; sourceTree = ""; }; - OBJECT_28 /* ViewController.swift */ = {isa = PBXFileReference; path = ViewController.swift; sourceTree = ""; }; + OBJECT_11 /* FrameworkFile.swift */ = {isa = PBXFileReference; name = FrameworkFile.swift; path = FrameworkFile.swift; sourceTree = ""; }; + OBJECT_12 /* Info.plist */ = {isa = PBXFileReference; name = Info.plist; path = Info.plist; sourceTree = ""; }; + OBJECT_13 /* MyFramework.h */ = {isa = PBXFileReference; name = MyFramework.h; path = MyFramework.h; sourceTree = ""; }; + OBJECT_25 /* AppDelegate.swift */ = {isa = PBXFileReference; name = AppDelegate.swift; path = AppDelegate.swift; sourceTree = ""; }; + OBJECT_26 /* Assets.xcassets */ = {isa = PBXFileReference; name = Assets.xcassets; path = Assets.xcassets; sourceTree = ""; }; + OBJECT_27 /* Base.lproj */ = {isa = PBXFileReference; name = Base.lproj; path = Base.lproj; sourceTree = ""; }; + OBJECT_28 /* Info.plist */ = {isa = PBXFileReference; name = Info.plist; path = Info.plist; sourceTree = ""; }; + OBJECT_29 /* ViewController.swift */ = {isa = PBXFileReference; name = ViewController.swift; path = ViewController.swift; sourceTree = ""; }; OBJECT_6 /* MyFramework.framework */ = {isa = PBXFileReference; explicitFileType = framework; includeInIndex = 0; path = MyFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; OBJECT_9 /* TestProject.app */ = {isa = PBXFileReference; explicitFileType = app; includeInIndex = 0; path = TestProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - OBJECT_22 /* Frameworks */ = { + OBJECT_24 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - OBJECT_41 /* Frameworks */ = { + OBJECT_44 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -95,20 +73,20 @@ path = MyFramework; sourceTree = ""; }; - OBJECT_29 /* TestProject */ = { + OBJECT_30 /* TestProject */ = { isa = PBXGroup; children = ( - OBJECT_25 /* Assets.xcassets */, - OBJECT_28 /* ViewController.swift */, - OBJECT_24 /* AppDelegate.swift */, - OBJECT_26 /* Base.lproj */, - OBJECT_27 /* Info.plist */, + OBJECT_25 /* AppDelegate.swift */, + OBJECT_28 /* Info.plist */, + OBJECT_26 /* Assets.xcassets */, + OBJECT_29 /* ViewController.swift */, + OBJECT_27 /* Base.lproj */, ); name = TestProject; path = TestProject; sourceTree = ""; }; - OBJECT_43 /* Products */ = { + OBJECT_45 /* Products */ = { isa = PBXGroup; children = ( OBJECT_6 /* MyFramework.framework */, @@ -117,25 +95,44 @@ name = Products; sourceTree = ""; }; - OBJECT_44 = { + OBJECT_46 = { isa = PBXGroup; children = ( OBJECT_14 /* MyFramework */, - OBJECT_29 /* TestProject */, - OBJECT_43 /* Products */, + OBJECT_30 /* TestProject */, + OBJECT_45 /* Products */, ); sourceTree = ""; }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + OBJECT_23 /* Frameworks */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + OBJECT_17, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + OBJECT_43 /* Frameworks */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ OBJECT_5 /* MyFramework */ = { isa = PBXNativeTarget; buildConfigurationList = OBJECT_20 /* Build configuration list for PBXNativeTarget "MyFramework" */; buildPhases = ( OBJECT_21 /* Sources */, - OBJECT_22 /* Frameworks */, - OBJECT_23 /* Copy Files */, + OBJECT_22 /* Resources */, + OBJECT_23 /* Headers */, + OBJECT_24 /* Frameworks */, ); buildRules = ( ); @@ -147,16 +144,17 @@ }; OBJECT_8 /* TestProject */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJECT_37 /* Build configuration list for PBXNativeTarget "TestProject" */; + buildConfigurationList = OBJECT_38 /* Build configuration list for PBXNativeTarget "TestProject" */; buildPhases = ( - OBJECT_40 /* Sources */, - OBJECT_41 /* Frameworks */, - OBJECT_42 /* Copy Files */, + OBJECT_41 /* Sources */, + OBJECT_42 /* Resources */, + OBJECT_43 /* Headers */, + OBJECT_44 /* Frameworks */, ); buildRules = ( ); dependencies = ( - OBJECT_39 /* PBXTargetDependency */, + OBJECT_40 /* PBXTargetDependency */, ); name = TestProject; productReference = OBJECT_9; @@ -173,7 +171,7 @@ compatibilityVersion = "Xcode 3.2"; knownRegions = ( ); - mainGroup = OBJECT_44; + mainGroup = OBJECT_46; targets = ( OBJECT_5 /* MyFramework */, OBJECT_8 /* TestProject */, @@ -181,36 +179,52 @@ }; /* End PBXProject section */ +/* Begin PBXResourcesBuildPhase section */ + OBJECT_22 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + OBJECT_16, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + OBJECT_42 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + OBJECT_33, + OBJECT_34, + OBJECT_32, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ OBJECT_21 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( OBJECT_15 /* FrameworkFile.swift in Sources */, - OBJECT_17 /* MyFramework.h in Sources */, - OBJECT_16 /* Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - OBJECT_40 /* Sources */ = { + OBJECT_41 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - OBJECT_33 /* Info.plist in Sources */, - OBJECT_31 /* Assets.xcassets in Sources */, - OBJECT_34 /* ViewController.swift in Sources */, - OBJECT_30 /* AppDelegate.swift in Sources */, - OBJECT_32 /* Base.lproj in Sources */, + OBJECT_35 /* ViewController.swift in Sources */, + OBJECT_31 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - OBJECT_39 /* PBXTargetDependency */ = { + OBJECT_40 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = OBJECT_5 /* MyFramework */; - targetProxy = OBJECT_38 /* PBXContainerItemProxy */; + targetProxy = OBJECT_39 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -338,7 +352,7 @@ }; name = release; }; - OBJECT_35 /* debug */ = { + OBJECT_36 /* debug */ = { isa = XCBuildConfiguration; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -385,6 +399,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 10.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; ONLY_ACTIVE_ARCH = true; + PRODUCT_BUNDLE_IDENTIFIER = com.test; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -393,7 +408,7 @@ }; name = debug; }; - OBJECT_36 /* release */ = { + OBJECT_37 /* release */ = { isa = XCBuildConfiguration; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -434,6 +449,7 @@ INFOPLIST_FILE = TestProject/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.test; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -454,11 +470,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = ""; }; - OBJECT_37 /* Build configuration list for PBXNativeTarget "TestProject" */ = { + OBJECT_38 /* Build configuration list for PBXNativeTarget "TestProject" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJECT_35 /* debug */, - OBJECT_36 /* release */, + OBJECT_36 /* debug */, + OBJECT_37 /* release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = ""; diff --git a/Fixtures/TestProject/spec.yml b/Fixtures/TestProject/spec.yml index e0882c6d..d2f7adc7 100644 --- a/Fixtures/TestProject/spec.yml +++ b/Fixtures/TestProject/spec.yml @@ -17,6 +17,7 @@ targets: - TestProject buildSettings: INFOPLIST_FILE: TestProject/Info.plist + PRODUCT_BUNDLE_IDENTIFIER: com.test dependencies: - name: MyFramework type: target diff --git a/Sources/XcodeGen/main.swift b/Sources/XcodeGen/main.swift index 15a3234b..99ea15dd 100644 --- a/Sources/XcodeGen/main.swift +++ b/Sources/XcodeGen/main.swift @@ -12,7 +12,6 @@ import Commander import XcodeGenKit import xcodeproj - func generate(spec: String, project: String?) { let specPath = Path(spec).normalize() diff --git a/Sources/XcodeGenKit/BuildSettingsPreset.swift b/Sources/XcodeGenKit/BuildSettingsPreset.swift new file mode 100644 index 00000000..0281788d --- /dev/null +++ b/Sources/XcodeGenKit/BuildSettingsPreset.swift @@ -0,0 +1,50 @@ +// +// BuildSettingGroupType.swift +// XcodeGen +// +// Created by Yonas Kolb on 23/7/17. +// +// + +import Foundation +import xcodeproj +import PathKit +import Yams +import JSONUtilities + +enum BuildSettingsPreset { + case config(ConfigType) + case platform(Platform) + case product(PBXProductType) + case base + + var path: String { + switch self { + case let .config(config): return "configs/\(config.rawValue)" + case let .platform(platform): return "platforms/\(platform.rawValue)" + case let .product(product): return "products/\(product.rawValue)" + case .base: return "base" + } + } + + private static var buildSettings: [String: BuildSettings] = [:] + + func getBuildSettings() throws -> BuildSettings? { + if let group = BuildSettingsPreset.buildSettings[path] { + return group + } + let settingsPath = Path(#file).parent().parent().parent() + "setting_groups/\(path).yml" + guard settingsPath.exists else { return nil } + let buildSettings = try BuildSettings(path: settingsPath) + BuildSettingsPreset.buildSettings[path] = buildSettings + return buildSettings + } +} + +extension BuildSettings: JSONObjectConvertible { + + public init(jsonDictionary: JSONDictionary) throws { + self.init(dictionary: jsonDictionary) + } +} + diff --git a/Sources/XcodeGenKit/Decoding.swift b/Sources/XcodeGenKit/Decoding.swift index 4cb7a668..ad448f02 100644 --- a/Sources/XcodeGenKit/Decoding.swift +++ b/Sources/XcodeGenKit/Decoding.swift @@ -8,6 +8,8 @@ import Foundation import JSONUtilities +import PathKit +import Yams extension Dictionary where Key: JSONKey { @@ -46,3 +48,15 @@ public protocol NamedJSONConvertible { init(name: String, json: Any) throws } + +extension JSONObjectConvertible { + + public init(path: Path) throws { + let content: String = try path.read() + let yaml = try Yams.load(yaml: content) + guard let jsonDictionary = yaml as? JSONDictionary else { + throw JSONUtilsError.fileNotAJSONDictionary + } + try self.init(jsonDictionary: jsonDictionary) + } +} diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift new file mode 100644 index 00000000..6da733d5 --- /dev/null +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -0,0 +1,242 @@ +// +// PBXProjGenerator.swift +// XcodeGen +// +// Created by Yonas Kolb on 23/7/17. +// +// + +import Foundation +import Foundation +import PathKit +import xcodeproj +import xcodeprojprotocols +import JSONUtilities +import Yams + +public class PBXProjGenerator { + + let spec: Spec + + var objects: [PBXObject] = [] + var fileReferencesByPath: [Path: PBXFileReference] = [:] + var groupsByPath: [String: String] = [:] + + var targetNativeReferences: [String: String] = [:] + var targetBuildFileReferences: [String: String] = [:] + var targetFileReferences: [String: String] = [:] + var topLevelGroups: [PBXGroup] = [] + + var ids = 0 + var projectReference: String + + public init(spec: Spec) { + self.spec = spec + projectReference = "" + projectReference = id() + } + + func id() -> String { + ids += 1 + // return ids.description.md5().uppercased() + return "OBJECT_\(ids)" + } + + public func generate() throws -> PBXProj { + let buildConfigs = spec.configs.map { config in + XCBuildConfiguration(reference: id(), name: config.name, baseConfigurationReference: nil, buildSettings: config.buildSettings) + } + let buildConfigList = XCConfigurationList(reference: id(), buildConfigurations: buildConfigs.referenceSet, defaultConfigurationName: buildConfigs.first?.name ?? "", defaultConfigurationIsVisible: 0) + + objects += buildConfigs.map { .xcBuildConfiguration($0) } + objects.append(.xcConfigurationList(buildConfigList)) + + for target in spec.targets { + targetNativeReferences[target.name] = id() + + let fileReference = PBXFileReference(reference: id(), sourceTree: .buildProductsDir, explicitFileType: target.type.fileExtension, path: target.filename, includeInIndex: 0) + objects.append(.pbxFileReference(fileReference)) + targetFileReferences[target.name] = fileReference.reference + + let buildFile = PBXBuildFile(reference: id(), fileRef: fileReference.reference) + objects.append(.pbxBuildFile(buildFile)) + targetBuildFileReferences[target.name] = buildFile.reference + } + + for target in spec.targets { + try generateTarget(target) + } + + let productGroup = PBXGroup(reference: id(), children: Set(targetFileReferences.values), sourceTree: .group, name: "Products") + objects.append(.pbxGroup(productGroup)) + topLevelGroups.append(productGroup) + + let mainGroup = PBXGroup(reference: id(), children: topLevelGroups.referenceSet, sourceTree: .group) + objects.append(.pbxGroup(mainGroup)) + + let pbxProjectRoot = PBXProject(reference: projectReference, buildConfigurationList: buildConfigList.reference, compatibilityVersion: "Xcode 3.2", mainGroup: mainGroup.reference, targets: Array(targetNativeReferences.values)) + objects.append(.pbxProject(pbxProjectRoot)) + + return PBXProj(archiveVersion: 1, objectVersion: 46, rootObject: projectReference, objects: objects) + } + + struct SourceFile { + let path: Path + let fileReference: PBXFileReference + 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: id(), fileRef: fileReference.reference, settings: settings) + objects.append(.pbxBuildFile(buildFile)) + return SourceFile(path: path, fileReference: fileReference, buildFile: buildFile) + } + + func generateTarget(_ target: Target) throws { + let source = spec.path.parent() + target.sources.first! + //TODO: handle multiple sources + //TODO: handle targets with shared sources + + let sourceGroup = try getGroup(path: source) + topLevelGroups.append(sourceGroup.group) + let sourceFiles = sourceGroup.filePaths.map(generateSourceFile) + + let configs: [XCBuildConfiguration] = try spec.configs.map { config in + let buildSettings = try getTargetBuildSettings(config: config, target: target) + return XCBuildConfiguration(reference: id(), name: config.name, baseConfigurationReference: nil, buildSettings: buildSettings) + } + objects += configs.map { .xcBuildConfiguration($0) } + let buildConfigList = XCConfigurationList(reference: id(), buildConfigurations: configs.referenceSet, defaultConfigurationName: "") + objects.append(.xcConfigurationList(buildConfigList)) + + var dependancies: [String] = [] + var frameworkFiles: [String] = [] + var copyFiles: [String] = [] + for dependancy in target.dependencies { + switch dependancy.type { + case .target: + let targetProxy = PBXContainerItemProxy(reference: id(), containerPortal: projectReference, remoteGlobalIDString: targetNativeReferences[dependancy.name]!, proxyType: .nativeTarget, remoteInfo: dependancy.name) + let targetDependancy = PBXTargetDependency(reference: id(), target: targetNativeReferences[dependancy.name]!, targetProxy: targetProxy.reference ) + + objects.append(.pbxContainerItemProxy(targetProxy)) + objects.append(.pbxTargetDependency(targetDependancy)) + dependancies.append(targetDependancy.reference) + + let dependencyBuildFile = targetBuildFileReferences[dependancy.name]! + frameworkFiles.append(dependencyBuildFile) + copyFiles.append(dependencyBuildFile) + case .system: + //TODO: handle system frameworks + break + } + } + + let fileReference = targetFileReferences[target.name]! + var buildPhases: [String] = [] + + if target.type == .framework { + let buildFile = PBXBuildFile(reference: targetBuildFileReferences[target.name]!, fileRef: fileReference) + objects.append(.pbxBuildFile(buildFile)) + } + + func getBuildFilesForPhase(_ buildPhase: BuildPhase) -> Set { + let files = sourceFiles.filter { getBuildPhaseForPath($0.path) == buildPhase } + return Set(files.map { $0.buildFile.reference }) + } + + let sourcesBuildPhase = PBXSourcesBuildPhase(reference: id(), files: getBuildFilesForPhase(.sources)) + objects.append(.pbxSourcesBuildPhase(sourcesBuildPhase)) + buildPhases.append(sourcesBuildPhase.reference) + + let resourcesBuildPhase = PBXResourcesBuildPhase(reference: id(), files: getBuildFilesForPhase(.resources)) + objects.append(.pbxResourcesBuildPhase(resourcesBuildPhase)) + buildPhases.append(resourcesBuildPhase.reference) + + let headersBuildPhase = PBXHeadersBuildPhase(reference: id(), files: getBuildFilesForPhase(.headers)) + objects.append(.pbxHeadersBuildPhase(headersBuildPhase)) + buildPhases.append(headersBuildPhase.reference) + + let frameworkBuildPhase = PBXFrameworksBuildPhase(reference: id(), files: Set(frameworkFiles), runOnlyForDeploymentPostprocessing: 0) + objects.append(.pbxFrameworksBuildPhase(frameworkBuildPhase)) + buildPhases.append(frameworkBuildPhase.reference) + +// let copyFilesPhase = PBXCopyFilesBuildPhase(reference: id(), dstPath: "", dstSubfolderSpec: .frameworks, files: Set(copyFiles)) +// objects.append(.pbxCopyFilesBuildPhase(copyFilesPhase)) +// buildPhases.append(copyFilesPhase.reference) + + let nativeTarget = PBXNativeTarget( + reference: targetNativeReferences[target.name]!, + buildConfigurationList: buildConfigList.reference, + buildPhases: buildPhases, + buildRules: [], + dependencies: dependancies, + name: target.name, + productReference: fileReference, + productType: target.type) + objects.append(.pbxNativeTarget(nativeTarget)) + } + + func getBuildPhaseForPath(_ path: Path) -> BuildPhase? { + if let fileExtension = path.extension { + switch fileExtension { + case "swift": return .sources + case "h", "hh", "hpp", "ipp", "tpp", "hxx", "def": return .headers + default: return .resources + } + } + return nil + } + + func getTargetBuildSettings(config: Config, target: Target) throws -> BuildSettings { + var buildSettings = BuildSettings() + + func getBuildSettingPreset(_ type: BuildSettingsPreset) throws -> BuildSettings? { + return try type.getBuildSettings() + } + + buildSettings += try getBuildSettingPreset(.base) + if let configType = config.type { + buildSettings += try getBuildSettingPreset(.config(configType)) + } + buildSettings += try getBuildSettingPreset(.platform(target.platform)) + buildSettings += try getBuildSettingPreset(.product(target.type)) + buildSettings += target.buildSettings?.buildSettings + buildSettings += target.buildSettings?.configSettings[config.name] + + return buildSettings + } + + func getGroup(path: Path) throws -> (filePaths: [Path], group: PBXGroup) { + + let directories = try path.children().filter { $0.isDirectory && $0.extension == nil } + var filePaths = try path.children().filter { $0.isFile || $0.extension != nil } + var groupChildren: [String] = [] + + for path in filePaths { + if let fileReference = fileReferencesByPath[path] { + groupChildren.append(fileReference.reference) + } else { + let fileReference = PBXFileReference(reference: id(), sourceTree: .group, path: path.lastComponent) + objects.append(.pbxFileReference(fileReference)) + fileReferencesByPath[path] = fileReference + groupChildren.append(fileReference.reference) + } + } + + for path in directories { + let subGroup = try getGroup(path: path) + filePaths += subGroup.filePaths + groupChildren.append(subGroup.group.reference) + } + + let group = PBXGroup(reference: id(), children: Set(groupChildren), sourceTree: .group, name: path.lastComponent, path: path.lastComponent) + objects.append(.pbxGroup(group)) + return (filePaths, group) + } + +} diff --git a/Sources/XcodeGenKit/ProjectExtensions.swift b/Sources/XcodeGenKit/ProjectExtensions.swift index d2696b87..03a58734 100644 --- a/Sources/XcodeGenKit/ProjectExtensions.swift +++ b/Sources/XcodeGenKit/ProjectExtensions.swift @@ -8,6 +8,7 @@ import Foundation import xcodeproj +import PathKit extension Array where Element: ProjectElement { @@ -45,3 +46,4 @@ func +=( lhs: inout BuildSettings, rhs: BuildSettings?) { guard let rhs = rhs else { return } lhs.merge(rhs) } + diff --git a/Sources/XcodeGenKit/ProjectGenerator.swift b/Sources/XcodeGenKit/ProjectGenerator.swift index 51673711..ae9cc501 100644 --- a/Sources/XcodeGenKit/ProjectGenerator.swift +++ b/Sources/XcodeGenKit/ProjectGenerator.swift @@ -16,221 +16,20 @@ import Yams public class ProjectGenerator { let spec: Spec - - var groups: [PBXGroup] = [] - var fileReferences: [PBXFileReference] = [] - var groupsByPath: [String: String] = [:] - - var targetNativeReferences: [String: String] = [:] - var targetBuildFileReferences: [String: String] = [:] - var targetFileReferences: [String: String] = [:] - - var ids = 0 - var projectReference: String - - var buildSettingGroups: [String: BuildSettings] = [:] - - enum BuildSettingGroupType { - case config(ConfigType) - case platform(Platform) - case product(PBXProductType) - case base - - var path: String { - switch self { - case let .config(config): return "configs/\(config.rawValue)" - case let .platform(platform): return "platforms/\(platform.rawValue)" - case let .product(product): return "products/\(product.rawValue)" - case .base: return "base" - } - } - } - - func getBuildSettingGroup(_ type: BuildSettingGroupType) throws -> BuildSettings? { - if let group = buildSettingGroups[type.path] { - return group - } - let path = Path(#file).parent().parent().parent() + "setting_groups/\(type.path).yml" - guard path.exists else { return nil } - let content: String = try path.read() - let yaml = try Yams.load(yaml: content) - let buildSettings = BuildSettings(dictionary: yaml as! JSONDictionary) - buildSettingGroups[type.path] = buildSettings - return buildSettings - } + let pbxProjGenerator: PBXProjGenerator public init(spec: Spec) { self.spec = spec - projectReference = "" - projectReference = id() - } - - func id() -> String { - ids += 1 -// return ids.description.md5().uppercased() - return "OBJECT_\(ids)" + pbxProjGenerator = PBXProjGenerator(spec: spec) } public func generate() throws -> XcodeProj { - let pbxProject = try generatePBXProj() + let pbxProject = try pbxProjGenerator.generate() let workspace = try generateWorkspace() let sharedData = try generateSharedData() return XcodeProj(workspace: workspace, pbxproj: pbxProject, sharedData: sharedData) } - func generatePBXProj() throws -> PBXProj { - var objects: [PBXObject] = [] - - let buildConfigs = spec.configs.map { config in - XCBuildConfiguration(reference: id(), name: config.name, baseConfigurationReference: nil, buildSettings: config.buildSettings) - } - let buildConfigList = XCConfigurationList(reference: id(), buildConfigurations: buildConfigs.referenceSet, defaultConfigurationName: buildConfigs.first?.name ?? "", defaultConfigurationIsVisible: 0) - - objects += buildConfigs.map { .xcBuildConfiguration($0) } - objects.append(.xcConfigurationList(buildConfigList)) - - var topLevelGroups: [PBXGroup] = [] - - for target in spec.targets { - targetNativeReferences[target.name] = id() - - let fileReference = PBXFileReference(reference: id(), sourceTree: .buildProductsDir, explicitFileType: target.type.fileExtension, path: target.filename, includeInIndex: 0) - objects.append(.pbxFileReference(fileReference)) - targetFileReferences[target.name] = fileReference.reference - - let buildFile = PBXBuildFile(reference: id(), fileRef: fileReference.reference) - objects.append(.pbxBuildFile(buildFile)) - targetBuildFileReferences[target.name] = buildFile.reference - } - - for target in spec.targets { - let source = spec.path.parent() + target.sources.first! - //TODO: handle multiple sources - //TODO: handle targets with shared sources - - groups = [] - fileReferences = [] - let sourceGroup = try getGroup(path: source) - topLevelGroups.append(sourceGroup) - objects += groups.map { .pbxGroup($0) } - objects += fileReferences.map { .pbxFileReference($0) } - - let buildFiles = fileReferences.map { PBXBuildFile(reference: id(), fileRef: $0.reference) } - objects += buildFiles.map { .pbxBuildFile($0) } - - let configs: [XCBuildConfiguration] = try spec.configs.map { config in - let buildSettings = try getTargetBuildSettings(config: config, target: target) - return XCBuildConfiguration(reference: id(), name: config.name, baseConfigurationReference: nil, buildSettings: buildSettings) - } - objects += configs.map { .xcBuildConfiguration($0) } - let buildConfigList = XCConfigurationList(reference: id(), buildConfigurations: configs.referenceSet, defaultConfigurationName: "") - objects.append(.xcConfigurationList(buildConfigList)) - - var dependancies: [String] = [] - var frameworkFiles: [String] = [] - var copyFiles: [String] = [] - for dependancy in target.dependencies { - switch dependancy.type { - case .target: - let targetProxy = PBXContainerItemProxy(reference: id(), containerPortal: projectReference, remoteGlobalIDString: targetNativeReferences[dependancy.name]!, proxyType: .nativeTarget, remoteInfo: dependancy.name) - let targetDependancy = PBXTargetDependency(reference: id(), target: targetNativeReferences[dependancy.name]!, targetProxy: targetProxy.reference ) - - objects.append(.pbxContainerItemProxy(targetProxy)) - objects.append(.pbxTargetDependency(targetDependancy)) - dependancies.append(targetDependancy.reference) - - let dependencyBuildFile = targetBuildFileReferences[dependancy.name]! - frameworkFiles.append(dependencyBuildFile) - copyFiles.append(dependencyBuildFile) - case .system: - //TODO: handle system frameworks - break - } - } - - let fileReference = targetFileReferences[target.name]! - if target.type == .framework { - let buildFile = PBXBuildFile(reference: targetBuildFileReferences[target.name]!, fileRef: fileReference) - objects.append(.pbxBuildFile(buildFile)) - } - - var buildPhases: [String] = [] - - let buildPhase = PBXSourcesBuildPhase(reference: id(), files: buildFiles.referenceSet) - objects.append(.pbxSourcesBuildPhase(buildPhase)) - buildPhases.append(buildPhase.reference) - - let frameworkPhase = PBXFrameworksBuildPhase(reference: id(), files: Set(frameworkFiles), runOnlyForDeploymentPostprocessing: 0) - objects.append(.pbxFrameworksBuildPhase(frameworkPhase)) - buildPhases.append(frameworkPhase.reference) - - let copyPhase = PBXCopyFilesBuildPhase(reference: id(), dstPath: "", dstSubfolderSpec: .frameworks, files: Set(copyFiles)) - objects.append(.pbxCopyFilesBuildPhase(copyPhase)) - buildPhases.append(copyPhase.reference) - - let nativeTarget = PBXNativeTarget( - reference: targetNativeReferences[target.name]!, - buildConfigurationList: buildConfigList.reference, - buildPhases: buildPhases, - buildRules: [], - dependencies: dependancies, - name: target.name, - productReference: fileReference, - productType: target.type) - objects.append(.pbxNativeTarget(nativeTarget)) - } - - let productGroup = PBXGroup(reference: id(), children: Set(targetFileReferences.values), sourceTree: .group, name: "Products") - objects.append(.pbxGroup(productGroup)) - topLevelGroups.append(productGroup) - - let mainGroup = PBXGroup(reference: id(), children: topLevelGroups.referenceSet, sourceTree: .group) - objects.append(.pbxGroup(mainGroup)) - - let pbxProjectRoot = PBXProject(reference: projectReference, buildConfigurationList: buildConfigList.reference, compatibilityVersion: "Xcode 3.2", mainGroup: mainGroup.reference, targets: Array(targetNativeReferences.values)) - objects.append(.pbxProject(pbxProjectRoot)) - - return PBXProj(archiveVersion: 1, objectVersion: 46, rootObject: projectReference, objects: objects) - } - - func getTargetBuildSettings(config: Config, target: Target) throws -> BuildSettings { - var buildSettings = BuildSettings() - - buildSettings += try getBuildSettingGroup(.base) - if let configType = config.type { - buildSettings += try getBuildSettingGroup(.config(configType)) - } - buildSettings += try getBuildSettingGroup(.platform(target.platform)) - buildSettings += try getBuildSettingGroup(.product(target.type)) - buildSettings += target.buildSettings?.buildSettings - buildSettings += target.buildSettings?.configSettings[config.name] - - return buildSettings - } - - func getGroup(path: Path) throws -> PBXGroup { - - let directories = try path.children().filter { $0.isDirectory && $0.extension == nil } - let files = try path.children().filter { $0.isFile || $0.extension != nil } - var children: [String] = [] - - let fileReferences = files.map { path in - PBXFileReference(reference: id(), sourceTree: .group, path: path.lastComponent) - } - - self.fileReferences += fileReferences - children += fileReferences.referenceList - - for path in directories { - let group = try getGroup(path: path) - children.append(group.reference) - } - - let group = PBXGroup(reference: id(), children: Set(children), sourceTree: .group, name: path.lastComponent, path: path.lastComponent) - groups.append(group) - return group - } - func generateWorkspace() throws -> XCWorkspace { let workspaceReferences: [XCWorkspace.Data.FileRef] = [XCWorkspace.Data.FileRef.project(path: Path(""))] let workspaceData = XCWorkspace.Data(references: workspaceReferences)