Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project Spec includes #44

Merged
merged 2 commits into from
Aug 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@
XCBC19846901 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
Expand All @@ -406,6 +407,7 @@
XCBC37128501 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = TestProject/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
Expand Down Expand Up @@ -472,6 +474,7 @@
XCBC60448901 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUNDLE_LOADER = "$(TEST_HOST)";
INFOPLIST_FILE = TestProjectTests/Info.plist;
Expand All @@ -489,6 +492,7 @@
XCBC86437501 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = TestProject/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
Expand Down Expand Up @@ -547,6 +551,7 @@
XCBC89077001 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUNDLE_LOADER = "$(TEST_HOST)";
INFOPLIST_FILE = TestProjectTests/Info.plist;
Expand All @@ -564,6 +569,7 @@
XCBC89204001 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
Expand Down
12 changes: 12 additions & 0 deletions Fixtures/include_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
include: [included.yml]
name: NewName
settingGroups:
test:
MY_SETTING1: NEW VALUE
MY_SETTING3: VALUE3
new:
MY_SETTING: VALUE
targets:
- name: NewTarget
type: application
platform: iOS
9 changes: 9 additions & 0 deletions Fixtures/included.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Included
settingGroups:
test:
MY_SETTING1: VALUE1
MY_SETTING2: VALUE2
targets:
- name: IncludedTarget
type: application
platform: iOS
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The project spec is a YAML or JSON file that defines your targets, configuration
- ✅ Automatically generate Schemes for **different environments** like test and production
- ✅ Easily **create new projects** with complicated setups on demand without messing around with Xcode
- ✅ Generate from anywhere including **Continuous Delivery** servers
- ✅ Distribute your spec amongst multiple files for easy **sharing** and overriding


Given a very simple project spec file like this:
Expand Down
16 changes: 2 additions & 14 deletions Sources/ProjectSpec/ProjectSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,15 @@ extension ProjectSpec.Options: Equatable {
}
}

