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

Introduce --include-existing option #8

Merged
merged 18 commits into from
May 1, 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## master
* Introduce `--include-existing` (`--no-include-existing`) option
[Toshihiro Suzuki](https://github.com/toshi0383)
[#8](https://github.com/toshi0383/xcconfig-extractor/pull/8)

## 0.2.0

* Rename `trim-duplicates` to `--no-trim-duplicates`
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,28 @@ Make sure you setup each configurations correctly on Xcode.
Options:
--no-trim-duplicates [default: false] - Don't extract duplicated lines to common xcconfig files, simply map each buildSettings to one file.
--no-edit-pbxproj [default: false] - Do not modify pbxproj.
--include-existing [default: true] - `#include` already configured xcconfigs.
```

# Build Setting Validation
⚠️***Waring***⚠️

You should check app's Build Settings hasn't been affected by applying this tool.
xcconfig does not allow any `$(inherited)` from `#include`ing xcconfigs. (See: https://github.com/toshi0383/xcconfig-extractor/pull/8#issuecomment-298234943) So if you have any existing xcconfig configured on your project, it might cause problems.

Recommended way to check Build Settings is to run command like below. Make sure outputs does not change between before and after.
```bash
$ xcodebuild -showBuildSettings -configuration Release > before
$ # apply xcconfig-extractor
$ xcodebuild -showBuildSettings -configuration Release > after
$ diff before after # should prints nothing!
```

If output changed, you should manually fix it. (e.g. by adding missing variable to target's(top level) xcconfig.)
[This article](https://pewpewthespells.com/blog/xcconfig_guide.html#BuildSettingInheritance) is helpful to understand how inheritance works.

# TODOs
- Add Tests
- Add More Tests

# License
MIT
30 changes: 20 additions & 10 deletions Sources/PBXProj/BuildConfigurationList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,39 @@
import Foundation

public struct BuildConfigurationList: IsaObject {
public let object: [String: Any]
public let key: String
public let rawObject: [String: Any]
public let buildConfigurations: [BuildConfiguration]
public let defaultConfigurationName: String?
public let defaultConfigurationIsVisible: String
public init(_ o: [String: Any], objects: [String: Any]) {
self.object = o
public init?(key: String, value o: [String: Any], objects: [String: Any]) {
guard IsaType(object: o) == .XCConfigurationList else {
return nil
}
self.key = key
self.rawObject = o
self.defaultConfigurationName = o["defaultConfigurationName"] as? String
let buildConfigurationKeys = o["buildConfigurations"] as! [String]
self.buildConfigurations = buildConfigurationKeys.map { key in objects[key] as! [String: Any] }
.map(BuildConfiguration.init)
self.buildConfigurations = buildConfigurationKeys.map { key in (key, objects) }
.flatMap(BuildConfiguration.init)
self.defaultConfigurationIsVisible = o["defaultConfigurationIsVisible"] as! String
}
}

public struct BuildConfiguration: IsaObject {
public let object: [String: Any]
public let key: String
public let rawObject: [String: Any]
public let name: String
public let baseConfigurationReference: String?
public let baseConfigurationReference: FileReference?
public let buildSettings: [String: Any]
public init(object o: [String: Any]) {
self.object = o
public init?(key: String, value o: [String: Any], objects: [String: Any]) {
guard IsaType(object: o) == .XCBuildConfiguration else {
return nil
}
self.key = key
self.rawObject = o
self.name = o["name"] as! String
self.baseConfigurationReference = o["baseConfigurationReference"] as? String
self.baseConfigurationReference = FileReference(key: o["baseConfigurationReference"], objects: objects)
self.buildSettings = o["buildSettings"] as! [String: Any]
}
}
37 changes: 37 additions & 0 deletions Sources/PBXProj/FileReference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// FileReference.swift
// xcconfig-extractor
//
// Created by Toshihiro Suzuki on 2017/04/30.
// Copyright © 2017 Toshihiro Suzuki. All rights reserved.
//

import Foundation

public enum FileType: String {
case xcconfig = "text.xcconfig"
}

public struct FileReference: IsaObject {
public let key: String
public let rawObject: [String : Any]
public let lastKnownFileType: FileType
public let path: String
public let sourceTree: String

// custom property
public let fullPath: String

public init?(key: String, value o: [String : Any], objects: [String : Any]) {
guard IsaType(object: o) == .PBXFileReference else {
return nil
}
self.key = key
self.rawObject = o
self.lastKnownFileType = FileType(rawValue: o["lastKnownFileType"] as! String)!
self.path = o["path"] as! String
let fullPath = findPaths(to: key, objects: objects) + [self.path]
self.fullPath = fullPath.joined(separator: "/")
self.sourceTree = o["sourceTree"] as! String
}
}
24 changes: 24 additions & 0 deletions Sources/PBXProj/Functions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Functions.swift
// xcconfig-extractor
//
// Created by Toshihiro Suzuki on 2017/04/30.
// Copyright © 2017 Toshihiro Suzuki. All rights reserved.
//

import Foundation

func findPaths(to id: String, objects: [String: Any]) -> [String] {
for (k, v) in objects {
if let o = v as? [String: Any], let group = Group(key: k, value: o, objects: objects) {
if group.children.contains(id) {
if let path = group.path {
return findPaths(to: k, objects: objects) + [path]
} else {
return findPaths(to: k, objects: objects)
}
}
}
}
return []
}
27 changes: 27 additions & 0 deletions Sources/PBXProj/Group.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Group.swift
// xcconfig-extractor
//
// Created by Toshihiro Suzuki on 2017/04/30.
// Copyright © 2017 Toshihiro Suzuki. All rights reserved.
//

import Foundation

public struct Group: IsaObject {
public let rawObject: [String : Any]
public let key: String
public let children: [String]
public let path: String?
public let name: String?
public init?(key: String, value o: [String : Any], objects: [String : Any]) {
guard IsaType(object: o) == .PBXGroup else {
return nil
}
self.key = key
self.rawObject = o
self.children = o["children"] as! [String]
self.path = o["path"] as? String
self.name = o["name"] as? String
}
}
26 changes: 24 additions & 2 deletions Sources/PBXProj/IsaObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,37 @@ public enum IsaType: String {
case PBXGroup
case PBXFrameworksBuildPhase
case PBXBuildRule
case PBXBuildFile
case PBXFileReference
case PBXContainerItemProxy
case PBXVariantGroup
case PBXTargetDependency
case PBXCopyFilesBuildPhase
case XCVersionGroup
init(object: [String: Any]) {
self.init(rawValue: object["isa"] as! String)!
}
}

public protocol IsaObject {
var key: String { get }
var isa: IsaType { get }
var object: [String: Any] { get }
var rawObject: [String: Any] { get }
init?(key: String, value: [String: Any], objects: [String: Any])
init?(key: Any?, objects: [String: Any])
}

extension IsaObject {
public var isa: IsaType {
return IsaType(rawValue: object["isa"] as! String)!
return IsaType(rawValue: rawObject["isa"] as! String)!
}
public init?(key: Any?, objects: [String: Any]) {
guard let key = key as? String else {
return nil
}
guard let o = objects[key] as? [String: Any] else {
return nil
}
self.init(key: key, value: o, objects: objects)
}
}
13 changes: 9 additions & 4 deletions Sources/PBXProj/NativeTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public enum ProductType: String {
}

public struct NativeTarget: IsaObject {
public let object: [String: Any]
public let key: String
public let rawObject: [String: Any]

public let name: String
public let productName: String
Expand All @@ -27,8 +28,12 @@ public struct NativeTarget: IsaObject {
public let dependencies: [Any] // TODO
public let buildPhases: [String] // TODO
public let buildConfigurationList: BuildConfigurationList
init(target o: [String: Any], objects: [String: Any]) {
self.object = o
public init?(key: String, value o: [String: Any], objects: [String: Any]) {
guard IsaType(object: o) == .PBXNativeTarget else {
return nil
}
self.key = key
self.rawObject = o
self.name = o["name"] as! String
self.productName = o["productName"] as! String
self.productType = ProductType(rawValue: o["productType"] as! String)!
Expand All @@ -37,6 +42,6 @@ public struct NativeTarget: IsaObject {
self.dependencies = o["dependencies"] as! [Any]
self.buildPhases = o["buildPhases"] as! [String]
let buildConfigurationListKey = o["buildConfigurationList"] as! String
self.buildConfigurationList = BuildConfigurationList(objects[buildConfigurationListKey] as! [String: Any], objects: objects)
self.buildConfigurationList = BuildConfigurationList(key: buildConfigurationListKey, objects: objects)!
}
}
2 changes: 1 addition & 1 deletion Sources/PBXProj/Pbxproj.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public struct Pbxproj {
fatalError("rootObject or objects not found!")
}
self.objects = objects
let rootObject = Project(objects[rootObjectKey] as! [String: Any], objects: objects)
let rootObject = Project(key: rootObjectKey, objects: objects)!
self.rootObject = rootObject
}
func object<T>(for key: String) -> T {
Expand Down
16 changes: 10 additions & 6 deletions Sources/PBXProj/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import Foundation

public struct Project: IsaObject {
public let object: [String: Any]
public let key: String
public let rawObject: [String: Any]
public let attributes: [String: Any]
public let buildConfigurationList: BuildConfigurationList
public let compatibilityVersion: String
Expand All @@ -21,11 +22,15 @@ public struct Project: IsaObject {
public let projectDirPath: String
public let projectRoot: String?
public let targets: [NativeTarget]
public init(_ o: [String: Any], objects: [String: Any]) {
self.object = o
public init?(key: String, value o: [String: Any], objects: [String: Any]) {
guard IsaType(object: o) == .PBXProject else {
return nil
}
self.key = key
self.rawObject = o
self.attributes = o["attributes"] as! [String: Any]
let buildConfigurationListKey = o["buildConfigurationList"] as! String
self.buildConfigurationList = BuildConfigurationList(objects[buildConfigurationListKey] as! [String: Any], objects: objects)
self.buildConfigurationList = BuildConfigurationList(key: buildConfigurationListKey, objects: objects)!
self.compatibilityVersion = o["compatibilityVersion"] as! String
self.developmentRegion = o["developmentRegion"] as! String
self.hasScannedForEncodings = o["hasScannedForEncodings"] as! String
Expand All @@ -35,8 +40,7 @@ public struct Project: IsaObject {
self.projectDirPath = o["projectDirPath"] as! String
self.projectRoot = o["projectRoot"] as? String
self.targets = (o["targets"] as! [String]).map { k in
let target = objects[k] as! [String: Any]
return NativeTarget(target: target, objects: objects)
return NativeTarget(key: k, objects: objects)!
}
}
}
17 changes: 17 additions & 0 deletions Sources/xcconfig-extractor/Config.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Config.swift
// xcconfig-extractor
//
// Created by Toshihiro Suzuki on 2017/04/30.
// Copyright © 2017 Toshihiro Suzuki. All rights reserved.
//

import Foundation

struct Config {
static let version = "0.2.0"
let isIncludeExisting: Bool
init(isIncludeExisting: Bool) {
self.isIncludeExisting = isIncludeExisting
}
}
30 changes: 30 additions & 0 deletions Sources/xcconfig-extractor/ErrorReporter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// ErrorReporter.swift
// xcconfig-extractor
//
// Created by Toshihiro Suzuki on 2017/04/30.
// Copyright © 2017 Toshihiro Suzuki. All rights reserved.
//

import Foundation

func printStdError(_ message: String) {
fputs("\(ANSI.red)\(message)\(ANSI.reset)", stderr)
}

private enum ANSI : String, CustomStringConvertible {
case red = "\u{001B}[0;31m"
case green = "\u{001B}[0;32m"
case yellow = "\u{001B}[0;33m"

case bold = "\u{001B}[0;1m"
case reset = "\u{001B}[0;0m"

var description:String {
if isatty(STDOUT_FILENO) > 0 {
return rawValue
}
return ""
}
}

30 changes: 30 additions & 0 deletions Sources/xcconfig-extractor/ResultFormatter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// ResultFormatter.swift
// xcconfig-extractor
//
// Created by Toshihiro Suzuki on 2017/04/30.
// Copyright © 2017 Toshihiro Suzuki. All rights reserved.
//

import Foundation

class ResultFormatter {
let config: Config
init(config: Config) {
self.config = config
}
private var header: [String] {
let signature = "// Generated using xcconfig-extractor \(Config.version) by Toshihiro Suzuki - https://github.com/toshi0383/xcconfig-extractor"
return [signature]
}
private func addInclude(filePath: String) -> String {
return "#include \"\(filePath)\""
}
func format(result: ResultObject, includes: [String] = []) -> [String] {
return header
+ result.includes.map(addInclude)
+ includes.map(addInclude)
+ result.settings
+ ["\n"]
}
}
Loading