diff --git a/CHANGELOG.md b/CHANGELOG.md index c97a88df..aa49f6b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ - Added support for the `instrumentsPackage` product type [#482](https://github.com/yonaskolb/XcodeGen/pull/482) @ksulliva - Added support for `inputFileLists` and `outputFileLists` within project build scripts [#500](https://github.com/yonaskolb/XcodeGen/pull/500) @lukewakeford +#### Changed +- **BREAKING** All the paths within included files are now relative to that file and not the root spec. This can be disabled with a `relativePaths: false` on the include. See [docs](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md#include) for more details [#489](https://github.com/yonaskolb/XcodeGen/pull/489) @ellneal + #### Fixed - Fixed `--project` argument not taking effect [#487](https://github.com/yonaskolb/XcodeGen/pull/487) @monowerker - Fixed Sticker Packs from generating an empty Source file phase which caused in error in the new build system [#492](https://github.com/yonaskolb/XcodeGen/pull/492) @rpassis diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index b5c1ad35..594f06ae 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -45,7 +45,22 @@ Required properties are marked with checkbox. Some of the YAML examples don't sh One or more specs can be included in the project spec. This can be used to split your project spec into multiple files, for easier structuring or sharing between multiple specs. Included specs can also include other specs and so on. -Include can either be a list of string paths or a single string path. They will be merged in order and then the current spec will be merged on top. +Include can either be a list of includes or a single include. They will be merged in order and then the current spec will be merged on top. + +An include can be provided via a string (the path) or an object of the form: + +**Include Object** + +- [x] **path**: **String** - The path to the included file. +- [ ] **relativePaths**: **Bool** - Dictates whether the included spec specifies paths relative to itself (the default) or the root spec file. + +```yaml +include: + - includedFile.yml + - path: path/to/includedFile.yml + relativePaths: false +``` + By default specs are merged additively. That is for every value: - if existing value and new value are both dictionaries merge them and continue down the hierachy diff --git a/Sources/ProjectSpec/AggregateTarget.swift b/Sources/ProjectSpec/AggregateTarget.swift index 45fcba14..e25fb553 100644 --- a/Sources/ProjectSpec/AggregateTarget.swift +++ b/Sources/ProjectSpec/AggregateTarget.swift @@ -61,3 +61,15 @@ extension AggregateTarget: NamedJSONDictionaryConvertible { attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [:] } } + +extension AggregateTarget: PathContainer { + + static var pathProperties: [PathProperty] { + return [ + .dictionary([ + .string("configFiles"), + .object("buildScripts", BuildScript.pathProperties), + ]), + ] + } +} diff --git a/Sources/ProjectSpec/BuildScript.swift b/Sources/ProjectSpec/BuildScript.swift index 8b436152..7042e18a 100644 --- a/Sources/ProjectSpec/BuildScript.swift +++ b/Sources/ProjectSpec/BuildScript.swift @@ -61,3 +61,12 @@ extension BuildScript: JSONObjectConvertible { showEnvVars = jsonDictionary.json(atKeyPath: "showEnvVars") ?? true } } + +extension BuildScript: PathContainer { + + static var pathProperties: [PathProperty] { + return [ + .string("path"), + ] + } +} diff --git a/Sources/ProjectSpec/Dependency.swift b/Sources/ProjectSpec/Dependency.swift index 722aae65..881eeee5 100644 --- a/Sources/ProjectSpec/Dependency.swift +++ b/Sources/ProjectSpec/Dependency.swift @@ -72,3 +72,12 @@ extension Dependency: JSONObjectConvertible { } } } + +extension Dependency: PathContainer { + + static var pathProperties: [PathProperty] { + return [ + .string("framework"), + ] + } +} diff --git a/Sources/ProjectSpec/PathContainer.swift b/Sources/ProjectSpec/PathContainer.swift new file mode 100644 index 00000000..a21e1e2e --- /dev/null +++ b/Sources/ProjectSpec/PathContainer.swift @@ -0,0 +1,50 @@ +import Foundation +import JSONUtilities +import PathKit + +protocol PathContainer { + + static var pathProperties: [PathProperty] { get } +} + +enum PathProperty { + case string(String) + case dictionary([PathProperty]) + case object(String, [PathProperty]) +} + +extension Array where Element == PathProperty { + + func resolvingPaths(in jsonDictionary: JSONDictionary, relativeTo path: Path) -> JSONDictionary { + var result = jsonDictionary + + for pathProperty in self { + switch pathProperty { + case .string(let key): + if let source = result[key] as? String { + result[key] = (path + source).string + } else if let source = result[key] as? [String] { + result[key] = source.map { (path + $0).string } + } else if let source = result[key] as? [String: String] { + result[key] = source.mapValues { (path + $0).string } + } + case .dictionary(let pathProperties): + for (key, dictionary) in result { + if let source = dictionary as? JSONDictionary { + result[key] = pathProperties.resolvingPaths(in: source, relativeTo: path) + } + } + case .object(let key, let pathProperties): + if let source = result[key] as? JSONDictionary { + result[key] = pathProperties.resolvingPaths(in: source, relativeTo: path) + } else if let source = result[key] as? [JSONDictionary] { + result[key] = source.map { pathProperties.resolvingPaths(in: $0, relativeTo: path) } + } else if let source = result[key] as? [String: JSONDictionary] { + result[key] = source.mapValues { pathProperties.resolvingPaths(in: $0, relativeTo: path) } + } + } + } + + return result + } +} diff --git a/Sources/ProjectSpec/Plist.swift b/Sources/ProjectSpec/Plist.swift index 5b2a502a..1f295106 100644 --- a/Sources/ProjectSpec/Plist.swift +++ b/Sources/ProjectSpec/Plist.swift @@ -24,3 +24,12 @@ extension Plist: JSONObjectConvertible { properties = jsonDictionary.json(atKeyPath: "properties") ?? [:] } } + +extension Plist: PathContainer { + + static var pathProperties: [PathProperty] { + return [ + .string("path"), + ] + } +} diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index 808b964c..c2c11b0f 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -133,8 +133,16 @@ extension Project: Equatable { extension Project { public init(basePath: Path, jsonDictionary: JSONDictionary) throws { + let spec = Spec(relativePath: Path(), jsonDictionary: jsonDictionary) + try self.init(spec: spec, basePath: basePath) + } + + public init(spec: Spec, basePath: Path) throws { self.basePath = basePath - let jsonDictionary = try Project.resolveProject(jsonDictionary: jsonDictionary) + + let spec = spec.resolvingPaths() + let jsonDictionary = try Project.resolveProject(jsonDictionary: spec.resolvedDictionary()) + name = try jsonDictionary.json(atKeyPath: "name") settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty settingGroups = jsonDictionary.json(atKeyPath: "settingGroups") @@ -166,6 +174,18 @@ extension Project { } } +extension Project: PathContainer { + + static var pathProperties: [PathProperty] { + return [ + .string("configFiles"), + .object("options", SpecOptions.pathProperties), + .object("targets", Target.pathProperties), + .object("aggregateTargets", AggregateTarget.pathProperties), + ] + } +} + extension Project { public var allFiles: [Path] { diff --git a/Sources/ProjectSpec/Settings.swift b/Sources/ProjectSpec/Settings.swift index 26d061bf..95254dd6 100644 --- a/Sources/ProjectSpec/Settings.swift +++ b/Sources/ProjectSpec/Settings.swift @@ -101,24 +101,6 @@ extension Dictionary where Key == String, Value: Any { } } -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 -} - public func += (lhs: inout BuildSettings, rhs: BuildSettings?) { guard let rhs = rhs else { return } lhs.merge(rhs) diff --git a/Sources/ProjectSpec/Spec.swift b/Sources/ProjectSpec/Spec.swift new file mode 100644 index 00000000..10f2c415 --- /dev/null +++ b/Sources/ProjectSpec/Spec.swift @@ -0,0 +1,111 @@ +import Foundation +import JSONUtilities +import PathKit + +public struct Spec { + public let relativePath: Path + public let jsonDictionary: JSONDictionary + public let subSpecs: [Spec] + + public init(relativePath: Path, jsonDictionary: JSONDictionary, subSpecs: [Spec] = []) { + self.relativePath = relativePath + self.jsonDictionary = jsonDictionary + self.subSpecs = subSpecs + } + + public init(filename: String, basePath: Path, relativePath: Path = Path()) throws { + let path = basePath + relativePath + filename + + // Depending on the extension we will either load the file as YAML or JSON + var json: [String: Any] + if path.extension?.lowercased() == "json" { + let data: Data = try path.read() + let jsonData = try JSONSerialization.jsonObject(with: data, options: .allowFragments) + guard let jsonDictionary = jsonData as? [String: Any] else { + fatalError("Invalid JSON at path \(path)") + } + json = jsonDictionary + } else { + json = try loadYamlDictionary(path: path) + } + + let processIncludeOption = { (option: Any) -> (String, Bool)? in + if let option = option as? String { + return (option, true) + } else if let option = option as? JSONDictionary, let path = option["path"] as? String { + return (path, (option["relativePaths"] as? Bool) ?? true) + } + return nil + } + + let includeSources: [(String, Bool)] + if let sources = json["include"] as? [Any] { + includeSources = sources.compactMap { processIncludeOption($0) } + } else if let source = json["include"] { + includeSources = [processIncludeOption(source)].compactMap { $0 } + } else { + includeSources = [] + } + + let includes = try includeSources.map { include -> Spec in + let path = Path(include.0) + let basePath = include.1 ? basePath + relativePath : basePath + relativePath + path.parent() + let relativePath = include.1 ? path.parent() : Path() + + return try Spec(filename: path.lastComponent, basePath: basePath, relativePath: relativePath) + } + + self.relativePath = relativePath + self.jsonDictionary = json + self.subSpecs = includes + } + + public func resolvedDictionary() -> JSONDictionary { + return jsonDictionary.merged(onto: + subSpecs + .map { $0.resolvedDictionary() } + .reduce([:]) { $1.merged(onto: $0) } + ) + } +} + +extension Spec { + + func resolvingPaths(relativeTo basePath: Path = Path()) -> Spec { + let relativePath = (basePath + self.relativePath).normalize() + guard relativePath != Path() else { + return self + } + + let jsonDictionary = Project.pathProperties.resolvingPaths(in: self.jsonDictionary, relativeTo: relativePath) + + return Spec( + relativePath: self.relativePath, + jsonDictionary: jsonDictionary, + subSpecs: self.subSpecs.map { template in + return template.resolvingPaths(relativeTo: relativePath) + } + ) + } +} + +extension Dictionary where Key == String, Value: Any { + + func merged(onto other: Dictionary) -> Dictionary { + var merged = other + + for (key, value) in self { + if key.hasSuffix(":REPLACE") { + let newKey = key[key.startIndex ..< key.index(key.endIndex, offsetBy: -8)] + merged[Key(newKey)] = value + } else if let dictionary = value as? Dictionary, let base = merged[key] as? Dictionary { + merged[key] = dictionary.merged(onto: base) as? Value + } else if let array = value as? [Any], let base = merged[key] as? [Any] { + merged[key] = (base + array) as? Value + } else { + merged[key] = value + } + } + return merged + } +} diff --git a/Sources/ProjectSpec/SpecLoader.swift b/Sources/ProjectSpec/SpecLoader.swift index 0542081a..db5ca726 100644 --- a/Sources/ProjectSpec/SpecLoader.swift +++ b/Sources/ProjectSpec/SpecLoader.swift @@ -1,48 +1,16 @@ import Foundation import JSONUtilities import PathKit -import Yams extension Project { public init(path: Path) throws { - let dictionary = try Project.loadDictionary(path: path) - try self.init(basePath: path.parent(), jsonDictionary: dictionary) + let basePath = path.parent() + let template = try Spec(filename: path.lastComponent, basePath: basePath) + try self.init(spec: template, basePath: basePath) } public static func loadDictionary(path: Path) throws -> JSONDictionary { - - // Depending on the extension we will either load the file as YAML or JSON - var json: [String: Any] - if path.extension?.lowercased() == "json" { - let data: Data = try path.read() - let jsonData = try JSONSerialization.jsonObject(with: data, options: .allowFragments) - guard let jsonDictionary = jsonData as? [String: Any] else { - fatalError("Invalid JSON at path \(path)") - } - json = jsonDictionary - } else { - 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 + return try Spec(filename: path.lastComponent, basePath: path.parent()).jsonDictionary } } diff --git a/Sources/ProjectSpec/SpecOptions.swift b/Sources/ProjectSpec/SpecOptions.swift index f218c88b..9bfb5a2d 100644 --- a/Sources/ProjectSpec/SpecOptions.swift +++ b/Sources/ProjectSpec/SpecOptions.swift @@ -120,3 +120,14 @@ extension SpecOptions: JSONObjectConvertible { generateEmptyDirectories = jsonDictionary.json(atKeyPath: "generateEmptyDirectories") ?? false } } + +extension SpecOptions: PathContainer { + + static var pathProperties: [PathProperty] { + return [ + .string("carthageBuildPath"), + .string("carthageExecutablePath"), + .string("defaultConfig"), + ] + } +} diff --git a/Sources/ProjectSpec/Target.swift b/Sources/ProjectSpec/Target.swift index 10218fad..0a9b748f 100644 --- a/Sources/ProjectSpec/Target.swift +++ b/Sources/ProjectSpec/Target.swift @@ -110,6 +110,26 @@ extension Target: CustomStringConvertible { } } +extension Target: PathContainer { + + static var pathProperties: [PathProperty] { + return [ + .dictionary([ + .string("sources"), + .object("sources", TargetSource.pathProperties), + .string("configFiles"), + .object("dependencies", Dependency.pathProperties), + .object("info", Plist.pathProperties), + .object("entitlements", Plist.pathProperties), + .object("preBuildScripts", BuildScript.pathProperties), + .object("prebuildScripts", BuildScript.pathProperties), + .object("postCompileScripts", BuildScript.pathProperties), + .object("postBuildScripts", BuildScript.pathProperties), + ]), + ] + } +} + extension Target { static func resolveTargetTemplates(jsonDictionary: JSONDictionary) throws -> JSONDictionary { @@ -125,10 +145,10 @@ extension Target { var mergedDictionary: JSONDictionary = [:] for template in templates { if let templateDictionary = targetTemplatesDictionary[template] { - mergedDictionary = merge(dictionary: templateDictionary, onto: mergedDictionary) + mergedDictionary = templateDictionary.merged(onto: mergedDictionary) } } - target = merge(dictionary: target, onto: mergedDictionary) + target = target.merged(onto: mergedDictionary) } targetsDictionary[targetName] = target } diff --git a/Sources/ProjectSpec/TargetSource.swift b/Sources/ProjectSpec/TargetSource.swift index fe51e445..950142b1 100644 --- a/Sources/ProjectSpec/TargetSource.swift +++ b/Sources/ProjectSpec/TargetSource.swift @@ -207,3 +207,13 @@ extension TargetSource.BuildPhase.CopyFilesSettings: JSONObjectConvertible { phaseOrder = .postCompile } } + +extension TargetSource: PathContainer { + + static var pathProperties: [PathProperty] { + return [ + .string("path"), + .string("excludes"), + ] + } +} diff --git a/Sources/XcodeGenKit/SpecLoader.swift b/Sources/XcodeGenKit/SpecLoader.swift index b9a35a2b..86edd32d 100644 --- a/Sources/XcodeGenKit/SpecLoader.swift +++ b/Sources/XcodeGenKit/SpecLoader.swift @@ -16,11 +16,11 @@ public class SpecLoader { } public func loadProject(path: Path) throws -> Project { - let dictionary = try Project.loadDictionary(path: path) - let project = try Project(basePath: path.parent(), jsonDictionary: dictionary) + let template = try Spec(filename: path.lastComponent, basePath: path.parent()) + let project = try Project(spec: template, basePath: path.parent()) self.project = project - projectDictionary = dictionary + projectDictionary = template.jsonDictionary return project } diff --git a/Tests/Fixtures/legacy_paths_test.yml b/Tests/Fixtures/legacy_paths_test.yml new file mode 100644 index 00000000..9ee86ce2 --- /dev/null +++ b/Tests/Fixtures/legacy_paths_test.yml @@ -0,0 +1,4 @@ +include: + path: legacy_paths_test/legacy_included_paths_test.yml + relativePaths: false +name: NewName diff --git a/Tests/Fixtures/legacy_paths_test/legacy_included_paths_test.yml b/Tests/Fixtures/legacy_paths_test/legacy_included_paths_test.yml new file mode 100644 index 00000000..de8cce3c --- /dev/null +++ b/Tests/Fixtures/legacy_paths_test/legacy_included_paths_test.yml @@ -0,0 +1,33 @@ +configFiles: + IncludedConfig: config +options: + carthageBuildPath: carthage_build + carthageExecutablePath: carthage_executable +targets: + IncludedTarget: + type: application + platform: tvOS + configFiles: + Config: config + sources: + - source + dependencies: + - framework: Framework + info: + path: info + entitlements: + path: entitlements + preBuildScripts: + - path: preBuildScript + postCompileScripts: + - path: postCompileScript + postBuildScripts: + - path: postBuildScript +aggregateTargets: + IncludedAggregateTarget: + targets: + - IncludedTarget + configFiles: + Config: config + buildScripts: + - path: buildScript diff --git a/Tests/Fixtures/paths_test.yml b/Tests/Fixtures/paths_test.yml new file mode 100644 index 00000000..2cc63148 --- /dev/null +++ b/Tests/Fixtures/paths_test.yml @@ -0,0 +1,32 @@ +include: paths_test/included_paths_test.yml +name: NewName +configFiles: + NewConfig: config +targets: + NewTarget: + type: application + platform: iOS + configFiles: + Config: config + sources: + - source + dependencies: + - framework: Framework + info: + path: info + entitlements: + path: entitlements + preBuildScripts: + - path: preBuildScript + postCompileScripts: + - path: postCompileScript + postBuildScripts: + - path: postBuildScript +aggregateTargets: + NewAggregateTarget: + targets: + - NewTarget + configFiles: + Config: config + buildScripts: + - path: buildScript diff --git a/Tests/Fixtures/paths_test/included_paths_test.yml b/Tests/Fixtures/paths_test/included_paths_test.yml new file mode 100644 index 00000000..aade9526 --- /dev/null +++ b/Tests/Fixtures/paths_test/included_paths_test.yml @@ -0,0 +1,31 @@ +include: recursive_test/recursive_test.yml +configFiles: + IncludedConfig: config +targets: + IncludedTarget: + type: application + platform: tvOS + configFiles: + Config: config + sources: + - source + dependencies: + - framework: Framework + info: + path: info + entitlements: + path: entitlements + preBuildScripts: + - path: preBuildScript + postCompileScripts: + - path: postCompileScript + postBuildScripts: + - path: postBuildScript +aggregateTargets: + IncludedAggregateTarget: + targets: + - IncludedTarget + configFiles: + Config: config + buildScripts: + - path: buildScript diff --git a/Tests/Fixtures/paths_test/recursive_test/recursive_test.yml b/Tests/Fixtures/paths_test/recursive_test/recursive_test.yml new file mode 100644 index 00000000..6dc9a86f --- /dev/null +++ b/Tests/Fixtures/paths_test/recursive_test/recursive_test.yml @@ -0,0 +1,33 @@ +options: + carthageBuildPath: carthage_build + carthageExecutablePath: carthage_executable +configFiles: + RecursiveConfig: config +targets: + RecursiveTarget: + type: application + platform: macOS + configFiles: + Config: config + sources: + - source + dependencies: + - framework: Framework + info: + path: info + entitlements: + path: entitlements + prebuildScripts: + - path: prebuildScript + postCompileScripts: + - path: postCompileScript + postBuildScripts: + - path: postBuildScript +aggregateTargets: + RecursiveAggregateTarget: + targets: + - RecursiveTarget + configFiles: + Config: config + buildScripts: + - path: buildScript diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 9c3f3b4c..3401adab 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -26,6 +26,124 @@ class SpecLoadingTests: XCTestCase { ] } + $0.it("expands directories") { + let path = fixturePath + "paths_test.yml" + let project = try Project(path: path) + + try expect(project.configFiles) == [ + "IncludedConfig": "paths_test/config", + "NewConfig": "config", + "RecursiveConfig": "paths_test/recursive_test/config", + ] + + try expect(project.options) == SpecOptions( + carthageBuildPath: "paths_test/recursive_test/carthage_build", + carthageExecutablePath: "paths_test/recursive_test/carthage_executable" + ) + + try expect(project.aggregateTargets) == [ + AggregateTarget( + name: "IncludedAggregateTarget", + targets: ["IncludedTarget"], + configFiles: ["Config": "paths_test/config"], + buildScripts: [BuildScript(script: .path("paths_test/buildScript"))] + ), + AggregateTarget( + name: "NewAggregateTarget", + targets: ["NewTarget"], + configFiles: ["Config": "config"], + buildScripts: [BuildScript(script: .path("buildScript"))] + ), + AggregateTarget( + name: "RecursiveAggregateTarget", + targets: ["RecursiveTarget"], + configFiles: ["Config": "paths_test/recursive_test/config"], + buildScripts: [BuildScript(script: .path("paths_test/recursive_test/buildScript"))] + ), + ] + + try expect(project.targets) == [ + Target( + name: "IncludedTarget", + type: .application, + platform: .tvOS, + configFiles: ["Config": "paths_test/config"], + sources: ["paths_test/source"], + dependencies: [Dependency(type: .framework, reference: "paths_test/Framework")], + info: Plist(path: "paths_test/info"), + entitlements: Plist(path: "paths_test/entitlements"), + preBuildScripts: [BuildScript(script: .path("paths_test/preBuildScript"))], + postCompileScripts: [BuildScript(script: .path("paths_test/postCompileScript"))], + postBuildScripts: [BuildScript(script: .path("paths_test/postBuildScript"))] + ), + Target( + name: "NewTarget", + type: .application, + platform: .iOS, + configFiles: ["Config": "config"], + sources: ["source"], + dependencies: [Dependency(type: .framework, reference: "Framework")], + info: Plist(path: "info"), + entitlements: Plist(path: "entitlements"), + preBuildScripts: [BuildScript(script: .path("preBuildScript"))], + postCompileScripts: [BuildScript(script: .path("postCompileScript"))], + postBuildScripts: [BuildScript(script: .path("postBuildScript"))] + ), + Target( + name: "RecursiveTarget", + type: .application, + platform: .macOS, + configFiles: ["Config": "paths_test/recursive_test/config"], + sources: ["paths_test/recursive_test/source"], + dependencies: [Dependency(type: .framework, reference: "paths_test/recursive_test/Framework")], + info: Plist(path: "paths_test/recursive_test/info"), + entitlements: Plist(path: "paths_test/recursive_test/entitlements"), + preBuildScripts: [BuildScript(script: .path("paths_test/recursive_test/prebuildScript"))], + postCompileScripts: [BuildScript(script: .path("paths_test/recursive_test/postCompileScript"))], + postBuildScripts: [BuildScript(script: .path("paths_test/recursive_test/postBuildScript"))] + ), + ] + } + + $0.it("respects directory expansion preference") { + let path = fixturePath + "legacy_paths_test.yml" + let project = try Project(path: path) + + try expect(project.configFiles) == [ + "IncludedConfig": "config", + ] + + try expect(project.options) == SpecOptions( + carthageBuildPath: "carthage_build", + carthageExecutablePath: "carthage_executable" + ) + + try expect(project.aggregateTargets) == [ + AggregateTarget( + name: "IncludedAggregateTarget", + targets: ["IncludedTarget"], + configFiles: ["Config": "config"], + buildScripts: [BuildScript(script: .path("buildScript"))] + ), + ] + + try expect(project.targets) == [ + Target( + name: "IncludedTarget", + type: .application, + platform: .tvOS, + configFiles: ["Config": "config"], + sources: ["source"], + dependencies: [Dependency(type: .framework, reference: "Framework")], + info: Plist(path: "info"), + entitlements: Plist(path: "entitlements"), + preBuildScripts: [BuildScript(script: .path("preBuildScript"))], + postCompileScripts: [BuildScript(script: .path("postCompileScript"))], + postBuildScripts: [BuildScript(script: .path("postBuildScript"))] + ), + ] + } + $0.it("parses yaml types") { let path = fixturePath + "yaml.yml" let dictionary = try loadYamlDictionary(path: path) @@ -537,7 +655,8 @@ fileprivate func getProjectSpec(_ project: [String: Any], file: String = #file, projectDictionary[key] = value } do { - return try Project(basePath: "", jsonDictionary: projectDictionary) + let template = Spec(relativePath: "", jsonDictionary: projectDictionary) + return try Project(spec: template, basePath: "") } catch { throw failure("\(error)", file: file, line: line) }