mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This is a tool specifically designed to generate Xcode projects for the Swift repo (as well as a couple of adjacent repos such as LLVM and Clang). It aims to provide a much more user-friendly experience than the CMake Xcode generation (`build-script --xcode`).
287 lines
5.9 KiB
Swift
287 lines
5.9 KiB
Swift
//===--- Command.swift ----------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2024 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
struct Command: Hashable {
|
|
var executable: AnyPath
|
|
var args: [Argument]
|
|
|
|
init(executable: AnyPath, args: [Argument]) {
|
|
self.executable = executable
|
|
self.args = args
|
|
}
|
|
}
|
|
|
|
extension Command: Decodable {
|
|
init(from decoder: Decoder) throws {
|
|
let command = try decoder.singleValueContainer().decode(String.self)
|
|
self = try CommandParser.parseCommand(command)
|
|
}
|
|
}
|
|
|
|
extension Command {
|
|
var printedArgs: [String] {
|
|
[executable.rawPath.escaped] + args.flatMap(\.printedArgs)
|
|
}
|
|
|
|
var printed: String {
|
|
printedArgs.joined(separator: " ")
|
|
}
|
|
}
|
|
|
|
// MARK: Argument
|
|
|
|
extension Command {
|
|
enum Argument: Hashable {
|
|
case option(Option)
|
|
case flag(Flag)
|
|
case value(String)
|
|
}
|
|
}
|
|
|
|
extension Command.Argument {
|
|
static func option(
|
|
_ flag: Command.Flag, spacing: Command.OptionSpacing, value: String
|
|
) -> Self {
|
|
.option(.init(flag, spacing: spacing, value: value))
|
|
}
|
|
|
|
var flag: Command.Flag? {
|
|
switch self {
|
|
case .option(let opt):
|
|
opt.flag
|
|
case .flag(let flag):
|
|
flag
|
|
case .value:
|
|
nil
|
|
}
|
|
}
|
|
|
|
var value: String? {
|
|
switch self {
|
|
case .option(let opt):
|
|
opt.value
|
|
case .value(let value):
|
|
value
|
|
case .flag:
|
|
nil
|
|
}
|
|
}
|
|
|
|
var printedArgs: [String] {
|
|
switch self {
|
|
case .option(let opt):
|
|
opt.printedArgs
|
|
case .value(let value):
|
|
[value.escaped]
|
|
case .flag(let f):
|
|
[f.printed]
|
|
}
|
|
}
|
|
|
|
var printed: String {
|
|
printedArgs.joined(separator: " ")
|
|
}
|
|
|
|
func option(for flag: Command.Flag) -> Command.Option? {
|
|
switch self {
|
|
case .option(let opt) where opt.flag == flag:
|
|
opt
|
|
default:
|
|
nil
|
|
}
|
|
}
|
|
|
|
/// If there is a value, apply a transform to it.
|
|
func mapValue(_ fn: (String) throws -> String) rethrows -> Self {
|
|
switch self {
|
|
case .option(let opt):
|
|
.option(try opt.mapValue(fn))
|
|
case .value(let value):
|
|
.value(try fn(value))
|
|
case .flag:
|
|
// Nothing to map.
|
|
self
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Flag
|
|
|
|
extension Command {
|
|
struct Flag: Hashable {
|
|
var dash: Dash
|
|
var name: Name
|
|
}
|
|
}
|
|
|
|
extension Command.Flag {
|
|
static func dash(_ name: Name) -> Self {
|
|
.init(dash: .single, name: name)
|
|
}
|
|
static func doubleDash(_ name: Name) -> Self {
|
|
.init(dash: .double, name: name)
|
|
}
|
|
|
|
var printed: String {
|
|
"\(dash.printed)\(name.rawValue)"
|
|
}
|
|
}
|
|
|
|
extension Command.Flag {
|
|
enum Dash: Int, CaseIterable, Comparable {
|
|
case single = 1, double
|
|
static func < (lhs: Self, rhs: Self) -> Bool { lhs.rawValue < rhs.rawValue }
|
|
}
|
|
}
|
|
|
|
extension Command.Flag.Dash {
|
|
init?(numDashes: Int) {
|
|
self.init(rawValue: numDashes)
|
|
}
|
|
|
|
var printed: String {
|
|
switch self {
|
|
case .single:
|
|
return "-"
|
|
case .double:
|
|
return "--"
|
|
}
|
|
}
|
|
}
|
|
|
|
extension DefaultStringInterpolation {
|
|
mutating func appendInterpolation(_ flag: Command.Flag) {
|
|
appendInterpolation(flag.printed)
|
|
}
|
|
}
|
|
|
|
// MARK: Option
|
|
|
|
extension Command {
|
|
struct Option: Hashable {
|
|
var flag: Flag
|
|
var spacing: OptionSpacing
|
|
var value: String
|
|
|
|
init(_ flag: Flag, spacing: OptionSpacing, value: String) {
|
|
self.flag = flag
|
|
self.spacing = spacing
|
|
self.value = value
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Command.Option {
|
|
func withValue(_ newValue: String) -> Self {
|
|
var result = self
|
|
result.value = newValue
|
|
return result
|
|
}
|
|
|
|
func mapValue(_ fn: (String) throws -> String) rethrows -> Self {
|
|
withValue(try fn(value))
|
|
}
|
|
|
|
var printedArgs: [String] {
|
|
switch spacing {
|
|
case .equals, .unspaced:
|
|
["\(flag)\(spacing)\(value.escaped)"]
|
|
case .spaced:
|
|
["\(flag)", value.escaped]
|
|
}
|
|
}
|
|
|
|
var printed: String {
|
|
printedArgs.joined(separator: " ")
|
|
}
|
|
}
|
|
|
|
// MARK: OptionSpacing
|
|
|
|
extension Command {
|
|
enum OptionSpacing: Comparable {
|
|
case equals, unspaced, spaced
|
|
}
|
|
}
|
|
|
|
extension Command.OptionSpacing {
|
|
var printed: String {
|
|
switch self {
|
|
case .equals: "="
|
|
case .unspaced: ""
|
|
case .spaced: " "
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: CustomStringConvertible
|
|
|
|
extension Command.Argument: CustomStringConvertible {
|
|
var description: String { printed }
|
|
}
|
|
|
|
extension Command.OptionSpacing: CustomStringConvertible {
|
|
var description: String { printed }
|
|
}
|
|
|
|
extension Command.Flag: CustomStringConvertible {
|
|
var description: String { printed }
|
|
}
|
|
|
|
extension Command.Option: CustomStringConvertible {
|
|
var description: String { printed }
|
|
}
|
|
|
|
// MARK: Comparable
|
|
// We sort the resulting command-line arguments to ensure deterministic
|
|
// ordering.
|
|
|
|
extension Command.Flag: Comparable {
|
|
static func < (lhs: Self, rhs: Self) -> Bool {
|
|
guard lhs.dash == rhs.dash else {
|
|
return lhs.dash < rhs.dash
|
|
}
|
|
return lhs.name.rawValue < rhs.name.rawValue
|
|
}
|
|
}
|
|
|
|
extension Command.Option: Comparable {
|
|
static func < (lhs: Self, rhs: Self) -> Bool {
|
|
guard lhs.flag == rhs.flag else {
|
|
return lhs.flag < rhs.flag
|
|
}
|
|
guard lhs.spacing == rhs.spacing else {
|
|
return lhs.spacing < rhs.spacing
|
|
}
|
|
return lhs.value < rhs.value
|
|
}
|
|
}
|
|
|
|
extension Command.Argument: Comparable {
|
|
static func < (lhs: Self, rhs: Self) -> Bool {
|
|
switch (lhs, rhs) {
|
|
// Sort flags < options < values
|
|
case (.flag, .option): true
|
|
case (.flag, .value): true
|
|
case (.option, .value): true
|
|
|
|
case (.option, .flag): false
|
|
case (.value, .flag): false
|
|
case (.value, .option): false
|
|
|
|
case (.flag(let lhs), .flag(let rhs)): lhs < rhs
|
|
case (.option(let lhs), .option(let rhs)): lhs < rhs
|
|
case (.value(let lhs), .value(let rhs)): lhs < rhs
|
|
}
|
|
}
|
|
}
|