add TargetSource.buildPhase for overriding build phase

This commit is contained in:
Yonas Kolb
2017-12-22 18:57:15 +08:00
parent 835a9f137c
commit cf6df044cf
5 changed files with 147 additions and 8 deletions
+5
View File
@@ -216,6 +216,11 @@ A source can be provided via a string (the path) or an object of the form:
- [ ] **name**: **String** - Can be used to override the name of the source file or directory. By default the last component of the path is used for the name
- [ ] **compilerFlags**: **[String]** or **String** - A list of compilerFlags to add to files under this specific path provided as a list or a space delimitted string. Defaults to empty.
- [ ] **excludes**: **[String]** or **String** - A list of global patterns representing the files to exclude.
- [ ] **buildPhase**: **String** - This manually sets the build phase this file or files in this directory will be added to, otherwise XcodeGen will guess based on the file extension. Note that `Info.plist` files will never be added to any build phases, no matter what this setting is. Possible values are:
- `sources` - Compile Sources phase
- `resources` - Copy Bundle Resources phase
- `headers` - Headers Phase
- `none` - Will not be added to any build phases
- [ ] **type**: **String**: This can be one of the following values
- `file`: a file reference with a parent group will be created (Default for files or directories with extensions)
- `group`: a group with all it's containing files. (Default for directories without extensions)
@@ -4,12 +4,14 @@ public enum SpecParsingError: Error, CustomStringConvertible {
case unknownTargetType(String)
case unknownTargetPlatform(String)
case invalidDependency([String: Any])
case unknownSourceBuildPhase(String)
public var description: String {
switch self {
case let .unknownTargetType(type): return "Unknown Target type: \(type)"
case let .unknownTargetPlatform(platform): return "Unknown Target platform: \(platform)"
case let .invalidDependency(dependency): return "Unknown Target dependency: \(dependency)"
case let .unknownSourceBuildPhase(buildPhase): return "Unknown Source Build Phase: \(buildPhase)"
}
}
}
+28 -1
View File
@@ -1,6 +1,7 @@
import Foundation
import JSONUtilities
import PathKit
import xcproj
public struct TargetSource {
@@ -10,6 +11,23 @@ public struct TargetSource {
public var excludes: [String]
public var type: SourceType?
public var optional: Bool
public var buildPhase: BuildPhase?
public enum BuildPhase: String {
case sources
case headers
case resources
case none
public var buildPhase: xcproj.BuildPhase? {
switch self {
case .sources: return .sources
case .headers: return .headers
case .resources: return .resources
case .none: return nil
}
}
}
public enum SourceType: String {
case group
@@ -17,13 +35,14 @@ public struct TargetSource {
case folder
}
public init(path: String, name: String? = nil, compilerFlags: [String] = [], excludes: [String] = [], type: SourceType? = nil, optional: Bool = false) {
public init(path: String, name: String? = nil, compilerFlags: [String] = [], excludes: [String] = [], type: SourceType? = nil, optional: Bool = false, buildPhase: BuildPhase? = nil) {
self.path = path
self.name = name
self.compilerFlags = compilerFlags
self.excludes = excludes
self.type = type
self.optional = optional
self.buildPhase = buildPhase
}
}
@@ -56,6 +75,13 @@ extension TargetSource: JSONObjectConvertible {
excludes = jsonDictionary.json(atKeyPath: "excludes") ?? []
type = jsonDictionary.json(atKeyPath: "type")
optional = jsonDictionary.json(atKeyPath: "optional") ?? false
if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") {
if let buildPhase = BuildPhase(rawValue: string) {
self.buildPhase = buildPhase
} else {
throw SpecParsingError.unknownSourceBuildPhase(string)
}
}
}
}
@@ -68,5 +94,6 @@ extension TargetSource: Equatable {
&& lhs.excludes == rhs.excludes
&& lhs.type == rhs.type
&& lhs.optional == rhs.optional
&& lhs.buildPhase == rhs.buildPhase
}
}
+11 -3
View File
@@ -44,9 +44,17 @@ class SourceGenerator {
func generateSourceFile(targetSource: TargetSource, path: Path, buildPhase: BuildPhase? = nil) -> SourceFile {
let fileReference = fileReferencesByPath[path]!
var settings: [String: Any] = [:]
let buildPhase = buildPhase ?? getDefaultBuildPhase(for: path)
let chosenBuildPhase: BuildPhase?
if buildPhase == .headers {
if let buildPhase = buildPhase {
chosenBuildPhase = buildPhase
} else if let buildPhase = targetSource.buildPhase {
chosenBuildPhase = buildPhase.buildPhase
} else {
chosenBuildPhase = getDefaultBuildPhase(for: path)
}
if chosenBuildPhase == .headers {
settings = ["ATTRIBUTES": ["Public"]]
}
if targetSource.compilerFlags.count > 0 {
@@ -55,7 +63,7 @@ class SourceGenerator {
// TODO: add the target name to the reference generator string so shared files don't have same reference (that will be escaped by appending a number)
let buildFile = PBXBuildFile(reference: referenceGenerator.generate(PBXBuildFile.self, fileReference + targetName), fileRef: fileReference, settings: settings.isEmpty ? nil : settings)
return SourceFile(path: path, fileReference: fileReference, buildFile: buildFile, buildPhase: buildPhase)
return SourceFile(path: path, fileReference: fileReference, buildFile: buildFile, buildPhase: chosenBuildPhase)
}
func getContainedFileReference(path: Path) -> String {
@@ -571,6 +571,89 @@ func projectGeneratorTests() {
try project.expectFile(paths: ["Sources/A"], names: ["A"], buildPhase: .resources)
try project.expectFileMissing(paths: ["Sources", "A", "a.swift"])
}
$0.it("adds files to correct build phase") {
let directories = """
A:
- file.swift
- file.xcassets
- file.h
- Info.plist
- file.xcconfig
B:
- file.swift
- file.xcassets
- file.h
- Info.plist
- file.xcconfig
C:
- file.swift
- file.m
- file.mm
- file.cpp
- file.c
- file.S
- file.h
- file.hh
- file.hpp
- file.ipp
- file.tpp
- file.hxx
- file.def
- file.xcconfig
- file.entitlements
- file.gpx
- file.apns
- file.123
- file.xcassets
- Info.plist
"""
try createDirectories(directories)
let target = Target(name: "Test", type: .framework, platform: .iOS, sources: [
TargetSource(path: "A", buildPhase: .resources),
TargetSource(path: "B", buildPhase: .none),
TargetSource(path: "C", buildPhase: nil),
])
let spec = ProjectSpec(basePath: directoryPath, name: "Test", targets: [target])
let project = try getPbxProj(spec)
try project.expectFile(paths: ["A", "file.swift"], buildPhase: .resources)
try project.expectFile(paths: ["A", "file.xcassets"], buildPhase: .resources)
try project.expectFile(paths: ["A", "file.h"], buildPhase: .resources)
try project.expectFile(paths: ["A", "Info.plist"], buildPhase: .none)
try project.expectFile(paths: ["A", "file.xcconfig"], buildPhase: .resources)
try project.expectFile(paths: ["B", "file.swift"], buildPhase: .none)
try project.expectFile(paths: ["B", "file.xcassets"], buildPhase: .none)
try project.expectFile(paths: ["B", "file.h"], buildPhase: .none)
try project.expectFile(paths: ["B", "Info.plist"], buildPhase: .none)
try project.expectFile(paths: ["B", "file.xcconfig"], buildPhase: .none)
try project.expectFile(paths: ["C", "file.swift"], buildPhase: .sources)
try project.expectFile(paths: ["C", "file.m"], buildPhase: .sources)
try project.expectFile(paths: ["C", "file.mm"], buildPhase: .sources)
try project.expectFile(paths: ["C", "file.cpp"], buildPhase: .sources)
try project.expectFile(paths: ["C", "file.c"], buildPhase: .sources)
try project.expectFile(paths: ["C", "file.S"], buildPhase: .sources)
try project.expectFile(paths: ["C", "file.h"], buildPhase: .headers)
try project.expectFile(paths: ["C", "file.hh"], buildPhase: .headers)
try project.expectFile(paths: ["C", "file.hpp"], buildPhase: .headers)
try project.expectFile(paths: ["C", "file.ipp"], buildPhase: .headers)
try project.expectFile(paths: ["C", "file.tpp"], buildPhase: .headers)
try project.expectFile(paths: ["C", "file.hxx"], buildPhase: .headers)
try project.expectFile(paths: ["C", "file.def"], buildPhase: .headers)
try project.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none)
try project.expectFile(paths: ["C", "file.entitlements"], buildPhase: .none)
try project.expectFile(paths: ["C", "file.gpx"], buildPhase: .none)
try project.expectFile(paths: ["C", "file.apns"], buildPhase: .none)
try project.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none)
try project.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none)
try project.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none)
try project.expectFile(paths: ["C", "file.xcassets"], buildPhase: .resources)
try project.expectFile(paths: ["C", "file.123"], buildPhase: .resources)
try project.expectFile(paths: ["C", "Info.plist"], buildPhase: .none)
}
}
}
}
@@ -600,7 +683,7 @@ extension PBXProj {
extension PBXProj {
/// expect a file within groups of the paths, using optional different names
func expectFile(paths: [String], names: [String]? = nil, buildPhase: BuildPhase? = nil) throws {
func expectFile(paths: [String], names: [String]? = nil, buildPhase: TargetSource.BuildPhase? = nil) throws {
guard let fileReference = getFileReference(paths: paths, names: names ?? paths) else {
var error = "Could not find file at path \(paths.joined(separator: "/").quoted)"
if let names = names, names != paths {
@@ -610,9 +693,23 @@ extension PBXProj {
}
if let buildPhase = buildPhase {
guard let buildFile = objects.buildFiles.referenceValues.first(where: { $0.fileRef == fileReference.reference }),
objects.buildPhases.referenceValues.contains(where: { $0.files.contains(buildFile.reference) }) else {
throw failure("File \(paths.joined(separator: "/").quoted) is not in a \(buildPhase.rawValue.quoted) build phase")
let buildFile = objects.buildFiles.referenceValues.first(where: { $0.fileRef == fileReference.reference })
let actualBuildPhase = buildFile.flatMap { buildFile in objects.buildPhases.referenceValues.first { $0.files.contains(buildFile.reference) } }?.buildPhase
var error: String?
if let buildPhase = buildPhase.buildPhase {
if actualBuildPhase != buildPhase {
if let actualBuildPhase = actualBuildPhase {
error = "is in the \(actualBuildPhase.rawValue) build phase instead of the expected \(buildPhase.rawValue.quoted)"
} else {
error = "isn't in a build phase when it's expected to be in \(buildPhase.rawValue.quoted)"
}
}
} else if let actualBuildPhase = actualBuildPhase {
error = "is in the \(actualBuildPhase.rawValue.quoted) build phase when it's expected to not be in any"
}
if let error = error {
throw failure("File \(paths.joined(separator: "/").quoted) \(error)")
}
}
}