Merge remote-tracking branch 'upstream/master' into ts-no-overwriting-xcconfig-value

This commit is contained in:
toshi0383
2017-11-03 22:00:44 +09:00
63 changed files with 3037 additions and 1825 deletions
+125 -74
View File
@@ -65,7 +65,8 @@ public class PBXProjGenerator {
project = PBXProj(objectVersion: 46, rootObject: generateUUID(PBXProject.self, spec.name))
for group in spec.fileGroups {
_ = try getGroups(path: spec.basePath + group)
// TODO: call a seperate function that only creates groups not source files
_ = try getSources(sourceMetadata: Source(path: group), path: spec.basePath + group)
}
let buildConfigs: [XCBuildConfiguration] = spec.configs.map { config in
@@ -143,13 +144,17 @@ public class PBXProjGenerator {
let buildFile: PBXBuildFile
}
func generateSourceFile(path: Path) -> SourceFile {
func generateSourceFile(sourceMetadata source: Source, path: Path) -> SourceFile {
let fileReference = fileReferencesByPath[path]!
var settings: [String: Any]?
var settings: [String: Any] = [:]
if getBuildPhaseForPath(path) == .headers {
settings = ["ATTRIBUTES": ["Public"]]
}
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference), fileRef: fileReference, settings: settings)
if source.compilerFlags.count > 0 {
settings["COMPILER_FLAGS"] = source.compilerFlags.joined(separator: " ")
}
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference), fileRef: fileReference, settings: settings.isEmpty ? nil : settings)
return SourceFile(path: path, fileReference: fileReference, buildFile: buildFile)
}
@@ -157,17 +162,20 @@ public class PBXProjGenerator {
let carthageDependencies = getAllCarthageDependencies(target: target)
let sourcePaths = target.sources.map { spec.basePath + $0 }
var sourceFiles: [SourceFile] = []
let sourceFiles = try getAllSourceFiles(sources: target.sources)
for source in sourcePaths {
let sourceGroups = try getGroups(path: source)
sourceFiles += sourceGroups.sourceFiles
}
// find all Info.plist
let infoPlists: [Path] = sourcePaths.reduce([]) {
$0 + ((try? $1.recursiveChildren()) ?? []).filter { $0.lastComponent == "Info.plist" }
// find all Info.plist files
let infoPlists: [Path] = target.sources.map { spec.basePath + $0.path }.flatMap { (path) -> [Path] in
if path.isFile {
if path.lastComponent == "Info.plist" {
return [path]
}
} else {
if let children = try? path.recursiveChildren() {
return children.filter { $0.lastComponent == "Info.plist" }
}
}
return []
}
let configs: [XCBuildConfiguration] = spec.configs.map { config in
@@ -354,9 +362,11 @@ public class PBXProjGenerator {
addObject(resourcesBuildPhase)
buildPhases.append(resourcesBuildPhase.reference)
let headersBuildPhase = PBXHeadersBuildPhase(reference: generateUUID(PBXHeadersBuildPhase.self, target.name), files: getBuildFilesForPhase(.headers))
addObject(headersBuildPhase)
buildPhases.append(headersBuildPhase.reference)
if target.type == .framework || target.type == .dynamicLibrary {
let headersBuildPhase = PBXHeadersBuildPhase(reference: generateUUID(PBXHeadersBuildPhase.self, target.name), files: getBuildFilesForPhase(.headers))
addObject(headersBuildPhase)
buildPhases.append(headersBuildPhase.reference)
}
if !targetFrameworkBuildFiles.isEmpty {
@@ -406,8 +416,8 @@ public class PBXProjGenerator {
}
let carthageFrameworksToEmbed = Array(Set(carthageDependencies
.filter { $0.embed ?? true }
.map { $0.reference }))
.filter { $0.embed ?? true }
.map { $0.reference }))
.sorted()
if !carthageFrameworksToEmbed.isEmpty {
@@ -465,7 +475,7 @@ public class PBXProjGenerator {
}
if let fileExtension = path.extension {
switch fileExtension {
case "swift", "m", "cpp": return .sources
case "swift", "m", "mm", "cpp", "c": return .sources
case "h", "hh", "hpp", "ipp", "tpp", "hxx", "def": return .headers
case "xcconfig", "entitlements", "gpx", "lproj", "apns": return nil
default: return .resources
@@ -474,94 +484,134 @@ public class PBXProjGenerator {
return nil
}
func getFileReference(path: Path, inPath: Path) -> String {
func getFileReference(path: Path, inPath: Path, name: String? = nil) -> String {
if let fileReference = fileReferencesByPath[path] {
return fileReference
} else {
let fileReference = PBXFileReference(reference: generateUUID(PBXFileReference.self, path.lastComponent), sourceTree: .group, path: path.byRemovingBase(path: inPath).string)
let fileReference = PBXFileReference(reference: generateUUID(PBXFileReference.self, path.lastComponent), sourceTree: .group, name: name, path: path.byRemovingBase(path: inPath).string)
addObject(fileReference)
fileReferencesByPath[path] = fileReference.reference
return fileReference.reference
}
}
func getGroups(path: Path, depth: Int = 0) throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) {
func getAllSourceFiles(sources: [Source]) throws -> [SourceFile] {
return try sources.flatMap{ try getSources(sourceMetadata: $0, path: spec.basePath + $0.path).sourceFiles }
}
func getSingleGroup(path: Path, mergingChildren children: [String], depth: Int = 0) -> PBXGroup {
let group: PBXGroup
if let cachedGroup = groupsByPath[path] {
cachedGroup.children += children
group = cachedGroup
} else {
group = PBXGroup(
reference: generateUUID(PBXGroup.self, path.lastComponent),
children: children,
sourceTree: .group,
name: path.lastComponent,
path: depth == 0 && !spec.options.createIntermediateGroups ?
path.byRemovingBase(path: spec.basePath).string :
path.lastComponent
)
addObject(group)
groupsByPath[path] = group
}
return group
}
// Add groups for all parents recursively
// ex: path/foo/bar/baz/Hello.swift -> path:[foo:[bar:[baz:[Hello.swift]]]]
func getIntermediateGroups(path: Path, group: PBXGroup) -> PBXGroup {
// verify path is a subpath of spec.basePath
guard Path(components: zip(path.components, spec.basePath.components).map{ $0.0 }) == spec.basePath else {
return group
}
// base case
if path == spec.basePath {
return group
}
// recursive case
return getIntermediateGroups(
path: path.parent(),
group: getSingleGroup(path: path, mergingChildren: [group.reference])
)
}
func getVariantGroup(path: Path, inPath: Path) -> PBXVariantGroup {
let variantGroup: PBXVariantGroup
if let cachedGroup = variantGroupsByPath[path] {
variantGroup = cachedGroup
} else {
variantGroup = PBXVariantGroup(reference: generateUUID(PBXVariantGroup.self, path.byRemovingBase(path: inPath).string),
children: [],
name: path.lastComponent,
sourceTree: .group)
addObject(variantGroup)
variantGroupsByPath[path] = variantGroup
}
return variantGroup
}
func getSources(sourceMetadata source: Source, path: Path, depth: Int = 0) throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) {
// if we have a file, move it to children and use the parent as the path
let (children, path) = path.isFile ?
([path], path.parent()) :
(try path.children(), path)
let excludedFiles: [String] = [".DS_Store"]
let directories = try path.children()
let directories = children
.filter { $0.isDirectory && $0.extension == nil && $0.extension != "lproj" }
.sorted { $0.lastComponent < $1.lastComponent }
let filePaths = try path.children()
let filePaths = children
.filter { $0.isFile || $0.extension != nil && $0.extension != "lproj" }
.filter { !excludedFiles.contains($0.lastComponent) }
.sorted { $0.lastComponent < $1.lastComponent }
let localisedDirectories = try path.children()
let localisedDirectories = children
.filter { $0.extension == "lproj" }
.sorted { $0.lastComponent < $1.lastComponent }
var groupChildren: [String] = filePaths.map { getFileReference(path: $0, inPath: path) }
var allSourceFiles: [SourceFile] = filePaths.map { generateSourceFile(path: $0) }
var allSourceFiles: [SourceFile] = filePaths.map {
generateSourceFile(sourceMetadata: Source(path: $0.string, compilerFlags: source.compilerFlags), path: $0)
}
var groups: [PBXGroup] = []
for path in directories {
let subGroups = try getGroups(path: path, depth: depth + 1)
let subGroups = try getSources(sourceMetadata: source, path: path, depth: depth + 1)
allSourceFiles += subGroups.sourceFiles
groupChildren.append(subGroups.groups.first!.reference)
groups += subGroups.groups
}
// create variant groups of the base localisation first
var baseLocalisationVariantGroups:[PBXVariantGroup] = []
var baseLocalisationVariantGroups: [PBXVariantGroup] = []
if let baseLocalisedDirectory = localisedDirectories.first(where: { $0.lastComponent == "Base.lproj" }) {
for path in try baseLocalisedDirectory.children() {
let filePath = "\(baseLocalisedDirectory.lastComponent)/\(path.lastComponent)"
let variantGroup: PBXVariantGroup
if let cachedGroup = variantGroupsByPath[path] {
variantGroup = cachedGroup
} else {
variantGroup = PBXVariantGroup(reference: generateUUID(PBXVariantGroup.self, filePath),
children: [],
name: path.lastComponent,
sourceTree: .group)
variantGroupsByPath[path] = variantGroup
addObject(variantGroup)
groupChildren.append(variantGroup.reference)
}
for filePath in try baseLocalisedDirectory.children() {
let variantGroup = getVariantGroup(path: filePath, inPath: path)
groupChildren.append(variantGroup.reference)
baseLocalisationVariantGroups.append(variantGroup)
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, variantGroup.reference), fileRef: variantGroup.reference, settings: nil)
allSourceFiles.append(SourceFile(path: path, fileReference: variantGroup.reference, buildFile: buildFile))
allSourceFiles.append(SourceFile(path: filePath, fileReference: variantGroup.reference, buildFile: buildFile))
}
}
// add references to localised resources into base localisation variant groups
for localisedDirectory in localisedDirectories {
let localisationName = localisedDirectory.lastComponentWithoutExtension
for path in try localisedDirectory.children().sorted { $0.lastComponent < $1.lastComponent } {
let filePath = "\(localisedDirectory.lastComponent)/\(path.lastComponent)"
for filePath in try localisedDirectory.children().sorted { $0.lastComponent < $1.lastComponent } {
// find base localisation variant group
let name = path.lastComponentWithoutExtension
let variantGroup = baseLocalisationVariantGroups.first { Path($0.name!).lastComponentWithoutExtension == name }
// ex: Foo.strings will be added to Foo.strings or Foo.storyboard variant group
let variantGroup = baseLocalisationVariantGroups.first { Path($0.name!).lastComponent == filePath.lastComponent } ??
baseLocalisationVariantGroups.first { Path($0.name!).lastComponentWithoutExtension == filePath.lastComponentWithoutExtension }
let fileReference: String
if let cachedFileReference = fileReferencesByPath[path] {
fileReference = cachedFileReference
} else {
let reference = PBXFileReference(reference: generateUUID(PBXFileReference.self, path.lastComponent),
sourceTree: .group,
name: variantGroup != nil ? localisationName : path.lastComponent,
path: filePath)
addObject(reference)
fileReference = reference.reference
fileReferencesByPath[path] = fileReference
}
let fileReference = getFileReference(path: filePath, inPath: path, name: variantGroup != nil ? localisationName : filePath.lastComponent)
if let variantGroup = variantGroup {
if !variantGroup.children.contains(fileReference) {
@@ -572,23 +622,24 @@ public class PBXProjGenerator {
let buildFile = PBXBuildFile(reference: generateUUID(PBXBuildFile.self, fileReference),
fileRef: fileReference,
settings: nil)
allSourceFiles.append(SourceFile(path: path, fileReference: fileReference, buildFile: buildFile))
allSourceFiles.append(SourceFile(path: filePath, fileReference: fileReference, buildFile: buildFile))
groupChildren.append(fileReference)
}
}
}
let groupPath: String = depth == 0 ? path.byRemovingBase(path: spec.basePath).string : path.lastComponent
let group: PBXGroup
if let cachedGroup = groupsByPath[path] {
group = cachedGroup
if spec.options.createIntermediateGroups {
group = getIntermediateGroups(
path: path.parent(),
group: getSingleGroup(path: path, mergingChildren: groupChildren, depth: depth)
)
} else {
group = PBXGroup(reference: generateUUID(PBXGroup.self, path.lastComponent), children: groupChildren, sourceTree: .group, name: path.lastComponent, path: groupPath)
addObject(group)
if depth == 0 {
topLevelGroups.append(group)
}
groupsByPath[path] = group
group = getSingleGroup(path: path, mergingChildren: groupChildren, depth: depth)
}
if depth == 0 {
topLevelGroups.append(group)
}
groups.insert(group, at: 0)
return (allSourceFiles, groups)
+1 -1
View File
@@ -15,7 +15,7 @@ import ProjectSpec
public class ProjectGenerator {
var spec: ProjectSpec
let spec: ProjectSpec
let currentXcodeVersion = "0900"
public init(spec: ProjectSpec) {
+1 -1
View File
@@ -37,7 +37,7 @@ extension ProjectSpec {
public func getTargetBuildSettings(target: Target, config: Config) -> BuildSettings {
var buildSettings = BuildSettings()
if options.settingPresets.applyTarget {
buildSettings += SettingsPresetFile.platform(target.platform).getBuildSettings()
buildSettings += SettingsPresetFile.product(target.type).getBuildSettings()
-63
View File
@@ -1,63 +0,0 @@
//
// SpecLoader.swift
// XcodeGen
//
// Created by Yonas Kolb on 30/8/17.
//
//
import Foundation
import ProjectSpec
import PathKit
import Yams
import JSONUtilities
public struct SpecLoader {
public static func loadSpec(path: Path) throws -> ProjectSpec {
let dictionary = try loadDictionary(path: path)
return try ProjectSpec(basePath: path.parent(), jsonDictionary: dictionary)
}
private static func loadDictionary(path: Path) throws -> JSONDictionary {
var json = try loadYamlDictionary(path: path)
var includes: [String]
if let includeString = json["include"] as? String {
includes = [includeString]
} else if let includeArray = json["include"] as? [String] {
includes = includeArray
} else {
includes = []
}
if !includes.isEmpty {
var includeDictionary: JSONDictionary = [:]
for include in includes {
let includePath = path.parent() + include
let dictionary = try loadDictionary(path: includePath)
includeDictionary = merge(dictionary: dictionary, onto: includeDictionary)
}
json = merge(dictionary: json, onto: includeDictionary)
}
return json
}
private static func merge(dictionary: JSONDictionary, onto base: JSONDictionary) -> JSONDictionary {
var merged = base
for (key, value) in dictionary {
if key.hasSuffix(":REPLACE") {
let newKey = key.replacingOccurrences(of: ":REPLACE", with: "")
merged[newKey] = value
} else if let dictionary = value as? JSONDictionary, let base = merged[key] as? JSONDictionary {
merged[key] = merge(dictionary: dictionary, onto: base)
} else if let array = value as? [Any], let base = merged[key] as? [Any] {
merged[key] = base + array
} else {
merged[key] = value
}
}
return merged
}
}
-199
View File
@@ -1,199 +0,0 @@
//
// SpecValidation.swift
// XcodeGenKit
//
// Created by Yonas Kolb on 24/9/17.
//
import Foundation
import ProjectSpec
import PathKit
extension ProjectSpec {
public mutating func validate() throws {
if configs.isEmpty {
configs = [Config(name: "Debug", type: .debug), Config(name: "Release", type: .release)]
}
var errors: [SpecValidationError.Error] = []
func validateSettings(_ settings: Settings) -> [SpecValidationError.Error] {
var errors: [SpecValidationError.Error] = []
for group in settings.groups {
if let settings = settingGroups[group] {
errors += validateSettings(settings)
} else {
errors.append(.invalidSettingsGroup(group))
}
}
for config in settings.configSettings.keys {
if !configs.contains(where: { $0.name.lowercased().contains(config.lowercased())}) {
errors.append(.invalidConfigReference(config))
}
}
return errors
}
for fileGroup in fileGroups {
if !(basePath + fileGroup).exists {
errors.append(.invalidFileGroup(fileGroup))
}
}
for (config, configFile) in configFiles {
if !(basePath + configFile).exists {
errors.append(.invalidConfigFile(configFile: configFile, config: config))
}
}
for settings in settingGroups.values {
errors += validateSettings(settings)
}
for target in targets {
for dependency in target.dependencies {
if dependency.type == .target, getTarget(dependency.reference) == nil {
errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference))
}
}
for (config, configFile) in target.configFiles {
if !(basePath + configFile).exists {
errors.append(.invalidTargetConfigFile(configFile: configFile, config: config, target: target.name))
}
}
for config in target.settings.configSettings.keys {
if getConfig(config) == nil {
errors.append(.invalidBuildSettingConfig(config))
}
}
for source in target.sources {
let sourcePath = basePath + source
if !sourcePath.exists {
errors.append(.missingTargetSource(target: target.name, source: sourcePath.string))
}
}
if let scheme = target.scheme {
for configVariant in scheme.configVariants {
if !configs.contains(where: { $0.name.contains(configVariant) && $0.type == .debug }) {
errors.append(.invalidTargetSchemeConfigVariant(target: target.name, configVariant: configVariant, configType: .debug))
}
if !configs.contains(where: { $0.name.contains(configVariant) && $0.type == .release }) {
errors.append(.invalidTargetSchemeConfigVariant(target: target.name, configVariant: configVariant, configType: .release))
}
}
if scheme.configVariants.isEmpty {
if !configs.contains(where: { $0.type == .debug }) {
errors.append(.missingConfigTypeForGeneratedTargetScheme(target: target.name, configType: .debug))
}
if !configs.contains(where: { $0.type == .release }) {
errors.append(.missingConfigTypeForGeneratedTargetScheme(target: target.name, configType: .release))
}
}
for testTarget in scheme.testTargets {
if getTarget(testTarget) == nil {
errors.append(.invalidTargetSchemeTest(target: target.name, testTarget: testTarget))
}
}
}
let scripts = target.prebuildScripts + target.postbuildScripts
for script in scripts {
if case let .path(pathString) = script.script {
let scriptPath = basePath + pathString
if !scriptPath.exists {
errors.append(.invalidBuildScriptPath(target: target.name, path: pathString))
}
}
}
errors += validateSettings(target.settings)
}
for scheme in schemes {
for buildTarget in scheme.build.targets {
if getTarget(buildTarget.target) == nil {
errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target))
}
}
if let buildAction = scheme.run, getConfig(buildAction.config) == nil {
errors.append(.invalidSchemeConfig(scheme: scheme.name, config: buildAction.config))
}
if let buildAction = scheme.test, getConfig(buildAction.config) == nil {
errors.append(.invalidSchemeConfig(scheme: scheme.name, config: buildAction.config))
}
if let buildAction = scheme.profile, getConfig(buildAction.config) == nil {
errors.append(.invalidSchemeConfig(scheme: scheme.name, config: buildAction.config))
}
if let buildAction = scheme.analyze, getConfig(buildAction.config) == nil {
errors.append(.invalidSchemeConfig(scheme: scheme.name, config: buildAction.config))
}
if let buildAction = scheme.archive, getConfig(buildAction.config) == nil {
errors.append(.invalidSchemeConfig(scheme: scheme.name, config: buildAction.config))
}
}
if !errors.isEmpty {
throw SpecValidationError(errors: errors)
}
}
}
public struct SpecValidationError: Error, CustomStringConvertible {
public var errors: [Error]
public enum Error: CustomStringConvertible {
case invalidTargetDependency(target: String, dependency: String)
case invalidSchemeTarget(scheme: String, target: String)
case invalidSchemeConfig(scheme: String, config: String)
case invalidConfigFile(configFile: String, config: String)
case invalidTargetConfigFile(configFile: String, config: String, target: String)
case invalidBuildSettingConfig(String)
case invalidSettingsGroup(String)
case missingTargetSource(target: String, source: String)
case invalidBuildScriptPath(target: String, path: String)
case invalidTargetSchemeConfigVariant(target: String, configVariant: String, configType: ConfigType)
case invalidTargetSchemeTest(target: String, testTarget: String)
case invalidFileGroup(String)
case invalidConfigReference(String)
case missingConfigTypeForGeneratedTargetScheme(target: String, configType: ConfigType)
public var description: String {
switch self {
case let .invalidTargetDependency(target, dependency): return "Target \(target.quoted) has invalid dependency: \(dependency.quoted)"
case let .invalidTargetConfigFile(configFile, config, target): return "Target \(target.quoted) has invalid config file \(configFile.quoted) for config \(config.quoted)"
case let .invalidConfigFile(configFile, config): return "Invalid config file \(configFile.quoted) for config \(config.quoted)"
case let .invalidSchemeTarget(scheme, target): return "Scheme \(scheme.quoted) has invalid build target \(target.quoted)"
case let .invalidSchemeConfig(scheme, config): return "Scheme \(scheme.quoted) has invalid build configuration \(config.quoted)"
case let .invalidBuildSettingConfig(config): return "Build setting has invalid build configuration \(config.quoted)"
case let .missingTargetSource(target, source): return "Target \(target.quoted) has a missing source directory \(source.quoted)"
case let .invalidSettingsGroup(group): return "Invalid settings group \(group.quoted)"
case let .invalidBuildScriptPath(target, path): return "Target \(target.quoted) has a script path that doesn't exist \(path.quoted)"
case let .invalidTargetSchemeConfigVariant(target, configVariant, configType): return "Target \(target.quoted) has an invalid scheme config variant which requires a config that has a \(configType.rawValue.quoted) type and contains the name \(configVariant.quoted)"
case let .invalidTargetSchemeTest(target, test): return "Target \(target.quoted) scheme has invalid test \(test.quoted)"
case let .invalidFileGroup(group): return "Invalid file group \(group.quoted)"
case let .invalidConfigReference(config): return "Invalid config reference \(config.quoted)"
case let .missingConfigTypeForGeneratedTargetScheme(target, configType): return "Target \(target.quoted) is missing a config of type \(configType.rawValue) to generate its scheme"
}
}
}
public var description: String {
let title: String
if errors.count == 1 {
title = "Spec validation error: "
} else {
title = "\(errors.count) Spec validations errors:\n\t- "
}
return "\(title)" + errors.map { $0.description }.joined(separator: "\n\t- ")
}
}
-39
View File
@@ -1,39 +0,0 @@
//
// YamsExtensions.swift
// XcodeGenKit
//
// Created by Yonas Kolb on 29/9/17.
//
import Foundation
import Yams
import PathKit
func loadYamlDictionary(path: Path) throws -> [String: Any] {
let string: String = try path.read()
if string == "" {
return [:]
}
guard let yaml = try Yams.load(yaml: string) else {
return [:]
}
return filterNull(yaml) as! [String: Any]
}
fileprivate func filterNull(_ object: Any) -> Any {
var returnedValue: Any = object
if let dict = object as? [String: Any] {
var mutabledic: [String: Any] = [:]
for (key, value) in dict {
mutabledic[key] = filterNull(value)
}
returnedValue = mutabledic
} else if let array = object as? [Any] {
var mutableArray: [Any] = array
for (index, value) in array.enumerated() {
mutableArray[index] = filterNull(value)
}
returnedValue = mutableArray
}
return (object is NSNull) ? "" : returnedValue
}