Merge pull request #437 from yonaskolb/cli

Add XcodeGenCLI
This commit is contained in:
Yonas Kolb 2018-11-11 13:31:30 +11:00 committed by GitHub
commit 74d353455f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 204 additions and 139 deletions

View File

@ -8,6 +8,7 @@
#### Changed
- Changed spelling of build phases to **preBuildPhase** and **postBuildPhase**. [402](https://github.com/yonaskolb/XcodeGen/pull/402) @brentleyjones
- **BREAKING** Moved generation to a specific subcommand `xcodegen generate`. If not specifying any arguments `xcodegen` will still work [#437](https://github.com/yonaskolb/XcodeGen/pull/437) @yonaskolb
## 2.0.0

View File

@ -10,15 +10,6 @@
"version": "4.3.3"
}
},
{
"package": "Commander",
"repositoryURL": "https://github.com/kylef/Commander.git",
"state": {
"branch": null,
"revision": "e5b50ad7b2e91eeb828393e89b03577b16be7db9",
"version": "0.8.0"
}
},
{
"package": "JSONUtilities",
"repositoryURL": "https://github.com/yonaskolb/JSONUtilities.git",
@ -55,6 +46,15 @@
"version": "0.9.0"
}
},
{
"package": "SwiftCLI",
"repositoryURL": "https://github.com/jakeheis/SwiftCLI.git",
"state": {
"branch": null,
"revision": "37f4a7f863f6fe76ce44fc0023f331eea0089beb",
"version": "5.2.0"
}
},
{
"package": "SwiftShell",
"repositoryURL": "https://github.com/kareman/SwiftShell",

View File

@ -11,18 +11,23 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/kylef/PathKit.git", from: "0.9.0"),
.package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "1.0.0"),
.package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.1.0"),
.package(url: "https://github.com/kylef/Spectre.git", from: "0.9.0"),
.package(url: "https://github.com/onevcat/Rainbow.git", from: "3.0.0"),
.package(url: "https://github.com/tuist/xcodeproj.git", from: "6.3.0"),
.package(url: "https://github.com/jakeheis/SwiftCLI.git", from: "5.2.0"),
],
targets: [
.target(name: "XcodeGen", dependencies: [
"XcodeGenCLI",
]),
.target(name: "XcodeGenCLI", dependencies: [
"XcodeGenKit",
"Commander",
"ProjectSpec",
"SwiftCLI",
"Rainbow",
"PathKit",
]),
.target(name: "XcodeGenKit", dependencies: [
"ProjectSpec",

View File

@ -118,14 +118,16 @@ Simply run:
xcodegen
```
This will look for a project spec in the current directory called `project.yml`
This will look for a project spec in the current directory called `project.yml` and generate an Xcode project with the name defined in the spec.
Use `xcodegen --help` to see the list of options:
To specify any options use the full `xcodegen generate` command and add the following:
- **--spec**: An optional path to a `.yml` or `.json` project spec.
- **--spec**: An optional path to a `.yml` or `.json` project spec. Defaults to `project.yml`
- **--project**: An optional path to a directory where the project will be generated. By default this is the directory the spec lives in.
- **--quiet**: Suppress informational and success messages.
Use `xcodegen help` to see more detailed usage information.
## Editing
```shell
git clone https://github.com/yonaskolb/XcodeGen.git

View File

@ -95,7 +95,6 @@ extension Project: CustomDebugStringConvertible {
if !settingGroups.isEmpty {
string += "\nSetting Groups:\n\(indent)" + settingGroups.keys
.sorted()
.map { "⚙️ \($0)" }
.joined(separator: "\n\(indent)")
}

View File

@ -1,39 +0,0 @@
import Foundation
import Rainbow
struct Logger {
// MARK: - Properties
let isQuiet: Bool
let isColored: Bool
// MARK: - Initializers
init(isQuiet: Bool = false, isColored: Bool = true) {
self.isQuiet = isQuiet
self.isColored = isColored
}
// MARK: - Logging
func error(_ message: String) {
print(isColored ? message.red : message)
}
func info(_ message: String) {
if isQuiet {
return
}
print(message)
}
func success(_ message: String) {
if isQuiet {
return
}
print(isColored ? message.green : message)
}
}

View File

@ -1,89 +1,7 @@
import Commander
import Foundation
import JSONUtilities
import PathKit
import ProjectSpec
import XcodeGenKit
import xcodeproj
import XcodeGenCLI
let version = try Version("2.0.0")
func generate(spec: String, project: String, isQuiet: Bool, justVersion: Bool) {
if justVersion {
print(version)
exit(EXIT_SUCCESS)
}
let logger = Logger(isQuiet: isQuiet)
func fatalError(_ message: String) -> Never {
logger.error(message)
exit(1)
}
let projectSpecPath = Path(spec).absolute()
var projectPath = project == "" ? projectSpecPath.parent() : Path(project).absolute()
if !projectSpecPath.exists {
fatalError("No project spec found at \(projectSpecPath.absolute())")
}
let project: Project
do {
project = try Project(path: projectSpecPath)
logger.info("📋 Loaded project:\n \(project.debugDescription.replacingOccurrences(of: "\n", with: "\n "))")
} catch let error as CustomStringConvertible {
fatalError("Parsing project spec failed: \(error)")
} catch {
fatalError("Parsing project spec failed: \(error.localizedDescription)")
}
do {
try project.validateMinimumXcodeGenVersion(version)
try project.validate()
logger.info("⚙️ Generating project...")
let projectGenerator = ProjectGenerator(project: project)
let xcodeProject = try projectGenerator.generateXcodeProject()
logger.info("⚙️ Writing project...")
let fileWriter = FileWriter(project: project)
projectPath = projectPath + "\(project.name).xcodeproj"
try fileWriter.writeXcodeProject(xcodeProject, to: projectPath)
try fileWriter.writePlists()
logger.success("💾 Saved project to \(projectPath)")
} catch let error as SpecValidationError {
fatalError(error.description)
} catch {
fatalError("Generation failed: \(error.localizedDescription)")
}
}
command(
Option<String>(
"spec",
default: "project.yml",
flag: "s",
description: "The path to the project spec file"
),
Option<String>(
"project",
default: "",
flag: "p",
description: "The path to the folder where the project should be generated"
),
Flag(
"quiet",
default: false,
flag: "q",
description: "Suppress printing of informational and success messages"
),
Flag(
"version",
default: false,
flag: "v",
description: "Show XcodeGen version"
),
generate
).run(version.description)
let cli = XcodeGenCLI(version: version)
cli.execute()

View File

@ -0,0 +1,10 @@
import Foundation
import PathKit
import SwiftCLI
extension Path: ConvertibleFromString {
public static func convert(from: String) -> Path? {
return Path(from)
}
}

View File

@ -0,0 +1,20 @@
import Foundation
import SwiftCLI
class CommandRouter: Router {
let defaultCommand: Command
init(defaultCommand: Command) {
self.defaultCommand = defaultCommand
}
func parse(commandGroup: CommandGroup, arguments: ArgumentList) throws -> (CommandPath, OptionRegistry) {
if !arguments.hasNext() {
arguments.manipulate { _ in
[defaultCommand.name]
}
}
return try DefaultRouter().parse(commandGroup: commandGroup, arguments: arguments)
}
}

View File

@ -0,0 +1,85 @@
import Foundation
import SwiftCLI
import PathKit
import ProjectSpec
import XcodeGenKit
import xcodeproj
class GenerateCommand: Command {
let name: String = "generate"
let shortDescription: String = "Generate an Xcode project from a spec"
let quiet = Flag("-q", "--quiet", description: "Suppress all informational and success output", defaultValue: false)
let spec = Key<Path>("-s", "--spec", description: "The path to the project spec file. Defaults to project.yml")
let projectDirectory = Key<Path>("-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
}
func execute() throws {
let projectSpecPath = (spec.value ?? "project.yml").absolute()
let projectDirectory = self.projectDirectory.value?.absolute() ?? projectSpecPath.parent()
if !projectSpecPath.exists {
throw GenerationError.missingProjectSpec(projectSpecPath)
}
let project: Project
do {
project = try Project(path: projectSpecPath)
} catch {
throw GenerationError.projectSpecParsingError(error)
}
info("Loaded project:\n \(project.debugDescription.replacingOccurrences(of: "\n", with: "\n "))")
do {
try project.validateMinimumXcodeGenVersion(version)
try project.validate()
} catch let error as SpecValidationError {
throw GenerationError.validationError(error)
}
info("⚙️ Generating project...")
let xcodeProject: XcodeProj
do {
let projectGenerator = ProjectGenerator(project: project)
xcodeProject = try projectGenerator.generateXcodeProject()
} catch {
throw GenerationError.generationError(error)
}
info("⚙️ Writing project...")
let projectPath = projectDirectory + "\(project.name).xcodeproj"
do {
let fileWriter = FileWriter(project: project)
try fileWriter.writeXcodeProject(xcodeProject, to: projectPath)
try fileWriter.writePlists()
} catch {
throw GenerationError.writingError(error)
}
success("Created project at \(projectPath)")
}
func info(_ string: String) {
if !quiet.value {
stdout.print(string)
}
}
func success(_ string: String) {
if !quiet.value {
stdout.print(string.green)
}
}
}

View File

@ -0,0 +1,36 @@
import PathKit
import Foundation
import ProjectSpec
import SwiftCLI
import Rainbow
enum GenerationError: Error, CustomStringConvertible, ProcessError {
case missingProjectSpec(Path)
case projectSpecParsingError(Error)
case validationError(SpecValidationError)
case generationError(Error)
case writingError(Error)
var description: String {
switch self {
case .missingProjectSpec(let path):
return "No project spec found at \(path.absolute())"
case .projectSpecParsingError(let error):
return "Parsing project spec failed: \(error)"
case .validationError(let error):
return error.description
case .generationError(let error):
return String(describing: error)
case .writingError(let error):
return String(describing: error)
}
}
var message: String? {
return description.red
}
var exitStatus: Int32 {
return 1
}
}

View File

@ -0,0 +1,28 @@
import Foundation
import SwiftCLI
import ProjectSpec
public class XcodeGenCLI {
let cli: CLI
public init(version: Version) {
let generateCommand = GenerateCommand(version: version)
cli = CLI(name: "xcodegen",
version: version.string,
description: "Generates Xcode projects",
commands: [generateCommand])
cli.parser = Parser(router: CommandRouter(defaultCommand: generateCommand))
}
public func execute(arguments: [String]? = nil) {
let status: Int32
if let arguments = arguments {
status = cli.go(with: arguments)
} else {
status = cli.go()
}
exit(status)
}
}