initial commit

This commit is contained in:
Yonas Kolb 2017-07-19 14:55:01 +02:00
commit ef402f6e36
16 changed files with 615 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
/.build
/Packages
XcodeGen.xcodeproj

66
Package.pins Normal file
View File

@ -0,0 +1,66 @@
{
"autoPin": true,
"pins": [
{
"package": "AEXML",
"reason": null,
"repositoryURL": "https://github.com/tadija/AEXML.git",
"version": "4.1.0"
},
{
"package": "Commander",
"reason": null,
"repositoryURL": "https://github.com/kylef/Commander.git",
"version": "0.6.1"
},
{
"package": "CryptoSwift",
"reason": null,
"repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git",
"version": "0.6.9"
},
{
"package": "JSONUtilities",
"reason": null,
"repositoryURL": "https://github.com/yonaskolb/JSONUtilities.git",
"version": "3.3.5"
},
{
"package": "PathKit",
"reason": null,
"repositoryURL": "https://github.com/kylef/PathKit.git",
"version": "0.8.0"
},
{
"package": "Rainbow",
"reason": null,
"repositoryURL": "https://github.com/onevcat/Rainbow",
"version": "2.0.1"
},
{
"package": "Spectre",
"reason": null,
"repositoryURL": "https://github.com/kylef/Spectre.git",
"version": "0.7.2"
},
{
"package": "Unbox",
"reason": null,
"repositoryURL": "https://github.com/JohnSundell/Unbox",
"version": "2.4.0"
},
{
"package": "Yams",
"reason": null,
"repositoryURL": "https://github.com/jpsim/Yams.git",
"version": "0.2.0"
},
{
"package": "xcodeproj",
"reason": null,
"repositoryURL": "https://github.com/yonaskolb/xcodeproj.git",
"version": "0.0.4"
}
],
"version": 1
}

20
Package.swift Normal file
View File

@ -0,0 +1,20 @@
// swift-tools-version:3.1
import PackageDescription
let package = Package(
name: "XcodeGen",
targets: [
Target(name: "XcodeGen", dependencies: ["XcodeGenKit"]),
Target(name: "XcodeGenKit"),
],
dependencies: [
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 8),
.Package(url: "https://github.com/kylef/Commander.git", majorVersion: 0, minor: 6),
.Package(url: "https://github.com/jpsim/Yams.git", majorVersion: 0, minor: 2),
.Package(url: "https://github.com/yonaskolb/JSONUtilities.git", majorVersion: 3, minor: 3),
.Package(url: "https://github.com/kylef/Spectre.git", majorVersion: 0, minor: 7),
.Package(url: "https://github.com/onevcat/Rainbow", majorVersion: 2),
.Package(url: "https://github.com/yonaskolb/xcodeproj.git", majorVersion: 0, minor: 0),
]
)

View File

@ -0,0 +1,40 @@
//
// main.swift
// SwiftySwagger
//
// Created by Yonas Kolb on 17/09/2016.
// Copyright © 2016 Yonas Kolb. All rights reserved.
//
import Foundation
import PathKit
import Commander
import XcodeGenKit
func generate(spec: String) {
let specPath = Path("~/Developer/XcodeGen/test_spec.yml").normalize()
let spec: Spec
do {
spec = try Spec(path: specPath)
print(spec)
print("")
} catch {
print("Parsing spec failed: \(error)")
return
}
do {
try Generator.generate(spec: spec, path: Path("~/Developer/XcodeGen/test_project.xcodeproj").normalize())
print("Generated Xcode Project")
} catch {
print("Generation failed: \(error)")
}
}
command(
Option<String>("spec", "", flag: "p", description: "The path to the spec file"),
generate)
.run()

View File

@ -0,0 +1,31 @@
//
// Decoding.swift
// XcodeGen
//
// Created by Yonas Kolb on 19/5/17.
//
//
import Foundation
import JSONUtilities
extension Dictionary where Key: JSONKey {
public func json<T: NamedJSONObjectConvertible>(atKeyPath keyPath: KeyPath, invalidItemBehaviour: InvalidItemBehaviour<T> = .remove) throws -> [T] {
guard let dictionary = json(atKeyPath: keyPath) as JSONDictionary? else {
return []
}
var items: [T] = []
for (key, _) in dictionary {
let jsonDictionary: JSONDictionary = try dictionary.json(atKeyPath: .key(key))
let item = try T(name: key, jsonDictionary: jsonDictionary)
items.append(item)
}
return items
}
}
public protocol NamedJSONObjectConvertible {
init(name: String, jsonDictionary: JSONDictionary) throws
}

