Add FileTypes for cross project file options (#914)

* move BuildPhase

* add fileTypes

* update changelog
This commit is contained in:
Yonas Kolb authored and GitHub committed 2020-08-04 09:14:32 +10:00
1 parent 25a539a236
commit 20a99e5018
15 files changed
+448 -222

No files matched your search

+2 -1
View File
@@ -3,7 +3,8 @@
## Next Version
#### Added
- Add `onlyCopyFilesOnInstall` option to targets for the Embed Files build phase. [#912](https://github.com/yonaskolb/XcodeGen/pull/912) @jsorge
- Added `options.fileTypes` which lets you set cross project defaults for certain file extensions [#914](https://github.com/yonaskolb/XcodeGen/pull/914) @yonaskolb
- Added `onlyCopyFilesOnInstall` option to targets for the Embed Files build phase. [#912](https://github.com/yonaskolb/XcodeGen/pull/912) @jsorge
#### Fixed
- Treat all directories with known UTI as file wrapper. [#896](https://github.com/yonaskolb/XcodeGen/pull/896) @KhaosT
+10
View File
@@ -123,6 +123,7 @@ Note that target names can also be changed by adding a `name` property to a targ
- [ ] **generateEmptyDirectories**: **Bool** - If this is `true` then empty directories will be added to project too else will be missed. Defaults to `false`.
- [ ] **findCarthageFrameworks**: **Bool** - When this is set to `true`, all the invididual frameworks for Carthage dependencies will automatically be found. This property can be overriden individually for each carthage dependency - for more details see See **findFrameworks** in the [Dependency](#dependency) section. Defaults to `false`.
- [ ] **localPackagesGroup**: **String** - The group name that local packages are put into. This defaults to `Packages`
- [ ] **fileTypes**: **[String: [FileType](#filetype)]** - A list of default file options for specific file extensions across the project. Values in [Sources](#sources) will overwrite these settings.
- [ ] **preGenCommand**: **String** - A bash command to run before the project has been generated. If the project isn't generated due to no changes when using the cache then this won't run. This is useful for running things like generating resources files before the project is regenerated.
- [ ] **postGenCommand**: **String** - A bash command to run after the project has been generated. If the project isn't generated due to no changes when using the cache then this won't run. This is useful for running things like `pod install` only if the project is actually regenerated.
@@ -151,6 +152,15 @@ options:
In this example, we set up the order of two groups. First one is the main group, i.e. the project, note that in this case, we shouldn't set `pattern` option and the second group order is for groups whose names ends with `Screen`.
### FileType
Default settings for file extensions. See [Sources](#sources) for more documentation on properties. If you overwrite an extension that XcodeGen already provides by default, you will need to provide all the settings.
- [ ] **file**: **Bool** - Whether this extension should be treated like a file. Defaults to true.
- [ ] **buildPhase**: **String** - The default build phase.
- [ ] **attributes**: **[String]** - Additional settings attributes that will be applied to any build files.
- [ ] **resourceTags**: **[String]** - On Demand Resource Tags that will be applied to any resources. This also adds to the project attribute's knownAssetTags.
- [ ] **compilerFlags**: **[String]** - A list of compiler flags to add.
### Configs
Each config maps to a build type of either `debug` or `release` which will then apply default build settings to the project. Any value other than `debug` or `release` (for example `none`), will mean no default build settings will be applied to the project.
+148
View File
@@ -0,0 +1,148 @@
//
// File.swift
//
//
// Created by Yonas Kolb on 1/5/20.
//
import Foundation
import XcodeProj
import JSONUtilities
public enum BuildPhaseSpec: Equatable {
case sources
case headers
case resources
case copyFiles(CopyFilesSettings)
case none
// Not currently exposed as selectable options, but used internally
case frameworks
case runScript
case carbonResources
public struct CopyFilesSettings: Equatable, Hashable {
public static let xpcServices = CopyFilesSettings(
destination: .productsDirectory,
subpath: "$(CONTENTS_FOLDER_PATH)/XPCServices",
phaseOrder: .postCompile
)
public enum Destination: String {
case absolutePath
case productsDirectory
case wrapper
case executables
case resources
case javaResources
case frameworks
case sharedFrameworks
case sharedSupport
case plugins
public var destination: PBXCopyFilesBuildPhase.SubFolder? {
switch self {
case .absolutePath: return .absolutePath
case .productsDirectory: return .productsDirectory
case .wrapper: return .wrapper
case .executables: return .executables
case .resources: return .resources
case .javaResources: return .javaResources
case .frameworks: return .frameworks
case .sharedFrameworks: return .sharedFrameworks
case .sharedSupport: return .sharedSupport
case .plugins: return .plugins
}
}
}
public enum PhaseOrder: String {
/// Run before the Compile Sources phase
case preCompile
/// Run after the Compile Sources and post-compile Run Script phases
case postCompile
}
public var destination: Destination
public var subpath: String
public var phaseOrder: PhaseOrder
public init(
destination: Destination,
subpath: String,
phaseOrder: PhaseOrder
) {
self.destination = destination
self.subpath = subpath
self.phaseOrder = phaseOrder
}
}
public var buildPhase: BuildPhase? {
switch self {
case .sources: return .sources
case .headers: return .headers
case .resources: return .resources
case .copyFiles: return .copyFiles
case .frameworks: return .frameworks
case .runScript: return .runScript
case .carbonResources: return .carbonResources
case .none: return nil
}
}
}
extension BuildPhaseSpec {
public init(string: String) throws {
switch string {
case "sources": self = .sources
case "headers": self = .headers
case "resources": self = .resources
case "copyFiles":
throw SpecParsingError.invalidSourceBuildPhase("copyFiles must specify a \"destination\" and optional \"subpath\"")
case "none": self = .none
default:
throw SpecParsingError.invalidSourceBuildPhase(string.quoted)
}
}
}
extension BuildPhaseSpec: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
self = .copyFiles(try jsonDictionary.json(atKeyPath: "copyFiles"))
}
}
extension BuildPhaseSpec: JSONEncodable {
public func toJSONValue() -> Any {
switch self {
case .sources: return "sources"
case .headers: return "headers"
case .resources: return "resources"
case .copyFiles(let files): return ["copyFiles": files.toJSONValue()]
case .none: return "none"
case .frameworks: fatalError("invalid build phase")
case .runScript: fatalError("invalid build phase")
case .carbonResources: fatalError("invalid build phase")
}
}
}
extension BuildPhaseSpec.CopyFilesSettings: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
destination = try jsonDictionary.json(atKeyPath: "destination")
subpath = jsonDictionary.json(atKeyPath: "subpath") ?? ""
phaseOrder = .postCompile
}
}
extension BuildPhaseSpec.CopyFilesSettings: JSONEncodable {
public func toJSONValue() -> Any {
[
"destination": destination.rawValue,
"subpath": subpath,
]
}
}
+114
View File
@@ -0,0 +1,114 @@
//
// File.swift
//
//
// Created by Yonas Kolb on 1/5/20.
//
import Foundation
import JSONUtilities
import enum XcodeProj.BuildPhase
public struct FileType: Equatable {
public enum Defaults {
public static let file = true
}
public var file: Bool
public var buildPhase: BuildPhaseSpec?
public var attributes: [String]
public var resourceTags: [String]
public var compilerFlags: [String]
public init(
file: Bool = Defaults.file,
buildPhase: BuildPhaseSpec? = nil,
attributes: [String] = [],
resourceTags: [String] = [],
compilerFlags: [String] = []
) {
self.file = file
self.buildPhase = buildPhase
self.attributes = attributes
self.resourceTags = resourceTags
self.compilerFlags = compilerFlags
}
}
extension FileType: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") {
buildPhase = try BuildPhaseSpec(string: string)
} else if let dict: JSONDictionary = jsonDictionary.json(atKeyPath: "buildPhase") {
buildPhase = try BuildPhaseSpec(jsonDictionary: dict)
}
file = jsonDictionary.json(atKeyPath: "file") ?? Defaults.file
attributes = jsonDictionary.json(atKeyPath: "attributes") ?? []
resourceTags = jsonDictionary.json(atKeyPath: "resourceTags") ?? []
compilerFlags = jsonDictionary.json(atKeyPath: "compilerFlags") ?? []
}
}
extension FileType: JSONEncodable {
public func toJSONValue() -> Any {
var dict: [String: Any?] = [
"buildPhase": buildPhase?.toJSONValue(),
"attributes": attributes,
"resourceTags": resourceTags,
"compilerFlags": compilerFlags,
]
if file != Defaults.file {
dict["file"] = file
}
return dict
}
}
extension FileType {
public static let defaultFileTypes: [String: FileType] = [
// resources
"bundle": FileType(buildPhase: .resources),
"xcassets": FileType(buildPhase: .resources),
// sources
"swift": FileType(buildPhase: .sources),
"m": FileType(buildPhase: .sources),
"mm": FileType(buildPhase: .sources),
"cpp": FileType(buildPhase: .sources),
"c": FileType(buildPhase: .sources),
"cc": FileType(buildPhase: .sources),
"S": FileType(buildPhase: .sources),
"xcdatamodeld": FileType(buildPhase: .sources),
"xcmappingmodel": FileType(buildPhase: .sources),
"intentdefinition": FileType(buildPhase: .sources),
"metal": FileType(buildPhase: .sources),
"mlmodel": FileType(buildPhase: .sources),
"rcproject": FileType(buildPhase: .sources),
// headers
"h": FileType(buildPhase: .headers),
"hh": FileType(buildPhase: .headers),
"hpp": FileType(buildPhase: .headers),
"ipp": FileType(buildPhase: .headers),
"tpp": FileType(buildPhase: .headers),
"hxx": FileType(buildPhase: .headers),
"def": FileType(buildPhase: .headers),
// frameworks
"framework": FileType(buildPhase: .frameworks),
// copyfiles
"xpc": FileType(buildPhase: .copyFiles(.xpcServices)),
// no build phase (not resources)
"xcconfig": FileType(buildPhase: BuildPhaseSpec.none),
"entitlements": FileType(buildPhase: BuildPhaseSpec.none),
"gpx": FileType(buildPhase: BuildPhaseSpec.none),
"lproj": FileType(buildPhase: BuildPhaseSpec.none),
"xcfilelist": FileType(buildPhase: BuildPhaseSpec.none),
"apns": FileType(buildPhase: BuildPhaseSpec.none),
"pch": FileType(buildPhase: BuildPhaseSpec.none),
]
}
+14
View File
@@ -0,0 +1,14 @@
//
// File.swift
//
//
// Created by Yonas Kolb on 1/5/20.
//
import Foundation
public enum SourceType: String {
case group
case file
case folder
}
+9
View File
@@ -27,6 +27,7 @@ public struct SpecOptions: Equatable {
public var transitivelyLinkDependencies: Bool
public var groupSortPosition: GroupSortPosition
public var groupOrdering: [GroupOrdering]
public var fileTypes: [String: FileType]
public var generateEmptyDirectories: Bool
public var findCarthageFrameworks: Bool
public var localPackagesGroup: String?
@@ -87,6 +88,7 @@ public struct SpecOptions: Equatable {
transitivelyLinkDependencies: Bool = transitivelyLinkDependenciesDefault,
groupSortPosition: GroupSortPosition = groupSortPositionDefault,
groupOrdering: [GroupOrdering] = [],
fileTypes: [String: FileType] = [:],
generateEmptyDirectories: Bool = generateEmptyDirectoriesDefault,
findCarthageFrameworks: Bool = findCarthageFrameworksDefault,
localPackagesGroup: String? = nil,
@@ -110,6 +112,7 @@ public struct SpecOptions: Equatable {
self.transitivelyLinkDependencies = transitivelyLinkDependencies
self.groupSortPosition = groupSortPosition
self.groupOrdering = groupOrdering
self.fileTypes = fileTypes
self.generateEmptyDirectories = generateEmptyDirectories
self.findCarthageFrameworks = findCarthageFrameworks
self.localPackagesGroup = localPackagesGroup
@@ -146,6 +149,11 @@ extension SpecOptions: JSONObjectConvertible {
localPackagesGroup = jsonDictionary.json(atKeyPath: "localPackagesGroup")
preGenCommand = jsonDictionary.json(atKeyPath: "preGenCommand")
postGenCommand = jsonDictionary.json(atKeyPath: "postGenCommand")
if jsonDictionary["fileTypes"] != nil {
fileTypes = try jsonDictionary.json(atKeyPath: "fileTypes")
} else {
fileTypes = [:]
}
}
}
@@ -169,6 +177,7 @@ extension SpecOptions: JSONEncodable {
"localPackagesGroup": localPackagesGroup,
"preGenCommand": preGenCommand,
"postGenCommand": postGenCommand,
"fileTypes": fileTypes
]
if settingPresets != SpecOptions.settingPresetsDefault {
+4 -150
View File
@@ -1,8 +1,6 @@
import Foundation
import JSONUtilities
import PathKit
import enum XcodeProj.BuildPhase
import class XcodeProj.PBXCopyFilesBuildPhase
public struct TargetSource: Equatable {
public static let optionalDefault = false
@@ -15,7 +13,7 @@ public struct TargetSource: Equatable {
public var includes: [String]
public var type: SourceType?
public var optional: Bool
public var buildPhase: BuildPhase?
public var buildPhase: BuildPhaseSpec?
public var headerVisibility: HeaderVisibility?
public var createIntermediateGroups: Bool?
public var attributes: [String]
@@ -35,94 +33,6 @@ public struct TargetSource: Equatable {
}
}
public enum BuildPhase: Equatable {
case sources
case headers
case resources
case copyFiles(CopyFilesSettings)
case none
// Not currently exposed as selectable options, but used internally
case frameworks
case runScript
case carbonResources
public struct CopyFilesSettings: Equatable, Hashable {
public static let xpcServices = CopyFilesSettings(
destination: .productsDirectory,
subpath: "$(CONTENTS_FOLDER_PATH)/XPCServices",
phaseOrder: .postCompile
)
public enum Destination: String {
case absolutePath
case productsDirectory
case wrapper
case executables
case resources
case javaResources
case frameworks
case sharedFrameworks
case sharedSupport
case plugins
public var destination: PBXCopyFilesBuildPhase.SubFolder? {
switch self {
case .absolutePath: return .absolutePath
case .productsDirectory: return .productsDirectory
case .wrapper: return .wrapper
case .executables: return .executables
case .resources: return .resources
case .javaResources: return .javaResources
case .frameworks: return .frameworks
case .sharedFrameworks: return .sharedFrameworks
case .sharedSupport: return .sharedSupport
case .plugins: return .plugins
}
}
}
public enum PhaseOrder: String {
/// Run before the Compile Sources phase
case preCompile
/// Run after the Compile Sources and post-compile Run Script phases
case postCompile
}
public var destination: Destination
public var subpath: String
public var phaseOrder: PhaseOrder
public init(
destination: Destination,
subpath: String,
phaseOrder: PhaseOrder
) {
self.destination = destination
self.subpath = subpath
self.phaseOrder = phaseOrder
}
}
public var buildPhase: XcodeProj.BuildPhase? {
switch self {
case .sources: return .sources
case .headers: return .headers
case .resources: return .resources
case .copyFiles: return .copyFiles
case .frameworks: return .frameworks
case .runScript: return .runScript
case .carbonResources: return .carbonResources
case .none: return nil
}
}
}
public enum SourceType: String {
case group
case file
case folder
}
public init(
path: String,
name: String? = nil,
@@ -132,7 +42,7 @@ public struct TargetSource: Equatable {
includes: [String] = [],
type: SourceType? = nil,
optional: Bool = optionalDefault,
buildPhase: BuildPhase? = nil,
buildPhase: BuildPhaseSpec? = nil,
headerVisibility: HeaderVisibility? = nil,
createIntermediateGroups: Bool? = nil,
attributes: [String] = [],
@@ -188,9 +98,9 @@ extension TargetSource: JSONObjectConvertible {
optional = jsonDictionary.json(atKeyPath: "optional") ?? TargetSource.optionalDefault
if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") {
buildPhase = try BuildPhase(string: string)
buildPhase = try BuildPhaseSpec(string: string)
} else if let dict: JSONDictionary = jsonDictionary.json(atKeyPath: "buildPhase") {
buildPhase = try BuildPhase(jsonDictionary: dict)
buildPhase = try BuildPhaseSpec(jsonDictionary: dict)
}
createIntermediateGroups = jsonDictionary.json(atKeyPath: "createIntermediateGroups")
@@ -228,62 +138,6 @@ extension TargetSource: JSONEncodable {
}
}
extension TargetSource.BuildPhase {
public init(string: String) throws {
switch string {
case "sources": self = .sources
case "headers": self = .headers
case "resources": self = .resources
case "copyFiles":
throw SpecParsingError.invalidSourceBuildPhase("copyFiles must specify a \"destination\" and optional \"subpath\"")
case "none": self = .none
default:
throw SpecParsingError.invalidSourceBuildPhase(string.quoted)
}
}
}
extension TargetSource.BuildPhase: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
self = .copyFiles(try jsonDictionary.json(atKeyPath: "copyFiles"))
}
}
extension TargetSource.BuildPhase: JSONEncodable {
public func toJSONValue() -> Any {
switch self {
case .sources: return "sources"
case .headers: return "headers"
case .resources: return "resources"
case .copyFiles(let files): return ["copyFiles": files.toJSONValue()]
case .none: return "none"
case .frameworks: fatalError("invalid build phase")
case .runScript: fatalError("invalid build phase")
case .carbonResources: fatalError("invalid build phase")
}
}
}
extension TargetSource.BuildPhase.CopyFilesSettings: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
destination = try jsonDictionary.json(atKeyPath: "destination")
subpath = jsonDictionary.json(atKeyPath: "subpath") ?? ""
phaseOrder = .postCompile
}
}
extension TargetSource.BuildPhase.CopyFilesSettings: JSONEncodable {
public func toJSONValue() -> Any {
[
"destination": destination.rawValue,
"subpath": subpath,
]
}
}
extension TargetSource: PathContainer {
static var pathProperties: [PathProperty] {
+4 -4
View File
@@ -476,7 +476,7 @@ public class PBXProjGenerator {
return addObject(shellScriptPhase)
}
func generateCopyFiles(targetName: String, copyFiles: TargetSource.BuildPhase.CopyFilesSettings, buildPhaseFiles: [PBXBuildFile]) -> PBXCopyFilesBuildPhase {
func generateCopyFiles(targetName: String, copyFiles: BuildPhaseSpec.CopyFilesSettings, buildPhaseFiles: [PBXBuildFile]) -> PBXCopyFilesBuildPhase {
let copyFilesBuildPhase = PBXCopyFilesBuildPhase(
dstPath: copyFiles.subpath,
dstSubfolderSpec: copyFiles.destination.destination,
@@ -644,7 +644,7 @@ public class PBXProjGenerator {
var dependencies: [PBXTargetDependency] = []
var targetFrameworkBuildFiles: [PBXBuildFile] = []
var frameworkBuildPaths = Set<String>()
var copyFilesBuildPhasesFiles: [TargetSource.BuildPhase.CopyFilesSettings: [PBXBuildFile]] = [:]
var copyFilesBuildPhasesFiles: [BuildPhaseSpec.CopyFilesSettings: [PBXBuildFile]] = [:]
var copyFrameworksReferences: [PBXBuildFile] = []
var copyResourcesReferences: [PBXBuildFile] = []
var copyBundlesReferences: [PBXBuildFile] = []
@@ -955,8 +955,8 @@ public class PBXProjGenerator {
return getBuildFilesForSourceFiles(filteredSourceFiles)
}
func getBuildFilesForCopyFilesPhases() -> [TargetSource.BuildPhase.CopyFilesSettings: [PBXBuildFile]] {
var sourceFilesByCopyFiles: [TargetSource.BuildPhase.CopyFilesSettings: [SourceFile]] = [:]
func getBuildFilesForCopyFilesPhases() -> [BuildPhaseSpec.CopyFilesSettings: [PBXBuildFile]] {
var sourceFilesByCopyFiles: [BuildPhaseSpec.CopyFilesSettings: [SourceFile]] = [:]
for sourceFile in sourceFiles {
guard case let .copyFiles(copyFilesSettings)? = sourceFile.buildPhase else { continue }
sourceFilesByCopyFiles[copyFilesSettings, default: []].append(sourceFile)
+55 -52
View File
@@ -8,7 +8,7 @@ struct SourceFile {
let path: Path
let fileReference: PBXFileElement
let buildFile: PBXBuildFile
let buildPhase: TargetSource.BuildPhase?
let buildPhase: BuildPhaseSpec?
}
class SourceGenerator {
@@ -87,11 +87,22 @@ class SourceGenerator {
_ = try getSourceFiles(targetType: .none, targetSource: TargetSource(path: path), path: fullPath)
}
func generateSourceFile(targetType: PBXProductType, targetSource: TargetSource, path: Path, buildPhase: TargetSource.BuildPhase? = nil, fileReference: PBXFileElement? = nil) -> SourceFile {
func getFileType(path: Path) -> FileType? {
if let fileExtension = path.extension {
return project.options.fileTypes[fileExtension] ?? FileType.defaultFileTypes[fileExtension]
} else {
return nil
}
}
func generateSourceFile(targetType: PBXProductType, targetSource: TargetSource, path: Path, buildPhase: BuildPhaseSpec? = nil, fileReference: PBXFileElement? = nil) -> SourceFile {
let fileReference = fileReference ?? fileReferencesByPath[path.string.lowercased()]!
var settings: [String: Any] = [:]
var attributes: [String] = targetSource.attributes
var chosenBuildPhase: TargetSource.BuildPhase?
let fileType = getFileType(path: path)
var attributes: [String] = targetSource.attributes + (fileType?.attributes ?? [])
var chosenBuildPhase: BuildPhaseSpec?
var compilerFlags: String = ""
let assetTags: [String] = targetSource.resourceTags + (fileType?.resourceTags ?? [])
let headerVisibility = targetSource.headerVisibility ?? .public
@@ -107,7 +118,7 @@ class SourceGenerator {
// Static libraries don't support the header build phase
// For public headers they need to be copied
if headerVisibility == .public {
chosenBuildPhase = .copyFiles(TargetSource.BuildPhase.CopyFilesSettings(
chosenBuildPhase = .copyFiles(BuildPhaseSpec.CopyFilesSettings(
destination: .productsDirectory,
subpath: "include/$(PRODUCT_NAME)",
phaseOrder: .preCompile
@@ -123,16 +134,28 @@ class SourceGenerator {
attributes.append(headerVisibility.settingName)
}
}
if chosenBuildPhase == .sources && targetSource.compilerFlags.count > 0 {
settings["COMPILER_FLAGS"] = targetSource.compilerFlags.joined(separator: " ")
if let flags = fileType?.compilerFlags {
compilerFlags += flags.joined(separator: " ")
}
if !targetSource.compilerFlags.isEmpty {
if !compilerFlags.isEmpty {
compilerFlags += " "
}
compilerFlags += targetSource.compilerFlags.joined(separator: " ")
}
if chosenBuildPhase == .sources && !compilerFlags.isEmpty {
settings["COMPILER_FLAGS"] = compilerFlags
}
if !attributes.isEmpty {
settings["ATTRIBUTES"] = attributes
}
if chosenBuildPhase == .resources && !targetSource.resourceTags.isEmpty {
settings["ASSET_TAGS"] = targetSource.resourceTags
if chosenBuildPhase == .resources && !assetTags.isEmpty {
settings["ASSET_TAGS"] = assetTags
}
let buildFile = PBXBuildFile(file: fileReference, settings: settings.isEmpty ? nil : settings)
@@ -226,53 +249,22 @@ class SourceGenerator {
}
/// returns a default build phase for a given path. This is based off the filename
private func getDefaultBuildPhase(for path: Path, targetType: PBXProductType) -> TargetSource.BuildPhase? {
private func getDefaultBuildPhase(for path: Path, targetType: PBXProductType) -> BuildPhaseSpec? {
if path.lastComponent == "Info.plist" {
return nil
}
if let buildPhase = getFileType(path: path)?.buildPhase {
return buildPhase
}
if let fileExtension = path.extension {
switch fileExtension {
case "swift",
"m",
"mm",
"cpp",
"c",
"cc",
"S",
"xcdatamodeld",
"xcmappingmodel",
"intentdefinition",
"metal",
"mlmodel",
"rcproject":
return .sources
case "h",
"hh",
"hpp",
"ipp",
"tpp",
"hxx",
"def":
return .headers
case "modulemap":
guard targetType == .staticLibrary else { return nil }
return .copyFiles(TargetSource.BuildPhase.CopyFilesSettings(
return .copyFiles(BuildPhaseSpec.CopyFilesSettings(
destination: .productsDirectory,
subpath: "include/$(PRODUCT_NAME)",
phaseOrder: .preCompile
))
case "framework":
return .frameworks
case "xpc":
return .copyFiles(.xpcServices)
case "xcconfig",
"entitlements",
"gpx",
"lproj",
"xcfilelist",
"apns",
"pch":
return nil
default:
return .resources
}
@@ -414,13 +406,24 @@ class SourceGenerator {
let children = try getSourceChildren(targetSource: targetSource, dirPath: path, excludePaths: excludePaths, includePaths: includePaths)
let createIntermediateGroups = targetSource.createIntermediateGroups ?? project.options.createIntermediateGroups
let nonLocalizedChildren = children.filter { $0.extension != "lproj" }
let directories = children
.filter { $0.isDirectory && !Xcode.isDirectoryFileWrapper(path: $0) && $0.extension != "lproj" }
let directories = nonLocalizedChildren
.filter {
if let fileType = getFileType(path: $0) {
return !fileType.file
} else {
return $0.isDirectory && !Xcode.isDirectoryFileWrapper(path: $0)
}
}
let filePaths = children
.filter { $0.isFile || $0.isDirectory && $0.extension != "lproj"
&& Xcode.isDirectoryFileWrapper(path: $0)
let filePaths = nonLocalizedChildren
.filter {
if let fileType = getFileType(path: $0) {
return fileType.file
} else {
return $0.isFile || $0.isDirectory && Xcode.isDirectoryFileWrapper(path: $0)
}
}
let localisedDirectories = children
@@ -576,7 +579,7 @@ class SourceGenerator {
rootGroups.insert(fileReference)
}
let buildPhase: TargetSource.BuildPhase?
let buildPhase: BuildPhaseSpec?
if let targetBuildPhase = targetSource.buildPhase {
buildPhase = targetBuildPhase
} else {
Whitespace-only changes.
Whitespace-only changes.
@@ -467,6 +467,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
01E6934B571B91EAAFF0EDCB /* Resource.abc */ = {isa = PBXFileReference; path = Resource.abc; sourceTree = "<group>"; };
020E4DA91C9132845CAFDC5D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
039F208D1138598CE060F140 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
03D6D1E34022DA9524E5B38D /* Mintfile */ = {isa = PBXFileReference; path = Mintfile; sourceTree = "<group>"; };
@@ -495,6 +496,7 @@
2A5F527F2590C14956518174 /* FrameworkFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameworkFile.swift; sourceTree = "<group>"; };
2E1E747C7BC434ADB80CC269 /* Headers */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Headers; sourceTree = SOURCE_ROOT; };
2F430AABE04B7499B458D9DB /* SwiftFileInDotPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftFileInDotPath.swift; sourceTree = "<group>"; };
325F18855099386B08DD309B /* Resource.abcd */ = {isa = PBXFileReference; path = Resource.abcd; sourceTree = "<group>"; };
33F6DCDC37D2E66543D4965D /* App_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
34F13B632328979093CE6056 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
3571E41E19A5AB8AAAB04109 /* StandaloneAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = StandaloneAssets.xcassets; sourceTree = "<group>"; };
@@ -726,6 +728,8 @@
BF59AC868D227C92CA8B1B57 /* Model.xcmappingmodel */,
C7809CE9FE9852C2AA87ACE5 /* module.modulemap */,
553D289724905857912C7A1D /* outputList.xcfilelist */,
01E6934B571B91EAAFF0EDCB /* Resource.abc */,
325F18855099386B08DD309B /* Resource.abcd */,
8AF20308873AEEEC4D8C45D1 /* Settings.bundle */,
0704B6CAFBB53E0EBB08F6B3 /* ViewController.swift */,
);
+5
View File
@@ -11,6 +11,11 @@ options:
groupSortPosition: top
preGenCommand: echo "This is a pre-gen command"
postGenCommand: scripts/script.sh
fileTypes:
abc:
buildPhase: none
abcd:
buildPhase: none
fileGroups:
- Configs
- FileGroup
@@ -1110,6 +1110,12 @@ class SpecLoadingTests: XCTestCase {
watchOS: "3.0",
macOS: "10.12.1"
),
fileTypes: ["abc": FileType(
file: false,
buildPhase: .sources,
attributes: ["a1", "a2"],
resourceTags: ["r1", "r2"],
compilerFlags: ["c1", "c2"])],
findCarthageFrameworks: true,
preGenCommand: "swiftgen",
postGenCommand: "pod install"
@@ -1125,6 +1131,13 @@ class SpecLoadingTests: XCTestCase {
"findCarthageFrameworks": true,
"preGenCommand": "swiftgen",
"postGenCommand": "pod install",
"fileTypes": ["abc": [
"file": false,
"buildPhase": "sources",
"attributes": ["a1", "a2"],
"resourceTags": ["r1", "r2"],
"compilerFlags": ["c1", "c2"],
]]
]]
let parsedSpec = try getProjectSpec(dictionary)
try expect(parsedSpec) == expected
@@ -602,7 +602,7 @@ class SourceGeneratorTests: XCTestCase {
let target = Target(name: "Test", type: .framework, platform: .iOS, sources: [
TargetSource(path: "A", buildPhase: .resources),
TargetSource(path: "B", buildPhase: TargetSource.BuildPhase.none),
TargetSource(path: "B", buildPhase: BuildPhaseSpec.none),
TargetSource(path: "C", buildPhase: nil),
])
let project = Project(basePath: directoryPath, name: "Test", targets: [target])
@@ -614,11 +614,11 @@ class SourceGeneratorTests: XCTestCase {
try pbxProj.expectFile(paths: ["A", "Info.plist"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["A", "file.xcconfig"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["B", "file.swift"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["B", "file.xcassets"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["B", "file.h"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["B", "Info.plist"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["B", "file.xcconfig"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["B", "file.swift"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["B", "file.xcassets"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["B", "file.h"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["B", "Info.plist"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["B", "file.xcconfig"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.swift"], buildPhase: .sources)
try pbxProj.expectFile(paths: ["C", "file.m"], buildPhase: .sources)
@@ -633,16 +633,16 @@ class SourceGeneratorTests: XCTestCase {
try pbxProj.expectFile(paths: ["C", "file.tpp"], buildPhase: .headers)
try pbxProj.expectFile(paths: ["C", "file.hxx"], buildPhase: .headers)
try pbxProj.expectFile(paths: ["C", "file.def"], buildPhase: .headers)
try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["C", "file.entitlements"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["C", "file.gpx"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["C", "file.apns"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.entitlements"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.gpx"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.apns"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.xcassets"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["C", "file.123"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["C", "Info.plist"], buildPhase: TargetSource.BuildPhase.none)
try pbxProj.expectFile(paths: ["C", "Info.plist"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["C", "file.metal"], buildPhase: .sources)
try pbxProj.expectFile(paths: ["C", "file.mlmodel"], buildPhase: .sources)
try pbxProj.expectFile(paths: ["C", "Intent.intentdefinition"], buildPhase: .sources)
@@ -654,6 +654,57 @@ class SourceGeneratorTests: XCTestCase {
try pbxProj.expectFile(paths: ["C", "WithPeriod2.0", "file.swift"], buildPhase: .sources)
}
$0.it("sets file type properties") {
let directories = """
A:
- file.resource1
- file.source1
- file.abc:
- file.a
- file.exclude1
- file.unphased1
- ignored.swift
"""
try createDirectories(directories)
let target = Target(name: "Test", type: .framework, platform: .iOS, sources: [
TargetSource(path: "A"),
])
let project = Project(basePath: directoryPath, name: "Test", targets: [target], options: .init(fileTypes: [
"abc": FileType(buildPhase: .sources),
"source1": FileType(buildPhase: .sources, attributes: ["a1", "a2"], resourceTags: ["r1", "r2"], compilerFlags: ["-c1", "-c2"]),
"resource1": FileType(buildPhase: .resources, attributes: ["a1", "a2"], resourceTags: ["r1", "r2"], compilerFlags: ["-c1", "-c2"]),
"unphased1": FileType(buildPhase: BuildPhaseSpec.none),
"swift": FileType(buildPhase: .resources),
]))
let pbxProj = try project.generatePbxProj()
try pbxProj.expectFile(paths: ["A", "file.abc"], buildPhase: .sources)
try pbxProj.expectFile(paths: ["A", "file.source1"], buildPhase: .sources)
try pbxProj.expectFile(paths: ["A", "file.resource1"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["A", "file.unphased1"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["A", "ignored.swift"], buildPhase: .resources)
do {
let fileReference = try unwrap(pbxProj.getFileReference(paths: ["A", "file.resource1"], names: ["A", "file.resource1"]))
let buildFile = try unwrap(pbxProj.buildFiles.first(where: { $0.file === fileReference }))
let settings = NSDictionary(dictionary: buildFile.settings ?? [:])
try expect(settings) == [
"ATTRIBUTES": ["a1", "a2"],
"ASSET_TAGS": ["r1", "r2"],
]
}
do {
let fileReference = try unwrap(pbxProj.getFileReference(paths: ["A", "file.source1"], names: ["A", "file.source1"]))
let buildFile = try unwrap(pbxProj.buildFiles.first(where: { $0.file === fileReference }))
let settings = NSDictionary(dictionary: buildFile.settings ?? [:])
try expect(settings) == [
"ATTRIBUTES": ["a1", "a2"],
"COMPILER_FLAGS": "-c1 -c2",
]
}
}
$0.it("duplicate TargetSource is included once in sources build phase") {
let directories = """
Sources:
@@ -1084,7 +1135,7 @@ class SourceGeneratorTests: XCTestCase {
extension PBXProj {
/// expect a file within groups of the paths, using optional different names
func expectFile(paths: [String], names: [String]? = nil, buildPhase: TargetSource.BuildPhase? = nil, file: String = #file, line: Int = #line) throws {
func expectFile(paths: [String], names: [String]? = nil, buildPhase: BuildPhaseSpec? = nil, file: String = #file, line: Int = #line) 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 {