extension ProjectSpec {

public init(path: Path) throws {
let string: String = try path.read()
try self.init(path: path, string: string)
}

public init(path: Path, string: String) throws {
let yaml = try Yams.load(yaml: string)
let json = yaml as! JSONDictionary

try self.init(jsonDictionary: json)
}
extension ProjectSpec: JSONObjectConvertible {

public init(jsonDictionary: JSONDictionary) throws {
name = try jsonDictionary.json(atKeyPath: "name")
settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
settingGroups = jsonDictionary.json(atKeyPath: "settingGroups") ?? jsonDictionary.json(atKeyPath: "settingPresets") ?? [:]
let configs: [String: String] = jsonDictionary.json(atKeyPath: "configs") ?? [:]
self.configs = configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }
self.targets = try Target.decodeTargets(jsonDictionary: jsonDictionary)
targets = try Target.decodeTargets(jsonDictionary: jsonDictionary)
schemes = try jsonDictionary.json(atKeyPath: "schemes")
if jsonDictionary["options"] != nil {
options = try jsonDictionary.json(atKeyPath: "options")
Expand Down
4 changes: 2 additions & 2 deletions Sources/ProjectSpec/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public struct Target {

extension Target {

static func decodeTargets(jsonDictionary: JSONDictionary) throws -> [Target] {
static func decodeTargets(jsonDictionary: JSONDictionary) throws -> [Target] {
guard jsonDictionary["targets"] != nil else {
return []
}
Expand Down Expand Up @@ -251,7 +251,7 @@ extension Dependency: JSONObjectConvertible {
}

embed = jsonDictionary.json(atKeyPath: "embed")

if let bool: Bool = jsonDictionary.json(atKeyPath: "codeSign") {
codeSign = bool
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/XcodeGen/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func generate(spec: String, project: String?) {

let spec: ProjectSpec
do {
spec = try ProjectSpec(path: specPath)
spec = try SpecLoader.loadSpec(path: specPath)
print("Loaded spec: \(spec.targets.count) targets, \(spec.schemes.count) schemes, \(spec.configs.count) configs")
} catch let error as DecodingError {
print("Parsing spec failed: \(error.description)")
Expand Down
2 changes: 1 addition & 1 deletion Sources/XcodeGenKit/PBXProjGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ public class PBXProjGenerator {
allFilePaths.append(path)
}
}

let groupPath: String = depth == 0 ? path.byRemovingBase(path: basePath).string : path.lastComponent
let group: PBXGroup
if let cachedGroup = groupsByPath[path] {
Expand Down
55 changes: 55 additions & 0 deletions Sources/XcodeGenKit/SpecLoader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// SpecLoader.swift
// XcodeGen
//
// Created by Yonas Kolb on 30/8/17.
//
//

import Foundation
import ProjectSpec
import PathKit
import Yams
import JSONUtilities

public struct SpecLoader {

public static func loadSpec(path: Path) throws -> ProjectSpec {
let dictionary = try loadDictionary(path: path)
return try ProjectSpec(jsonDictionary: dictionary)
}

private static func loadDictionary(path: Path) throws -> JSONDictionary {
let string: String = try path.read()
let yaml = try Yams.load(yaml: string)
guard var json = yaml as? JSONDictionary else {
throw JSONUtilsError.fileNotAJSONDictionary
}

if let includes = json["include"] as? [String] {
var includeDictionary: JSONDictionary = [:]
for include in includes {
let includePath = path.parent() + include
let dictionary = try loadDictionary(path: includePath)
includeDictionary = merge(dictionary: dictionary, onto: includeDictionary)
}
json = merge(dictionary: json, onto: includeDictionary)
}
return json
}

private static func merge(dictionary: JSONDictionary, onto base: JSONDictionary) -> JSONDictionary {
var merged = base

for (key, value) in dictionary {
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
}
}
26 changes: 21 additions & 5 deletions Tests/XcodeGenKitTests/SpecLoadingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ func specLoadingTests() {
let validTarget: [String: Any] = ["name": "test", "type": "application", "platform": "iOS"]
let invalid = "invalid"

describe("Spec Loader") {
$0.it("merges includes") {
let path = fixturePath + "include_test.yml"
let spec = try SpecLoader.loadSpec(path: path)

try expect(spec.name) == "NewName"
try expect(spec.settingGroups) == [
"test": Settings(dictionary: ["MY_SETTING1": "NEW VALUE", "MY_SETTING2": "VALUE2", "MY_SETTING3": "VALUE3"]),
"new": Settings(dictionary: ["MY_SETTING": "VALUE"]),
]
try expect(spec.targets) == [
Target(name: "IncludedTarget", type: .application, platform: .iOS),
Target(name: "NewTarget", type: .application, platform: .iOS),
]
}
}

describe("Project Spec") {

$0.it("fails with incorrect platform") {
Expand Down Expand Up @@ -66,19 +83,19 @@ func specLoadingTests() {

$0.it("parsed cross platform targets") {
let targetDictionary: [String: Any] = [
"name":"Framework",
"name": "Framework",
"platform": ["iOS", "tvOS"],
"type": "framework",
"sources": ["Framework", "Framework $platform"],
"settings": ["SETTING": "value_$platform"],
]

let spec = try getProjectSpec(["targets":[targetDictionary]])
let spec = try getProjectSpec(["targets": [targetDictionary]])
var target_iOS = Target(name: "Framework_iOS", type: .framework, platform: .iOS)
var target_tvOS = Target(name: "Framework_tvOS", type: .framework, platform: .tvOS)

target_iOS.sources = ["Framework","Framework iOS"]
target_tvOS.sources = ["Framework","Framework tvOS"]
target_iOS.sources = ["Framework", "Framework iOS"]
target_tvOS.sources = ["Framework", "Framework tvOS"]
target_iOS.settings = ["PRODUCT_NAME": "Framework", "SETTING": "value_iOS"]
target_tvOS.settings = ["PRODUCT_NAME": "Framework", "SETTING": "value_tvOS"]

Expand Down Expand Up @@ -160,6 +177,5 @@ func specLoadingTests() {
let parsedSpec = try getProjectSpec(["options": ["carthageBuildPath": "../Carthage/Build"]])
try expect(parsedSpec) == expected
}

}
}
1 change: 1 addition & 0 deletions docs/ProjectSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Required properties are marked 🔵 and optional properties with ⚪️.
## Project

- 🔵 **name**: `String` - Name of the generated project
- ⚪️ **include**: `[String]` - The paths to other specs. They will be merged in order and then the current spec will be merged on top
- ⚪️ **options**: [Options](#options) - Various options to override default behaviour
- ⚪️ **configs**: [Configs](#configs) - Project build configurations. Defaults to `Debug` and `Release` configs
- ⚪️ **settings**: [Settings](#settings) - Project specific settings. Default base and config type settings will be applied first before any settings defined here
Expand Down