View File

@ -0,0 +1,80 @@
//
// Generator.swift
// XcodeGen
//
// Created by Yonas Kolb on 19/5/17.
//
//
import Foundation
import PathKit
import xcodeproj
import xcodeprojprotocols
public struct Generator {
public static func generate(spec: Spec, path: Path) throws {
let workspaceReferences: [XCWorkspace.Data.FileRef] = [XCWorkspace.Data.FileRef.project(path: path)]
let workspaceData = XCWorkspace.Data(path: path, references: workspaceReferences)
let workspace = XCWorkspace(path: path + "project.xcworkspace", data: workspaceData)
var objects: [PBXObject] = []
var ids = 0
func id() -> String {
ids += 1
return "OBJECT_\(ids)"
}
for target in spec.targets {
let sourcePaths: [Path] = target.sources.reduce([]) { paths, source in
// $0 + spec.path.parent().glob($1)
let sourcePaths = try! (spec.path.parent() + source).recursiveChildren().filter { $0.isFile }
return paths + sourcePaths
}
let fileReferences = sourcePaths.map { PBXFileReference(reference: id(), sourceTree: .group, path: $0.lastComponent) }
let buildFiles = fileReferences.map { PBXBuildFile(reference: id(), fileRef: $0.reference) }
let buildPhase = PBXSourcesBuildPhase(reference: id(), files: Set(buildFiles.map { $0.reference }))
let buildPhases = [buildPhase]
let nativeTarget = PBXNativeTarget(reference: "OBJECT_\(objects.count)", buildConfigurationList: "234", buildPhases: buildPhases.map{ $0.reference }, buildRules: [], dependencies: [], name: target.name)
objects += buildFiles.map { .pbxBuildFile($0) }
objects += fileReferences.map { .pbxFileReference($0) }
objects += buildPhases.map { .pbxSourcesBuildPhase($0) }
objects.append(.pbxNativeTarget(nativeTarget))
}
let pbxProject = PBXProj(path: path + "project.pbxproj", name: "Generated_Project", archiveVersion: 1, objectVersion: 46, rootObject: "12345", objects: objects)
let schemes: [XCScheme] = spec.schemes.map { schemeSpec in
// let buildEntries: [XCScheme.BuildAction.Entry] = schemeSpec.build.entries.map { build in
// let buildableReference: XCScheme.BuildableReference? = nil
// return XCScheme.BuildAction.Entry(buildableReference: buildableReference!, buildFor: build.buildTypes)
// }
let buildAction = XCScheme.BuildAction(buildActionEntries: [], parallelizeBuild: true, buildImplicitDependencies: true)
return XCScheme(path: path, lastUpgradeVersion: nil, version: nil, buildAction: buildAction, testAction: nil, launchAction: nil, profileAction: nil, analyzeAction: nil, archiveAction: nil)
}
let sharedData = XCSharedData(path: path, schemes: schemes)
let project = XcodeProj(path: path, workspace: workspace, pbxproj: pbxProject, sharedData: sharedData)
try project.write(override: true)
}
}
extension XcodeProj: Writable {
public func write(override: Bool) throws {
if override && path.exists {
try path.delete()
}
try path.mkpath()
try pbxproj.write(override: override)
}
}

View File

