From 3b17164bbfb9fcc01ec0ae968b74f0cf9292d1fa Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Sun, 22 Jul 2018 17:37:16 +1000 Subject: [PATCH] add aggregate targets --- Docs/ProjectSpec.md | 11 + Sources/ProjectSpec/AggregateTarget.swift | 46 ++++ Sources/ProjectSpec/Project.swift | 8 + Sources/ProjectSpec/ProjectTarget.swift | 24 ++ Sources/ProjectSpec/SpecValidation.swift | 46 ++-- Sources/ProjectSpec/Target.swift | 43 +--- Sources/ProjectSpec/TargetScheme.swift | 44 ++++ Sources/XcodeGenKit/PBXProjGenerator.swift | 133 +++++++---- .../Project.xcodeproj/project.pbxproj | 221 +++++++++++++----- Tests/Fixtures/TestProject/project.yml | 10 + .../ProjectGeneratorTests.swift | 23 ++ Tests/XcodeGenKitTests/ProjectSpecTests.swift | 25 ++ Tests/XcodeGenKitTests/SpecLoadingTests.swift | 12 + 13 files changed, 491 insertions(+), 155 deletions(-) create mode 100644 Sources/ProjectSpec/AggregateTarget.swift create mode 100644 Sources/ProjectSpec/ProjectTarget.swift create mode 100644 Sources/ProjectSpec/TargetScheme.swift diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 4c54cf9f..1f946103 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -23,6 +23,7 @@ Required properties are marked with checkbox. Some of the YAML examples don't sh - [Dependency](#dependency) - [Target Scheme](#target-scheme) - [Legacy Target](#legacy-target) +- [Aggregate Target](#aggregate-target) - [Scheme](#scheme) ## Project @@ -449,6 +450,16 @@ By providing a legacy target, you are opting in to the "Legacy Target" mode. Thi - [ ] ***passSettings***: Bool - Whether or not to pass build settings down to the build tool in the legacy target. - [ ] ***workingDirectory***: String - The working directory under which the build tool will be invoked in the legacy target. +## Aggregate Target + +This is used to override settings or run build scripts in specific targets + +- [x] **targets**: **[String]** - The list of target names to include as target dependencies +- [ ] **configFiles**: **[Config Files](#config-files)** - `.xcconfig` files per config +- [ ] **settings**: **[Settings](#settings)** - Target specific build settings. +- [ ] **buildScripts**: **[[Build Script](#build-script)]** - Build scripts to run +- [ ] **scheme**: **[Target Scheme](#target-scheme)** - Generated scheme + ## Scheme Schemes allows for more control than the convenience [Target Scheme](#target-scheme) on [Target](#target) diff --git a/Sources/ProjectSpec/AggregateTarget.swift b/Sources/ProjectSpec/AggregateTarget.swift new file mode 100644 index 00000000..8c635b82 --- /dev/null +++ b/Sources/ProjectSpec/AggregateTarget.swift @@ -0,0 +1,46 @@ +import Foundation +import JSONUtilities + +public struct AggregateTarget: ProjectTarget, Equatable { + public var name: String + public var targets: [String] + public var settings: Settings + public var buildScripts: [BuildScript] + public var configFiles: [String: String] + public var scheme: TargetScheme? + + public init( + name: String, + targets: [String], + settings: Settings = .empty, + configFiles: [String: String] = [:], + buildScripts: [BuildScript] = [], + scheme: TargetScheme? = nil + ) { + self.name = name + self.targets = targets + self.settings = settings + self.configFiles = configFiles + self.buildScripts = buildScripts + self.scheme = scheme + } +} + +extension AggregateTarget: CustomStringConvertible { + + public var description: String { + return "\(name): \(targets.joined(separator: ", "))" + } +} + +extension AggregateTarget: NamedJSONDictionaryConvertible { + + public init(name: String, jsonDictionary: JSONDictionary) throws { + self.name = jsonDictionary.json(atKeyPath: "name") ?? name + targets = jsonDictionary.json(atKeyPath: "targets") ?? [] + settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty + configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:] + buildScripts = jsonDictionary.json(atKeyPath: "buildScripts") ?? [] + scheme = jsonDictionary.json(atKeyPath: "scheme") + } +} diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index 89b0a2e4..246f3bb6 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -13,6 +13,7 @@ public struct Project { targetsMap = Dictionary(uniqueKeysWithValues: targets.map { ($0.name, $0) }) } } + public var aggregateTargets: [AggregateTarget] public var settings: Settings public var settingGroups: [String: Settings] @@ -30,6 +31,7 @@ public struct Project { name: String, configs: [Config] = Config.defaultConfigs, targets: [Target] = [], + aggregateTargets: [AggregateTarget] = [], settings: Settings = .empty, settingGroups: [String: Settings] = [:], schemes: [Scheme] = [], @@ -42,6 +44,7 @@ public struct Project { self.name = name self.targets = targets targetsMap = Dictionary(uniqueKeysWithValues: self.targets.map { ($0.name, $0) }) + self.aggregateTargets = aggregateTargets self.configs = configs self.settings = settings self.settingGroups = settingGroups @@ -80,6 +83,9 @@ extension Project: CustomDebugStringConvertible { if !targets.isEmpty { string += "\nTargets:\n\(indent)" + targets.map { $0.description }.joined(separator: "\n\(indent)") } + if !aggregateTargets.isEmpty { + string += "\nAggregate Targets:\n\(indent)" + aggregateTargets.map { $0.description }.joined(separator: "\n\(indent)") + } return string } @@ -90,6 +96,7 @@ extension Project: Equatable { public static func == (lhs: Project, rhs: Project) -> Bool { return lhs.name == rhs.name && lhs.targets == rhs.targets && + lhs.aggregateTargets == rhs.aggregateTargets && lhs.settings == rhs.settings && lhs.settingGroups == rhs.settingGroups && lhs.configs == rhs.configs && @@ -114,6 +121,7 @@ extension Project { self.configs = configs.isEmpty ? Config.defaultConfigs : configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name } targets = try jsonDictionary.json(atKeyPath: "targets").sorted { $0.name < $1.name } + aggregateTargets = try jsonDictionary.json(atKeyPath: "aggregateTargets").sorted { $0.name < $1.name } schemes = try jsonDictionary.json(atKeyPath: "schemes") fileGroups = jsonDictionary.json(atKeyPath: "fileGroups") ?? [] configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:] diff --git a/Sources/ProjectSpec/ProjectTarget.swift b/Sources/ProjectSpec/ProjectTarget.swift new file mode 100644 index 00000000..be8b9271 --- /dev/null +++ b/Sources/ProjectSpec/ProjectTarget.swift @@ -0,0 +1,24 @@ +// +// ProjectTarget.swift +// ProjectSpec +// +// Created by Yonas Kolb on 22/7/18. +// + +import Foundation + +protocol ProjectTarget { + + var name: String { get } + var settings: Settings { get } + var buildScripts: [BuildScript] { get } + var configFiles: [String: String] { get } + var scheme: TargetScheme? { get } +} + +extension Target { + + var buildScripts: [BuildScript] { + return prebuildScripts + postbuildScripts + } +} diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index 85706dbf..0cfb8ebc 100644 --- a/Sources/ProjectSpec/SpecValidation.swift +++ b/Sources/ProjectSpec/SpecValidation.swift @@ -53,12 +53,9 @@ extension Project { errors += validateSettings(settings) } - for target in targets { - for dependency in target.dependencies { - if dependency.type == .target, getTarget(dependency.reference) == nil { - errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference)) - } - } + let projectTargets: [ProjectTarget] = targets.map { $0 as ProjectTarget } + aggregateTargets.map { $0 as ProjectTarget } + + for target in projectTargets { for (config, configFile) in target.configFiles { if !(basePath + configFile).exists { @@ -69,13 +66,6 @@ extension Project { } } - for source in target.sources { - let sourcePath = basePath + source.path - if !source.optional && !sourcePath.exists { - errors.append(.invalidTargetSource(target: target.name, source: sourcePath.string)) - } - } - if let scheme = target.scheme { for configVariant in scheme.configVariants { @@ -84,14 +74,14 @@ extension Project { target: target.name, configVariant: configVariant, configType: .debug - )) + )) } if !configs.contains(where: { $0.name.contains(configVariant) && $0.type == .release }) { errors.append(.invalidTargetSchemeConfigVariant( target: target.name, configVariant: configVariant, configType: .release - )) + )) } } @@ -111,8 +101,7 @@ extension Project { } } - let scripts = target.prebuildScripts + target.postbuildScripts - for script in scripts { + for script in target.buildScripts { if case let .path(pathString) = script.script { let scriptPath = basePath + pathString if !scriptPath.exists { @@ -124,6 +113,29 @@ extension Project { errors += validateSettings(target.settings) } + for target in aggregateTargets { + for dependency in target.targets { + if getTarget(dependency) == nil { + errors.append(.invalidTargetDependency(target: target.name, dependency: dependency)) + } + } + } + + for target in targets { + for dependency in target.dependencies { + if dependency.type == .target, getTarget(dependency.reference) == nil { + errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference)) + } + } + + for source in target.sources { + let sourcePath = basePath + source.path + if !source.optional && !sourcePath.exists { + errors.append(.invalidTargetSource(target: target.name, source: sourcePath.string)) + } + } + } + for scheme in schemes { for buildTarget in scheme.build.targets { if getTarget(buildTarget.target) == nil { diff --git a/Sources/ProjectSpec/Target.swift b/Sources/ProjectSpec/Target.swift index a9aa63b5..5c7f9f43 100644 --- a/Sources/ProjectSpec/Target.swift +++ b/Sources/ProjectSpec/Target.swift @@ -21,7 +21,7 @@ public struct LegacyTarget: Equatable { } } -public struct Target { +public struct Target: ProjectTarget { public var name: String public var type: PBXProductType public var platform: Platform @@ -209,47 +209,6 @@ extension Target: Equatable { } } -public struct TargetScheme: Equatable { - public var testTargets: [String] - public var configVariants: [String] - public var gatherCoverageData: Bool - public var commandLineArguments: [String: Bool] - public var environmentVariables: [XCScheme.EnvironmentVariable] - public var preActions: [Scheme.ExecutionAction] - public var postActions: [Scheme.ExecutionAction] - - public init( - testTargets: [String] = [], - configVariants: [String] = [], - gatherCoverageData: Bool = false, - commandLineArguments: [String: Bool] = [:], - environmentVariables: [XCScheme.EnvironmentVariable] = [], - preActions: [Scheme.ExecutionAction] = [], - postActions: [Scheme.ExecutionAction] = [] - ) { - self.testTargets = testTargets - self.configVariants = configVariants - self.gatherCoverageData = gatherCoverageData - self.commandLineArguments = commandLineArguments - self.environmentVariables = environmentVariables - self.preActions = preActions - self.postActions = postActions - } -} - -extension TargetScheme: JSONObjectConvertible { - - public init(jsonDictionary: JSONDictionary) throws { - testTargets = jsonDictionary.json(atKeyPath: "testTargets") ?? [] - configVariants = jsonDictionary.json(atKeyPath: "configVariants") ?? [] - gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? false - commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] - environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) - preActions = jsonDictionary.json(atKeyPath: "preActions") ?? [] - postActions = jsonDictionary.json(atKeyPath: "postActions") ?? [] - } -} - extension LegacyTarget: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { diff --git a/Sources/ProjectSpec/TargetScheme.swift b/Sources/ProjectSpec/TargetScheme.swift new file mode 100644 index 00000000..a097ffd6 --- /dev/null +++ b/Sources/ProjectSpec/TargetScheme.swift @@ -0,0 +1,44 @@ +import Foundation +import JSONUtilities +import xcproj + +public struct TargetScheme: Equatable { + public var testTargets: [String] + public var configVariants: [String] + public var gatherCoverageData: Bool + public var commandLineArguments: [String: Bool] + public var environmentVariables: [XCScheme.EnvironmentVariable] + public var preActions: [Scheme.ExecutionAction] + public var postActions: [Scheme.ExecutionAction] + + public init( + testTargets: [String] = [], + configVariants: [String] = [], + gatherCoverageData: Bool = false, + commandLineArguments: [String: Bool] = [:], + environmentVariables: [XCScheme.EnvironmentVariable] = [], + preActions: [Scheme.ExecutionAction] = [], + postActions: [Scheme.ExecutionAction] = [] + ) { + self.testTargets = testTargets + self.configVariants = configVariants + self.gatherCoverageData = gatherCoverageData + self.commandLineArguments = commandLineArguments + self.environmentVariables = environmentVariables + self.preActions = preActions + self.postActions = postActions + } +} + +extension TargetScheme: JSONObjectConvertible { + + public init(jsonDictionary: JSONDictionary) throws { + testTargets = jsonDictionary.json(atKeyPath: "testTargets") ?? [] + configVariants = jsonDictionary.json(atKeyPath: "configVariants") ?? [] + gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? false + commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] + environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) + preActions = jsonDictionary.json(atKeyPath: "preActions") ?? [] + postActions = jsonDictionary.json(atKeyPath: "postActions") ?? [] + } +} diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 5b638a97..c4394897 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -150,6 +150,7 @@ public class PBXProjGenerator { } try project.targets.forEach(generateTarget) + try project.aggregateTargets.forEach(generateAggregateTarget) let productGroup = createObject( id: "Products", @@ -218,6 +219,92 @@ public class PBXProjGenerator { return pbxProj } + func generateAggregateTarget(_ target: AggregateTarget) throws { + + let configs: [ObjectReference] = 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) } + + let aggregateTarget = PBXAggregateTarget(name: target.name, + buildConfigurationList: + buildConfigList.reference, + buildPhases: buildPhases, + buildRules: [], + dependencies: dependencies, + productName: target.name, + productReference: nil, + productType: nil + ) + _ = addObject(id: target.name, aggregateTarget) + } + + func generateTargetDependency(from: String, to target: String) -> ObjectReference { + + let targetProxy = createObject( + id: "\(from)-\(target)", + PBXContainerItemProxy( + containerPortal: pbxProj.rootObject, + remoteGlobalIDString: targetObjects[target]!.reference, + proxyType: .nativeTarget, + remoteInfo: target + ) + ) + + let targetDependency = createObject( + id: "\(from)-\(target)", + PBXTargetDependency( + target: targetObjects[target]!.reference, + 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 generateTargetAttributes() -> [String: Any]? { var targetAttributes: [String: [String: Any]] = [:] @@ -415,23 +502,7 @@ public class PBXProjGenerator { guard let dependencyTarget = project.getTarget(dependencyTargetName) else { continue } let dependencyFileReference = targetFileReferences[dependencyTargetName]! - let targetProxy = createObject( - id: "\(target.name)-\(dependency.reference)", - PBXContainerItemProxy( - containerPortal: pbxProj.rootObject, - remoteGlobalIDString: targetObjects[dependencyTargetName]!.reference, - proxyType: .nativeTarget, - remoteInfo: dependencyTargetName - ) - ) - - let targetDependency = createObject( - id: dependencyTargetName + target.name, - PBXTargetDependency( - target: targetObjects[dependencyTargetName]!.reference, - targetProxy: targetProxy.reference - ) - ) + let targetDependency = generateTargetDependency(from: target.name, to: dependencyTargetName) dependencies.append(targetDependency.reference) @@ -549,31 +620,7 @@ public class PBXProjGenerator { .map { $0.reference } } - func generateBuildScript(buildScript: BuildScript) throws { - - 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 - let shellScriptPhaseReference = createObject(id: String(describing: buildScript.name) + shellScript + target.name, shellScriptPhase) - buildPhases.append(shellScriptPhaseReference.reference) - } - - try target.prebuildScripts.forEach(generateBuildScript) + buildPhases += try target.prebuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) } let sourcesBuildPhaseFiles = getBuildFilesForPhase(.sources) let sourcesBuildPhase = createObject(id: target.name, PBXSourcesBuildPhase(files: sourcesBuildPhaseFiles)) @@ -687,7 +734,7 @@ public class PBXProjGenerator { ).reference } - try target.postbuildScripts.forEach(generateBuildScript) + buildPhases += try target.postbuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) } let targetObject = targetObjects[target.name]!.object diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index d943660d..4f74ec2a 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -6,6 +6,22 @@ objectVersion = 46; objects = { +/* Begin PBXAggregateTarget section */ + AT_445731917037 /* SuperTarget */ = { + isa = PBXAggregateTarget; + buildConfigurationList = CL_445731917037 /* Build configuration list for PBXAggregateTarget "SuperTarget" */; + buildPhases = ( + SSBP_8280041834 /* MyScript */, + ); + dependencies = ( + TD_208010900457 /* PBXTargetDependency */, + TD_747418473860 /* PBXTargetDependency */, + ); + name = SuperTarget; + productName = SuperTarget; + }; +/* End PBXAggregateTarget section */ + /* Begin PBXBuildFile section */ BF_130062884703 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_257516580010 /* Alamofire.framework */; }; BF_138356261076 /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_363921640403 /* InterfaceController.swift */; }; @@ -90,6 +106,13 @@ remoteGlobalIDString = NT_507023492251; remoteInfo = "App_watchOS Extension"; }; + CIP_20801090045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = P_8448771205358 /* Project object */; + proxyType = 1; + remoteGlobalIDString = NT_825232110500; + remoteInfo = App_iOS; + }; CIP_28856087625 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = P_8448771205358 /* Project object */; @@ -132,6 +155,13 @@ remoteGlobalIDString = NT_399755008402; remoteInfo = StaticLibrary_ObjC; }; + CIP_74741847386 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = P_8448771205358 /* Project object */; + proxyType = 1; + remoteGlobalIDString = NT_472296042419; + remoteInfo = Framework_iOS; + }; CIP_78441234790 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = P_8448771205358 /* Project object */; @@ -642,7 +672,7 @@ buildRules = ( ); dependencies = ( - TD_432517223942 /* PBXTargetDependency */, + TD_676191581124 /* PBXTargetDependency */, ); name = App_iOS_UITests; productName = App_iOS_UITests; @@ -662,7 +692,7 @@ buildRules = ( ); dependencies = ( - TD_249869083204 /* PBXTargetDependency */, + TD_193291182921 /* PBXTargetDependency */, ); name = App_watchOS; productName = App_watchOS; @@ -696,7 +726,7 @@ buildRules = ( ); dependencies = ( - TD_562366646359 /* PBXTargetDependency */, + TD_784412347908 /* PBXTargetDependency */, ); name = Framework_watchOS; productName = Framework_watchOS; @@ -715,7 +745,7 @@ buildRules = ( ); dependencies = ( - TD_202479103763 /* PBXTargetDependency */, + TD_667455552356 /* PBXTargetDependency */, ); name = Framework_iOS; productName = Framework_iOS; @@ -751,7 +781,7 @@ buildRules = ( ); dependencies = ( - TD_698828390883 /* PBXTargetDependency */, + TD_337103940101 /* PBXTargetDependency */, ); name = Framework_macOS; productName = Framework_macOS; @@ -786,7 +816,7 @@ buildRules = ( ); dependencies = ( - TD_883611297273 /* PBXTargetDependency */, + TD_957476409477 /* PBXTargetDependency */, ); name = Framework_tvOS; productName = Framework_tvOS; @@ -803,7 +833,7 @@ buildRules = ( ); dependencies = ( - TD_436638162860 /* PBXTargetDependency */, + TD_887143865471 /* PBXTargetDependency */, ); name = App_iOS_Tests; productName = App_iOS_Tests; @@ -826,10 +856,10 @@ buildRules = ( ); dependencies = ( - TD_257389546865 /* PBXTargetDependency */, - TD_354342487294 /* PBXTargetDependency */, - TD_490183489588 /* PBXTargetDependency */, - TD_669996692733 /* PBXTargetDependency */, + TD_824101787753 /* PBXTargetDependency */, + TD_109724965966 /* PBXTargetDependency */, + TD_721522128177 /* PBXTargetDependency */, + TD_288560876254 /* PBXTargetDependency */, ); name = App_iOS; productName = App_iOS; @@ -847,7 +877,7 @@ buildRules = ( ); dependencies = ( - TD_762620471828 /* PBXTargetDependency */, + TD_317921131342 /* PBXTargetDependency */, ); name = iMessageApp; productName = iMessageApp; @@ -1088,6 +1118,20 @@ shellPath = /bin/sh; shellScript = "echo \"You ran a script\"\n"; }; + SSBP_8280041834 /* MyScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = MyScript; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"do the thing\""; + }; SSBP_8706434794 /* MyScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1215,62 +1259,72 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - TD_202479103763 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = NT_399755008402 /* StaticLibrary_ObjC */; - targetProxy = CIP_66745555235 /* PBXContainerItemProxy */; - }; - TD_249869083204 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = NT_507023492251 /* App_watchOS Extension */; - targetProxy = CIP_19329118292 /* PBXContainerItemProxy */; - }; - TD_257389546865 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = NT_324671077936 /* App_watchOS */; - targetProxy = CIP_82410178775 /* PBXContainerItemProxy */; - }; - TD_354342487294 /* PBXTargetDependency */ = { + TD_109724965966 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = NT_472296042419 /* Framework_iOS */; targetProxy = CIP_10972496596 /* PBXContainerItemProxy */; }; - TD_432517223942 /* PBXTargetDependency */ = { + TD_193291182921 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_507023492251 /* App_watchOS Extension */; + targetProxy = CIP_19329118292 /* PBXContainerItemProxy */; + }; + TD_208010900457 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = NT_825232110500 /* App_iOS */; - targetProxy = CIP_67619158112 /* PBXContainerItemProxy */; + targetProxy = CIP_20801090045 /* PBXContainerItemProxy */; }; - TD_436638162860 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = NT_825232110500 /* App_iOS */; - targetProxy = CIP_88714386547 /* PBXContainerItemProxy */; - }; - TD_490183489588 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = NT_399755008402 /* StaticLibrary_ObjC */; - targetProxy = CIP_72152212817 /* PBXContainerItemProxy */; - }; - TD_562366646359 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = NT_399755008402 /* StaticLibrary_ObjC */; - targetProxy = CIP_78441234790 /* PBXContainerItemProxy */; - }; - TD_669996692733 /* PBXTargetDependency */ = { + TD_288560876254 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = NT_935153865209 /* iMessageApp */; targetProxy = CIP_28856087625 /* PBXContainerItemProxy */; }; - TD_698828390883 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = NT_399755008402 /* StaticLibrary_ObjC */; - targetProxy = CIP_33710394010 /* PBXContainerItemProxy */; - }; - TD_762620471828 /* PBXTargetDependency */ = { + TD_317921131342 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = NT_618687462494 /* iMessageExtension */; targetProxy = CIP_31792113134 /* PBXContainerItemProxy */; }; - TD_883611297273 /* PBXTargetDependency */ = { + TD_337103940101 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_399755008402 /* StaticLibrary_ObjC */; + targetProxy = CIP_33710394010 /* PBXContainerItemProxy */; + }; + TD_667455552356 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_399755008402 /* StaticLibrary_ObjC */; + targetProxy = CIP_66745555235 /* PBXContainerItemProxy */; + }; + TD_676191581124 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_825232110500 /* App_iOS */; + targetProxy = CIP_67619158112 /* PBXContainerItemProxy */; + }; + TD_721522128177 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_399755008402 /* StaticLibrary_ObjC */; + targetProxy = CIP_72152212817 /* PBXContainerItemProxy */; + }; + TD_747418473860 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_472296042419 /* Framework_iOS */; + targetProxy = CIP_74741847386 /* PBXContainerItemProxy */; + }; + TD_784412347908 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_399755008402 /* StaticLibrary_ObjC */; + targetProxy = CIP_78441234790 /* PBXContainerItemProxy */; + }; + TD_824101787753 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_324671077936 /* App_watchOS */; + targetProxy = CIP_82410178775 /* PBXContainerItemProxy */; + }; + TD_887143865471 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = NT_825232110500 /* App_iOS */; + targetProxy = CIP_88714386547 /* PBXContainerItemProxy */; + }; + TD_957476409477 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = NT_399755008402 /* StaticLibrary_ObjC */; targetProxy = CIP_95747640947 /* PBXContainerItemProxy */; @@ -1365,6 +1419,13 @@ }; name = "Test Debug"; }; + BC_122527152613 /* Test Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + MY_SETTING = hello; + }; + name = "Test Release"; + }; BC_128103534773 /* Production Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1719,6 +1780,13 @@ }; name = "Staging Debug"; }; + BC_334821934458 /* Production Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + MY_SETTING = hello; + }; + name = "Production Release"; + }; BC_351179072988 /* Test Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1777,6 +1845,13 @@ }; name = "Production Release"; }; + BC_370290618113 /* Production Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + MY_SETTING = hello; + }; + name = "Production Debug"; + }; BC_380703106572 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1903,6 +1978,13 @@ }; name = "Production Release"; }; + BC_439666751882 /* Staging Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + MY_SETTING = hello; + }; + name = "Staging Debug"; + }; BC_444705348320 /* Staging Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1936,6 +2018,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-ObjC"; SDKROOT = iphoneos; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Test Release"; @@ -2291,6 +2374,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-ObjC"; SDKROOT = iphoneos; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Production Release"; @@ -2343,6 +2427,13 @@ }; name = "Production Release"; }; + BC_612244244384 /* Test Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + MY_SETTING = hello; + }; + name = "Test Debug"; + }; BC_622471558373 /* Production Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2374,6 +2465,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-ObjC"; SDKROOT = iphoneos; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Production Debug"; @@ -2605,6 +2697,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-ObjC"; SDKROOT = iphoneos; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Staging Debug"; @@ -2789,6 +2882,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-ObjC"; SDKROOT = iphoneos; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Staging Release"; @@ -2985,6 +3079,13 @@ }; name = "Test Debug"; }; + BC_851086512031 /* Staging Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + MY_SETTING = hello; + }; + name = "Staging Release"; + }; BC_864485046941 /* Staging Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3008,6 +3109,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-ObjC"; SDKROOT = iphoneos; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Test Debug"; @@ -3092,6 +3194,19 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = ""; }; + CL_445731917037 /* Build configuration list for PBXAggregateTarget "SuperTarget" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BC_370290618113 /* Production Debug */, + BC_334821934458 /* Production Release */, + BC_439666751882 /* Staging Debug */, + BC_851086512031 /* Staging Release */, + BC_612244244384 /* Test Debug */, + BC_122527152613 /* Test Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ""; + }; CL_472296042419 /* Build configuration list for PBXNativeTarget "Framework_iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Tests/Fixtures/TestProject/project.yml b/Tests/Fixtures/TestProject/project.yml index 7c378c80..45de1ac0 100644 --- a/Tests/Fixtures/TestProject/project.yml +++ b/Tests/Fixtures/TestProject/project.yml @@ -158,3 +158,13 @@ schemes: targetTemplates: MyTemplate: scheme: {} +aggregateTargets: + SuperTarget: + targets: + - App_iOS + - Framework_iOS + settings: + MY_SETTING: hello + buildScripts: + - name: MyScript + script: echo "do the thing" diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index ed408e3a..58a864f3 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -187,6 +187,29 @@ class ProjectGeneratorTests: XCTestCase { } } + func testAggregateTargets() { + describe { + + let aggregateTarget = AggregateTarget(name: "AggregateTarget", targets: ["MyApp", "MyFramework"] ) + let project = Project(basePath: "", name: "test", targets: targets, aggregateTargets: [aggregateTarget]) + + $0.it("generates aggregate targets") { + let pbxProject = try project.generatePbxProj() + let aggregateTargets = pbxProject.objects.aggregateTargets.referenceValues + try expect(aggregateTargets.count) == 1 + guard let pbxAggregateTarget = aggregateTargets.first else { + throw failure("Couldn't find AggregateTarget") + } + + try expect(pbxAggregateTarget.name) == "AggregateTarget" + try expect(pbxAggregateTarget.dependencies.count) == 2 + + let targetDependencies = pbxProject.objects.targetDependencies.referenceValues + try expect(targetDependencies.count) == 4 + } + } + } + func testTargets() { describe { diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index baf68df7..abf3207a 100644 --- a/Tests/XcodeGenKitTests/ProjectSpecTests.swift +++ b/Tests/XcodeGenKitTests/ProjectSpecTests.swift @@ -132,6 +132,31 @@ class ProjectSpecTests: XCTestCase { try expectValidationError(project, .invalidTargetSchemeConfigVariant(target: "target1", configVariant: "invalidVariant", configType: .debug)) } + $0.it("fails with invalid aggregate target") { + var project = baseProject + project.aggregateTargets = [AggregateTarget( + name: "target1", + targets: ["invalidDependency"], + settings: invalidSettings, + configFiles: ["invalidConfig": "invalidConfigFile"], + buildScripts: [BuildScript(script: .path("invalidPrebuildScript"), name: "buildScript1")], + scheme: TargetScheme(testTargets: ["invalidTarget"]) + )] + + try expectValidationError(project, .invalidTargetDependency(target: "target1", dependency: "invalidDependency")) + try expectValidationError(project, .invalidTargetConfigFile(target: "target1", configFile: "invalidConfigFile", config: "invalidConfig")) + try expectValidationError(project, .invalidTargetSchemeTest(target: "target1", testTarget: "invalidTarget")) + try expectValidationError(project, .invalidBuildSettingConfig("invalidConfig")) + try expectValidationError(project, .invalidSettingsGroup("invalidSettingGroup")) + try expectValidationError(project, .invalidBuildScriptPath(target: "target1", name: "buildScript1", path: "invalidPrebuildScript")) + + try expectValidationError(project, .missingConfigForTargetScheme(target: "target1", configType: .debug)) + try expectValidationError(project, .missingConfigForTargetScheme(target: "target1", configType: .release)) + + project.aggregateTargets[0].scheme?.configVariants = ["invalidVariant"] + try expectValidationError(project, .invalidTargetSchemeConfigVariant(target: "target1", configVariant: "invalidVariant", configType: .debug)) + } + $0.it("fails with invalid scheme") { var project = baseProject project.schemes = [Scheme( diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index f1162d85..a5e1120a 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -206,6 +206,18 @@ class SpecLoadingTests: XCTestCase { try expect(target.sources) == ["templateSource", "targetSource"] // merges array in order } + $0.it("parses aggregate targets") { + let dictionary: [String: Any] = [ + "targets": ["target_1", "target_2"], + "settings": ["SETTING": "VALUE"], + "configFiles": ["debug": "file.xcconfig"], + ] + + let project = try getProjectSpec(["aggregateTargets": ["AggregateTarget": dictionary]]) + let expectedTarget = AggregateTarget(name: "AggregateTarget", targets: ["target_1", "target_2"], settings: ["SETTING": "VALUE"], configFiles: ["debug": "file.xcconfig"]) + try expect(project.aggregateTargets) == [expectedTarget] + } + $0.it("parses target schemes") { var targetDictionary = validTarget targetDictionary["scheme"] = [