mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2026-03-18 20:02:25 +00:00
Merge pull request #206 from yonaskolb/override_build_phase
Add TargetSource.buildPhase for overriding build phase
This commit is contained in:
@@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user