@ -0,0 +1,58 @@
//
// Scheme.swift
// XcodeGen
//
// Created by Yonas Kolb on 19/5/17.
//
//
import Foundation
import xcodeproj
import JSONUtilities
public struct SchemeSpec {
public var name: String
public var build: Build
public struct Build {
public var entries: [BuildEntry]
}
public struct BuildEntry {
public var target: String
public var buildTypes: [XCScheme.BuildAction.Entry.BuildFor]
}
}
extension SchemeSpec: NamedJSONObjectConvertible {
public init(name: String, jsonDictionary: JSONDictionary) throws {
self.name = name
build = try jsonDictionary.json(atKeyPath: "build")
}
}
extension SchemeSpec.Build: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
entries = try jsonDictionary.json(atKeyPath: "targets")
}
}
extension SchemeSpec.BuildEntry: NamedJSONObjectConvertible {
public init(name: String, jsonDictionary: JSONDictionary) throws {
target = name
buildTypes = jsonDictionary.json(atKeyPath: "buildTypes") ?? [.running, .testing, .archiving, .analyzing]
}
}
extension XCScheme.BuildAction.Entry.BuildFor: JSONPrimitiveConvertible {
public typealias JSONType = String
public static func from(jsonValue: String) -> XCScheme.BuildAction.Entry.BuildFor? {
return .testing
}
}

View File

@ -0,0 +1,129 @@
//
// Spec.swift
// XcodeGen
//
// Created by Yonas Kolb on 19/5/17.
//
//
import Foundation
import JSONUtilities
import PathKit
import Yams
extension Spec {
public init(path: Path) throws {
var url = URL(string: path.string)!
if url.scheme == nil {
url = URL(fileURLWithPath: path.string)
}
let data = try Data(contentsOf: url)
let string = String(data: data, encoding: .utf8)!
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(path: path, jsonDictionary: json)
}
public init(path: Path, jsonDictionary: JSONDictionary) throws {
self.path = path
settingGroups = try jsonDictionary.json(atKeyPath: "settingGroups")
configs = try jsonDictionary.json(atKeyPath: "configs")
targets = try jsonDictionary.json(atKeyPath: "targets")
schemes = try jsonDictionary.json(atKeyPath: "schemes")
}
}
public struct Spec {
public var settingGroups: [BuildSettingGroup]
public var configs: [Config]
public var targets: [TargetSpec]
public var schemes: [SchemeSpec]
public var path: Path
}
public struct TargetSpec {
public var name: String
public var type: String
public var localizedSource: String?
public var sources: [String]
public var sourceExludes: [String]
public var dependancies: [Dependancy]
public var prebuildScripts: [String]
public var postbuildScripts: [String]
}
extension TargetSpec: NamedJSONObjectConvertible {
public init(name: String, jsonDictionary: JSONDictionary) throws {
self.name = name
type = try jsonDictionary.json(atKeyPath: "type")
sources = jsonDictionary.json(atKeyPath: "sources") ?? []
sourceExludes = jsonDictionary.json(atKeyPath: "sourceExludes") ?? []
dependancies = jsonDictionary.json(atKeyPath: "dependancies") ?? []
prebuildScripts = jsonDictionary.json(atKeyPath: "prebuildScripts") ?? []
postbuildScripts = jsonDictionary.json(atKeyPath: "postbuildScripts") ?? []
}
}
public struct Dependancy {
public var path: String
public var type: DependancyType
public enum DependancyType: String {
case target
case system
}
}
extension Dependancy: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
path = try jsonDictionary.json(atKeyPath: "path")
type = try jsonDictionary.json(atKeyPath: "type")
}
}
public struct Config {
public var name: String
public var buildSettingGroups: [BuildSettingGroup]
public var settings: [String: String]
}
extension Config: NamedJSONObjectConvertible {
public init(name: String, jsonDictionary: JSONDictionary) throws {
self.name = name
buildSettingGroups = try jsonDictionary.json(atKeyPath: "settingGroups")
settings = jsonDictionary.json(atKeyPath: "settings") ?? [:]
}
}
public struct BuildSettingGroup {
public var name: String
public var buildSettings: [String: String]
}
extension BuildSettingGroup: NamedJSONObjectConvertible {
public init(name: String, jsonDictionary: JSONDictionary) throws {
self.name = name
buildSettings = [:]
for (key, value) in jsonDictionary {
buildSettings[key] = String(describing: value)
}
}
}

6
Tests/LinuxMain.swift Normal file
View File

@ -0,0 +1,6 @@
import XCTest
@testable import XcodeGenTests
XCTMain([
testCase(XcodeGenTests.allTests),
])

View File

