add target templates

This commit is contained in:
Yonas Kolb 2018-07-22 21:49:39 +10:00
parent a43a96075a
commit c50d1dd157
10 changed files with 169 additions and 22 deletions

View File

@ -38,6 +38,7 @@ Required properties are marked with checkbox. Some of the YAML examples don't sh
- [ ] **targets**: **[String: [Target](#target)]** - The list of targets in the project mapped by name - [ ] **targets**: **[String: [Target](#target)]** - The list of targets in the project mapped by name
- [ ] **fileGroups**: **[String]** - A list of paths to add to the root of the project. These aren't files that will be included in your targets, but that you'd like to include in the project hierachy anyway. For example a folder of xcconfig files that aren't already added by any target sources, or a Readme file. - [ ] **fileGroups**: **[String]** - A list of paths to add to the root of the project. These aren't files that will be included in your targets, but that you'd like to include in the project hierachy anyway. For example a folder of xcconfig files that aren't already added by any target sources, or a Readme file.
- [ ] **schemes**: **[Scheme](#scheme)** - A list of schemes by name. This allows more control over what is found in [Target Scheme](#target-scheme) - [ ] **schemes**: **[Scheme](#scheme)** - A list of schemes by name. This allows more control over what is found in [Target Scheme](#target-scheme)
- [ ] **targetTemplates**: **[String: [Target](#target)]** - a list of targets that can be used as templates for actual targets which reference them via a `template` property. They can be used to extract common target settings. Works great in combination with `include`.
### Include ### Include
@ -163,6 +164,7 @@ Settings are merged in the following order: groups, base, configs.
- `INFOPLIST_FILE`: If it doesn't exist your sources will be searched for `Info.plist` files and the first one found will be used for this setting - `INFOPLIST_FILE`: If it doesn't exist your sources will be searched for `Info.plist` files and the first one found will be used for this setting
- `FRAMEWORK_SEARCH_PATHS`: If carthage dependencies are used, the platform build path will be added to this setting - `FRAMEWORK_SEARCH_PATHS`: If carthage dependencies are used, the platform build path will be added to this setting
- [ ] **dependencies**: **[[Dependency](#dependency)]** - Dependencies for the target - [ ] **dependencies**: **[[Dependency](#dependency)]** - Dependencies for the target
- [ ] **templates**: **[String]** - A list of target templates that will be merged in order
- [ ] **transitivelyLinkDependencies**: **Bool** - If this is not specified the value from the project set in [Options](#options)`.transitivelyLinkDependencies` will be used. - [ ] **transitivelyLinkDependencies**: **Bool** - If this is not specified the value from the project set in [Options](#options)`.transitivelyLinkDependencies` will be used.
- [ ] **prebuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases - [ ] **prebuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases
- [ ] **postbuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases - [ ] **postbuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases

View File

@ -128,6 +128,6 @@ extension Project {
} }
static func filterJSON(jsonDictionary: JSONDictionary) throws -> JSONDictionary { static func filterJSON(jsonDictionary: JSONDictionary) throws -> JSONDictionary {
return try Target.generateCrossPlaformTargets(jsonDictionary: jsonDictionary) return try Target.resolveTargets(jsonDictionary: jsonDictionary)
} }
} }

View File

@ -101,6 +101,24 @@ extension Dictionary where Key == String, Value: Any {
} }
} }
func merge(dictionary: JSONDictionary, onto base: JSONDictionary) -> JSONDictionary {
var merged = base
for (key, value) in dictionary {
if key.hasSuffix(":REPLACE") {
let newKey = key.replacingOccurrences(of: ":REPLACE", with: "")
merged[newKey] = value
} else if let dictionary = value as? JSONDictionary, let base = merged[key] as? JSONDictionary {
merged[key] = merge(dictionary: dictionary, onto: base)
} else if let array = value as? [Any], let base = merged[key] as? [Any] {
merged[key] = base + array
} else {
merged[key] = value
}
}
return merged
}
public func += (lhs: inout BuildSettings, rhs: BuildSettings?) { public func += (lhs: inout BuildSettings, rhs: BuildSettings?) {
guard let rhs = rhs else { return } guard let rhs = rhs else { return }
lhs.merge(rhs) lhs.merge(rhs)

View File

@ -45,22 +45,4 @@ extension Project {
} }
return json return json
} }
private static func merge(dictionary: JSONDictionary, onto base: JSONDictionary) -> JSONDictionary {
var merged = base
for (key, value) in dictionary {
if key.hasSuffix(":REPLACE") {
let newKey = key.replacingOccurrences(of: ":REPLACE", with: "")
merged[newKey] = value
} else if let dictionary = value as? JSONDictionary, let base = merged[key] as? JSONDictionary {
merged[key] = merge(dictionary: dictionary, onto: base)
} else if let array = value as? [Any], let base = merged[key] as? [Any] {
merged[key] = base + array
} else {
merged[key] = value
}
}
return merged
}
} }

View File

