diff --git a/CHANGELOG.md b/CHANGELOG.md index dcffde09..21f8cdd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ #### Added - Add Carthage static framework dependencies support. [#688](https://github.com/yonaskolb/XcodeGen/pull/688) @giginet +- Added `xcodegen dump` command [#710](https://github.com/yonaskolb/XcodeGen/pull/710) @yonaskolb - Added `--no-env` option to disable environment variables expansion [#704](https://github.com/yonaskolb/XcodeGen/pull/704) @rcari #### Fixed diff --git a/README.md b/README.md index c9747fcf..800be7f0 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Options: - **--use-cache**: Used to prevent unnecessarily generating the project. If this is set, then a cache file will be written to when a project is generated. If `xcodegen` is later run but the spec and all the files it contains are the same, the project won't be generated. - **--cache-path**: A custom path to use for your cache file. This defaults to `~/.xcodegen/cache/{PROJECT_SPEC_PATH_HASH}` -Use `xcodegen help` to see more detailed usage information. +There are other commands as well. Use `xcodegen help` to see more detailed usage information. ## Editing ```shell diff --git a/Sources/ProjectSpec/SpecFile.swift b/Sources/ProjectSpec/SpecFile.swift index dbea372e..f5a87777 100644 --- a/Sources/ProjectSpec/SpecFile.swift +++ b/Sources/ProjectSpec/SpecFile.swift @@ -144,7 +144,7 @@ extension Dictionary where Key == String, Value: Any { return merged } - func expand(variables: [String:String]) -> JSONDictionary { + func expand(variables: [String: String]) -> JSONDictionary { var expanded: JSONDictionary = self if !variables.isEmpty { @@ -189,8 +189,7 @@ extension Dictionary where Key == String, Value: Any { index = result.endIndex } else if substring[index] == "$" && substring[substring.index(index, offsetBy: 1)] == "{" - && substring[substring.index(index, offsetBy: 2)] != "}" - { + && substring[substring.index(index, offsetBy: 2)] != "}" { // This is the start of a variable expansion... let variableStart = index if let variableEnd = substring.firstIndex(of: "}") { diff --git a/Sources/ProjectSpec/SpecLoader.swift b/Sources/ProjectSpec/SpecLoader.swift index e193c55a..54c4cf53 100644 --- a/Sources/ProjectSpec/SpecLoader.swift +++ b/Sources/ProjectSpec/SpecLoader.swift @@ -7,7 +7,7 @@ import Yams public class SpecLoader { var project: Project! - private var projectDictionary: [String: Any]? + public private(set) var projectDictionary: [String: Any]? let version: Version public init(version: Version) { diff --git a/Sources/XcodeGenCLI/Commands/DumpCommand.swift b/Sources/XcodeGenCLI/Commands/DumpCommand.swift new file mode 100644 index 00000000..0cc075c4 --- /dev/null +++ b/Sources/XcodeGenCLI/Commands/DumpCommand.swift @@ -0,0 +1,71 @@ +import Foundation +import SwiftCLI +import PathKit +import ProjectSpec +import Yams + +class DumpCommand: ProjectCommand { + + private let dumpType = Key( + "--type", + "-t", + description: """ + The type of dump to output. Either \(DumpType.allCases.map { "\"\($0.rawValue)\"" }.joined(separator: ", ")). Defaults to \(DumpType.defaultValue.rawValue). The "parsed" types parse the project into swift and then back again. + """ + ) + + private let file = Key( + "--file", + "-f", + description: "The path of a file to write to. If not supplied will output to stdout" + ) + + init(version: Version) { + super.init(version: version, + name: "dump", + shortDescription: "Dumps the resolved project spec to stdout or a file" + ) + } + + override func execute(specLoader: SpecLoader, projectSpecPath: Path, project: Project) throws { + let type = dumpType.value ?? .defaultValue + + let output: String + switch type { + case .swiftDump: + var string = "" + dump(project, to: &string) + output = string + case .json: + let data = try JSONSerialization.data(withJSONObject: specLoader.projectDictionary!, options: .prettyPrinted) + output = String(data: data, encoding: .utf8)! + case .yaml: + output = try Yams.dump(object: specLoader.projectDictionary!) + case .parsedJSON: + let data = try JSONSerialization.data(withJSONObject: project.toJSONDictionary(), options: .prettyPrinted) + output = String(data: data, encoding: .utf8)! + case .parsedYaml: + output = try Yams.dump(object: project.toJSONDictionary()) + case .summary: + output = project.debugDescription + } + + if let file = file.value { + try file.parent().mkpath() + try file.write(output) + } else { + stdout.print(output) + } + } +} + +private enum DumpType: String, ConvertibleFromString, CaseIterable { + case swiftDump = "swift-dump" + case json + case yaml + case parsedJSON = "parsed-json" + case parsedYaml = "parsed-yaml" + case summary + + static var defaultValue: DumpType { .yaml } +} diff --git a/Sources/XcodeGenCLI/GenerateCommand.swift b/Sources/XcodeGenCLI/Commands/GenerateCommand.swift similarity index 72% rename from Sources/XcodeGenCLI/GenerateCommand.swift rename to Sources/XcodeGenCLI/Commands/GenerateCommand.swift index 92763ef4..e3e6a690 100644 --- a/Sources/XcodeGenCLI/GenerateCommand.swift +++ b/Sources/XcodeGenCLI/Commands/GenerateCommand.swift @@ -5,10 +5,7 @@ import SwiftCLI import XcodeGenKit import XcodeProj -class GenerateCommand: Command { - - let name: String = "generate" - let shortDescription: String = "Generate an Xcode project from a spec" +class GenerateCommand: ProjectCommand { let quiet = Flag( "-q", @@ -17,13 +14,6 @@ class GenerateCommand: Command { defaultValue: false ) - let disableEnvExpansion = Flag( - "-n", - "--no-env", - description: "Disable environment variables expansions", - defaultValue: false - ) - let useCache = Flag( "-c", "--use-cache", @@ -36,43 +26,23 @@ class GenerateCommand: Command { description: "Where the cache file will be loaded from and save to. Defaults to ~/.xcodegen/cache/{SPEC_PATH_HASH}" ) - let spec = Key( - "-s", - "--spec", - description: "The path to the project spec file. Defaults to project.yml" + let projectDirectory = Key( + "-p", + "--project", + description: "The path to the directory where the project should be generated. Defaults to the directory the spec is in. The filename is defined in the project spec" ) - let projectDirectory = Key("-p", "--project", description: "The path to the directory where the project should be generated. Defaults to the directory the spec is in. The filename is defined in the project spec") - - let version: Version - init(version: Version) { - self.version = version + super.init(version: version, + name: "generate", + shortDescription: "Generate an Xcode project from a spec" + ) } - func execute() throws { - - let projectSpecPath = (spec.value ?? "project.yml").absolute() + override func execute(specLoader: SpecLoader, projectSpecPath: Path, project: Project) throws { let projectDirectory = self.projectDirectory.value?.absolute() ?? projectSpecPath.parent() - if !projectSpecPath.exists { - throw GenerationError.missingProjectSpec(projectSpecPath) - } - - let specLoader = SpecLoader(version: version) - let project: Project - - let variables: [String: String] = disableEnvExpansion.value ? [:] : ProcessInfo.processInfo.environment - - // load project spec - do { - project = try specLoader.loadProject(path: projectSpecPath, variables: variables) - info("Loaded project:\n \(project.debugDescription.replacingOccurrences(of: "\n", with: "\n "))") - } catch { - throw GenerationError.projectSpecParsingError(error) - } - // validate project dictionary do { try specLoader.validateProjectDictionaryWarnings() diff --git a/Sources/XcodeGenCLI/Commands/ProjectCommand.swift b/Sources/XcodeGenCLI/Commands/ProjectCommand.swift new file mode 100644 index 00000000..18c2faa1 --- /dev/null +++ b/Sources/XcodeGenCLI/Commands/ProjectCommand.swift @@ -0,0 +1,56 @@ +import Foundation +import SwiftCLI +import ProjectSpec +import XcodeGenKit +import PathKit +import Core + +class ProjectCommand: Command { + + let version: Version + let name: String + let shortDescription: String + + let spec = Key( + "-s", + "--spec", + description: "The path to the project spec file. Defaults to project.yml" + ) + + let disableEnvExpansion = Flag( + "-n", + "--no-env", + description: "Disable environment variable expansions", + defaultValue: false + ) + + init(version: Version, name: String, shortDescription: String) { + self.version = version + self.name = name + self.shortDescription = shortDescription + } + + func execute() throws { + + let projectSpecPath = (spec.value ?? "project.yml").absolute() + + if !projectSpecPath.exists { + throw GenerationError.missingProjectSpec(projectSpecPath) + } + + let specLoader = SpecLoader(version: version) + let project: Project + + let variables: [String: String] = disableEnvExpansion.value ? [:] : ProcessInfo.processInfo.environment + + do { + project = try specLoader.loadProject(path: projectSpecPath, variables: variables) + } catch { + throw GenerationError.projectSpecParsingError(error) + } + + try execute(specLoader: specLoader, projectSpecPath: projectSpecPath, project: project) + } + + func execute(specLoader: SpecLoader, projectSpecPath: Path, project: Project) throws {} +} diff --git a/Sources/XcodeGenCLI/XcodeGenCLI.swift b/Sources/XcodeGenCLI/XcodeGenCLI.swift index 5a7e772c..32936561 100644 --- a/Sources/XcodeGenCLI/XcodeGenCLI.swift +++ b/Sources/XcodeGenCLI/XcodeGenCLI.swift @@ -12,7 +12,10 @@ public class XcodeGenCLI { name: "xcodegen", version: version.string, description: "Generates Xcode projects", - commands: [generateCommand] + commands: [ + generateCommand, + DumpCommand(version: version), + ] ) cli.parser.routeBehavior = .searchWithFallback(generateCommand) } diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index e1a5d75b..cd54dc4f 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -479,7 +479,7 @@ public class PBXProjGenerator { let dependecyLinkage = dependencyTarget.defaultLinkage let link = dependency.link ?? ((dependecyLinkage == .dynamic && target.type != .staticLibrary) || - (dependecyLinkage == .static && target.type.isExecutable)) + (dependecyLinkage == .static && target.type.isExecutable)) if link { let dependencyFile = targetFileReferences[dependencyTarget.name]!