@ -0,0 +1,15 @@
import XCTest
@testable import XcodeGen
class XcodeGenTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
XCTAssertEqual(XcodeGen().text, "Hello, World!")
}
static var allTests = [
("testExample", testExample),
]
}

View File

@ -0,0 +1,42 @@
---
ALWAYS_SEARCH_USER_PATHS: 'NO'
CLANG_ANALYZER_NONNULL: 'YES'
CLANG_CXX_LANGUAGE_STANDARD: gnu++0x
CLANG_CXX_LIBRARY: libc++
CLANG_ENABLE_MODULES: 'YES'
CLANG_ENABLE_OBJC_ARC: 'YES'
CLANG_WARN_BOOL_CONVERSION: 'YES'
CLANG_WARN_CONSTANT_CONVERSION: 'YES'
CLANG_WARN_DIRECT_OBJC_ISA_USAGE: YES_ERROR
CLANG_WARN_DOCUMENTATION_COMMENTS: 'YES'
CLANG_WARN_EMPTY_BODY: 'YES'
CLANG_WARN_ENUM_CONVERSION: 'YES'
CLANG_WARN_INFINITE_RECURSION: 'YES'
CLANG_WARN_INT_CONVERSION: 'YES'
CLANG_WARN_OBJC_ROOT_CLASS: YES_ERROR
CLANG_WARN_SUSPICIOUS_MOVES: 'YES'
CLANG_WARN_UNREACHABLE_CODE: 'YES'
CLANG_WARN__DUPLICATE_METHOD_MATCH: 'YES'
COPY_PHASE_STRIP: 'NO'
DEBUG_INFORMATION_FORMAT: dwarf
ENABLE_STRICT_OBJC_MSGSEND: 'YES'
ENABLE_TESTABILITY: 'YES'
GCC_C_LANGUAGE_STANDARD: gnu99
GCC_DYNAMIC_NO_PIC: 'NO'
GCC_NO_COMMON_BLOCKS: 'YES'
GCC_OPTIMIZATION_LEVEL: '0'
GCC_PREPROCESSOR_DEFINITIONS:
- DEBUG=1
- "$(inherited)"
GCC_WARN_64_TO_32_BIT_CONVERSION: 'YES'
GCC_WARN_ABOUT_RETURN_TYPE: YES_ERROR
GCC_WARN_UNDECLARED_SELECTOR: 'YES'
GCC_WARN_UNINITIALIZED_AUTOS: YES_AGGRESSIVE
GCC_WARN_UNUSED_FUNCTION: 'YES'
GCC_WARN_UNUSED_VARIABLE: 'YES'
ONLY_ACTIVE_ARCH: 'YES'
SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEBUG
SWIFT_OPTIMIZATION_LEVEL: "-Onone"
CODE_SIGN_IDENTITY[sdk=iphoneos*]: iPhone Developer
CODE_SIGN_IDENTITY: "-"
CLANG_WARN_SUSPICIOUS_MOVE: "YES"

View File

@ -0,0 +1,36 @@
---
ALWAYS_SEARCH_USER_PATHS: 'NO'
CLANG_ANALYZER_NONNULL: 'YES'
CLANG_CXX_LANGUAGE_STANDARD: gnu++0x
CLANG_CXX_LIBRARY: libc++
CLANG_ENABLE_MODULES: 'YES'
CLANG_ENABLE_OBJC_ARC: 'YES'
CLANG_WARN_BOOL_CONVERSION: 'YES'
CLANG_WARN_CONSTANT_CONVERSION: 'YES'
CLANG_WARN_DIRECT_OBJC_ISA_USAGE: YES_ERROR
CLANG_WARN_DOCUMENTATION_COMMENTS: 'YES'
CLANG_WARN_EMPTY_BODY: 'YES'
CLANG_WARN_ENUM_CONVERSION: 'YES'
CLANG_WARN_INFINITE_RECURSION: 'YES'
CLANG_WARN_INT_CONVERSION: 'YES'
CLANG_WARN_OBJC_ROOT_CLASS: YES_ERROR
CLANG_WARN_SUSPICIOUS_MOVES: 'YES'
CLANG_WARN_UNREACHABLE_CODE: 'YES'
CLANG_WARN__DUPLICATE_METHOD_MATCH: 'YES'
COPY_PHASE_STRIP: 'NO'
DEBUG_INFORMATION_FORMAT: dwarf-with-dsym
ENABLE_NS_ASSERTIONS: 'NO'
ENABLE_STRICT_OBJC_MSGSEND: 'YES'
GCC_C_LANGUAGE_STANDARD: gnu99
GCC_NO_COMMON_BLOCKS: 'YES'
GCC_WARN_64_TO_32_BIT_CONVERSION: 'YES'
GCC_WARN_ABOUT_RETURN_TYPE: YES_ERROR
GCC_WARN_UNDECLARED_SELECTOR: 'YES'
GCC_WARN_UNINITIALIZED_AUTOS: YES_AGGRESSIVE
GCC_WARN_UNUSED_FUNCTION: 'YES'
GCC_WARN_UNUSED_VARIABLE: 'YES'
SWIFT_OPTIMIZATION_LEVEL: "-Owholemodule"
VALIDATE_PRODUCT: 'YES'
CODE_SIGN_IDENTITY[sdk=iphoneos*]: iPhone Developer
CODE_SIGN_IDENTITY: "-"
CLANG_WARN_SUSPICIOUS_MOVE: "YES"