@ -95,11 +95,27 @@ extension Target: CustomStringConvertible {
extension Target { extension Target {
static func generateCrossPlaformTargets(jsonDictionary: JSONDictionary) throws -> JSONDictionary { static func resolveTargets(jsonDictionary: JSONDictionary) throws -> JSONDictionary {
guard let targetsDictionary: [String: JSONDictionary] = jsonDictionary["targets"] as? [String: JSONDictionary] else { guard var targetsDictionary: [String: JSONDictionary] = jsonDictionary["targets"] as? [String: JSONDictionary] else {
return jsonDictionary return jsonDictionary
} }
let targetTemplatesDictionary: [String: JSONDictionary] = jsonDictionary["targetTemplates"] as? [String: JSONDictionary] ?? [:]
for (targetName, var target) in targetsDictionary {
if let templates = target["templates"] as? [String] {
var mergedDictionary: JSONDictionary = [:]
for template in templates {
if let templateDictionary = targetTemplatesDictionary[template] {
mergedDictionary = merge(dictionary: templateDictionary, onto: mergedDictionary)
}
}
target = merge(dictionary: mergedDictionary, onto: target)
}
targetsDictionary[targetName] = target
}
let platformReplacement = "$platform" let platformReplacement = "$platform"
var crossPlatformTargets: [String: JSONDictionary] = [:] var crossPlatformTargets: [String: JSONDictionary] = [:]
@ -156,6 +172,7 @@ extension Target {
} }
} }
var merged = jsonDictionary var merged = jsonDictionary
merged["targets"] = crossPlatformTargets merged["targets"] = crossPlatformTargets
return merged return merged
} }

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "NT_324671077936"
BuildableName = "App_watchOS.app"
BlueprintName = "App_watchOS"
ReferencedContainer = "container:Project.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Production Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "NT_324671077936"
BuildableName = "App_watchOS.app"
BlueprintName = "App_watchOS"
ReferencedContainer = "container:Project.xcodeproj">
</BuildableReference>
</MacroExpansion>
<CommandLineArguments>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Production Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "NT_324671077936"
BuildableName = "App_watchOS.app"
BlueprintName = "App_watchOS"
ReferencedContainer = "container:Project.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Production Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "NT_324671077936"
BuildableName = "App_watchOS.app"
BlueprintName = "App_watchOS"
ReferencedContainer = "container:Project.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Production Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Production Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -80,6 +80,7 @@ targets:
PRODUCT_BUNDLE_IDENTIFIER: com.project.app.watch PRODUCT_BUNDLE_IDENTIFIER: com.project.app.watch
dependencies: dependencies:
- target: App_watchOS Extension - target: App_watchOS Extension
templates: [MyTemplate]
App_watchOS Extension: App_watchOS Extension:
type: watchkit2-extension type: watchkit2-extension
@ -148,3 +149,6 @@ schemes:
commandLineArguments: commandLineArguments:
argument: YES argument: YES
argument.with.dot: YES argument.with.dot: YES
targetTemplates:
MyTemplate:
scheme: {}

View File

@ -12,6 +12,9 @@ targets:
NewTarget: NewTarget:
type: application type: application
platform: iOS platform: iOS
templates: [IncludedTemplate]
sources:
- folder1
IncludedTarget: IncludedTarget:
name: IncludedTargetNew name: IncludedTargetNew
platform: tvOS platform: tvOS

View File

@ -11,3 +11,7 @@ targets:
platform: iOS platform: iOS
sources: sources:
- Target - Target
targetTemplates:
IncludedTemplate:
sources:
- folder2

View File

@ -22,7 +22,7 @@ class SpecLoadingTests: XCTestCase {
] ]
try expect(project.targets) == [ try expect(project.targets) == [
Target(name: "IncludedTargetNew", type: .application, platform: .tvOS, sources: ["NewSource"]), Target(name: "IncludedTargetNew", type: .application, platform: .tvOS, sources: ["NewSource"]),
Target(name: "NewTarget", type: .application, platform: .iOS), Target(name: "NewTarget", type: .application, platform: .iOS, sources: ["folder1", "folder2"]),
] ]
} }
@ -176,6 +176,26 @@ class SpecLoadingTests: XCTestCase {
try expect(project.targets) == [target_iOS, target_tvOS] try expect(project.targets) == [target_iOS, target_tvOS]
} }
$0.it("parses target templates") {
let targetDictionary: [String: Any] = [
"type": "framework",
"templates": ["temp2", "temp"]
]
let project = try getProjectSpec([
"targets": ["Framework": targetDictionary],
"targetTemplates": [
"temp": ["platform": "iOS"],
"temp2": ["platform": "tvOS"],
]
])
let target = Target(name: "Framework", type: .framework, platform: .iOS)
try expect(project.targets.first) == target
}
$0.it("parses target schemes") { $0.it("parses target schemes") {
var targetDictionary = validTarget var targetDictionary = validTarget
targetDictionary["scheme"] = [ targetDictionary["scheme"] = [