View File

@ -0,0 +1,3 @@
---
MTL_ENABLE_DEBUG_INFO: 'YES'
IPHONEOS_DEPLOYMENT_TARGET: '10.2'

View File

@ -0,0 +1,3 @@
---
MTL_ENABLE_DEBUG_INFO: 'NO'
IPHONEOS_DEPLOYMENT_TARGET: '10.2'

View File

@ -0,0 +1,62 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = (
);
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
OBJECT_10 /* .DS_Store in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_3 /* .DS_Store */; };
OBJECT_11 /* Decoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_4 /* Decoding.swift */; };
OBJECT_12 /* Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_5 /* Generator.swift */; };
OBJECT_13 /* Scheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_6 /* Scheme.swift */; };
OBJECT_14 /* Spec.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_7 /* Spec.swift */; };
OBJECT_8 /* .DS_Store in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_1 /* .DS_Store */; };
OBJECT_9 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJECT_2 /* main.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
OBJECT_1 /* .DS_Store */ = {isa = PBXFileReference; path = .DS_Store; sourceTree = "<group>"; };
OBJECT_2 /* main.swift */ = {isa = PBXFileReference; path = main.swift; sourceTree = "<group>"; };
OBJECT_3 /* .DS_Store */ = {isa = PBXFileReference; path = .DS_Store; sourceTree = "<group>"; };
OBJECT_4 /* Decoding.swift */ = {isa = PBXFileReference; path = Decoding.swift; sourceTree = "<group>"; };
OBJECT_5 /* Generator.swift */ = {isa = PBXFileReference; path = Generator.swift; sourceTree = "<group>"; };
OBJECT_6 /* Scheme.swift */ = {isa = PBXFileReference; path = Scheme.swift; sourceTree = "<group>"; };
OBJECT_7 /* Spec.swift */ = {isa = PBXFileReference; path = Spec.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXNativeTarget section */
OBJECT_0 /* Axis iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 234 /* Build configuration list for PBXNativeTarget "Axis iOS" */;
buildPhases = (
OBJECT_15 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = Axis iOS;
};
/* End PBXNativeTarget section */
/* Begin PBXSourcesBuildPhase section */
OBJECT_15 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
OBJECT_13 /* Scheme.swift in Sources */,
OBJECT_8 /* .DS_Store in Sources */,
OBJECT_9 /* main.swift in Sources */,
OBJECT_12 /* Generator.swift in Sources */,
OBJECT_10 /* .DS_Store in Sources */,
OBJECT_11 /* Decoding.swift in Sources */,
OBJECT_14 /* Spec.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
};
rootObject = 12345 /* Project object */;
}

20
test_spec.yml Normal file
View File

@ -0,0 +1,20 @@
settingGroups:
test:
CLANG_ENABLE_MODULES: 'YES'
configs:
Staging Test:
settingGroups:
- debug
- test
settings:
targets:
Axis iOS:
type: application
sources:
- Sources
schemes:
iOS Test:
build:
targets:
Axis iOS:
archive: true