Files
swift-mirror/utils/swift-xcodegen/Sources/SwiftXcodeGen/Command/KnownCommand.swift
Hamish Knight 03d8ea5248 Introduce swift-xcodegen
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`).
2024-11-05 22:42:10 +00:00

284 lines
8.1 KiB
Swift

//===--- KnownCommand.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
//
//===----------------------------------------------------------------------===//
@preconcurrency import SwiftOptions
enum KnownCommand {
case clang, swiftc, swiftFrontend
}
extension PathProtocol {
var knownCommand: KnownCommand? {
switch fileName {
case "clang", "clang++", "c++", "cc", "clang-cache":
.clang
case "swiftc":
.swiftc
case "swift-frontend":
.swiftFrontend
default:
nil
}
}
}
extension Command.Flag {
/// If this spec is for a suboption, returns the command that it is for, or
/// `nil` if it is not for a suboption
var subOptionCommand: KnownCommand? {
switch self {
case .Xcc:
.clang
case .Xfrontend:
.swiftFrontend
default:
nil
}
}
}
extension Command.Flag {
struct Name: Hashable {
let rawValue: String
// Fileprivate because definitions should be added below.
fileprivate init(_ rawValue: String) {
self.rawValue = rawValue
}
}
fileprivate static func dash(_ name: String) -> Self {
dash(.init(name))
}
fileprivate static func doubleDash(_ name: String) -> Self {
doubleDash(.init(name))
}
fileprivate static func swiftc(_ opt: SwiftOptions.Option) -> Self {
let dashes = opt.spelling.prefix(while: { $0 == "-" }).count
guard let dash = Command.Flag.Dash(numDashes: dashes) else {
fatalError("Dash count not handled")
}
let name = String(opt.spelling.dropFirst(dashes))
return .init(dash: dash, name: .init(name))
}
}
extension SwiftOptions.Option {
fileprivate static let optionsWithJoinedEquals: Set<String> = {
// Make a note of all flags that are equal joined.
var result = Set<String>()
for opt in SwiftOptions.Option.allOptions {
switch opt.kind {
case .separate, .input, .flag, .remaining, .multiArg:
continue
case .joined, .joinedOrSeparate, .commaJoined:
if opt.spelling.hasSuffix("=") {
result.insert(String(opt.spelling.dropLast()))
}
}
}
return result
}()
fileprivate var spacingSpec: Command.FlagSpec.OptionSpacingSpec {
var spacing = Command.OptionSpacingSpec()
switch kind {
case .input, .remaining:
fatalError("Not handled")
case .flag:
break
case .joined, .commaJoined:
spacing.insert(.unspaced)
case .separate, .multiArg:
spacing.insert(.spaced)
case .joinedOrSeparate:
spacing.insert([.unspaced, .spaced])
}
if Self.optionsWithJoinedEquals.contains(spelling) {
spacing.insert(.equals)
}
return spacing
}
}
extension Command.FlagSpec {
fileprivate init(_ options: [SwiftOptions.Option]) {
self.init(options.map { .init(.swiftc($0), option: $0.spacingSpec) })
}
}
extension Command.Flag {
// Swift + Clang
static let D = dash("D")
static let I = dash("I")
static let target = dash("target")
// Clang
static let isystem = dash("isystem")
static let isysroot = dash("isysroot")
static let f = dash("f")
static let U = dash("U")
static let W = dash("W")
static let std = dash("std")
// Swift
static let cxxInteroperabilityMode =
swiftc(.cxxInteroperabilityMode)
static let enableExperimentalCxxInterop =
swiftc(.enableExperimentalCxxInterop)
static let enableExperimentalFeature =
swiftc(.enableExperimentalFeature)
static let enableLibraryEvolution =
swiftc(.enableLibraryEvolution)
static let experimentalSkipAllFunctionBodies =
swiftc(.experimentalSkipAllFunctionBodies)
static let experimentalSkipNonInlinableFunctionBodies =
swiftc(.experimentalSkipNonInlinableFunctionBodies)
static let experimentalSkipNonInlinableFunctionBodiesWithoutTypes =
swiftc(.experimentalSkipNonInlinableFunctionBodiesWithoutTypes)
static let F =
swiftc(.F)
static let Fsystem =
swiftc(.Fsystem)
static let moduleName =
swiftc(.moduleName)
static let moduleLinkName =
swiftc(.moduleLinkName)
static let nostdimport =
swiftc(.nostdimport)
static let O =
swiftc(.O)
static let Onone =
swiftc(.Onone)
static let packageName =
swiftc(.packageName)
static let parseAsLibrary =
swiftc(.parseAsLibrary)
static let parseStdlib =
swiftc(.parseStdlib)
static let runtimeCompatibilityVersion =
swiftc(.runtimeCompatibilityVersion)
static let sdk =
swiftc(.sdk)
static let swiftVersion =
swiftc(.swiftVersion)
static let wholeModuleOptimization =
swiftc(.wholeModuleOptimization)
static let wmo =
swiftc(.wmo)
static let Xcc =
swiftc(.Xcc)
static let Xfrontend =
swiftc(.Xfrontend)
static let Xllvm =
swiftc(.Xllvm)
}
extension KnownCommand {
private static let clangSpec = Command.FlagSpec([
.init(.I, option: .equals, .unspaced, .spaced),
.init(.D, option: .unspaced, .spaced),
.init(.U, option: .unspaced, .spaced),
.init(.W, option: .unspaced),
// This isn't an actual Clang flag, but it allows us to scoop up all the
// -f[...] flags.
// FIXME: We ought to see if we can get away with preserving unknown flags.
.init(.f, option: .unspaced),
// FIXME: Really we ought to map to Xcode's SDK
.init(.isystem, option: .unspaced, .spaced),
.init(.isysroot, option: .unspaced, .spaced),
.init(.std, option: .equals),
.init(.target, option: .spaced),
])
// FIXME: We currently only parse a small subset of the supported driver
// options. This is because:
//
// - It avoids including incompatible options (e.g options only suitable when
// emitting modules when we want to do a regular build).
// - It avoids including options that produce unnecessary outputs (e.g
// dependencies, object files), especially as they would be outputting into
// the Ninja build, which needs to be left untouched (maybe we could filter
// out options that have paths that point into the build dir?).
// - It avoids including options that do unnecessary work (e.g emitting debug
// info, code coverage).
// - It's quicker.
//
// This isn't great though, and we probably ought to revisit this, especially
// if the driver starts categorizing its options such that we can better
// reason about which we want to use. It should also be noted that we
// currently allow arbitrary options to be passed through -Xfrontend, we may
// want to reconsider that.
// NOTE: You can pass '--log-level debug' to see the options that are
// currently being missed.
private static let swiftOptions: [SwiftOptions.Option] = [
.cxxInteroperabilityMode,
.D,
.disableAutolinkingRuntimeCompatibilityDynamicReplacements,
.enableBuiltinModule,
.enableExperimentalCxxInterop,
.enableExperimentalFeature,
.enableLibraryEvolution,
.experimentalSkipAllFunctionBodies,
.experimentalSkipNonInlinableFunctionBodies,
.experimentalSkipNonInlinableFunctionBodiesWithoutTypes,
.F,
.Fsystem,
.I,
.nostdimport,
.O,
.Onone,
.moduleName,
.moduleLinkName,
.packageName,
.parseAsLibrary,
.parseStdlib,
.runtimeCompatibilityVersion,
.target,
.sdk,
.swiftVersion,
.wholeModuleOptimization,
.wmo,
.Xcc,
.Xfrontend,
.Xllvm,
]
private static let swiftcSpec = Command.FlagSpec(
swiftOptions.filter { !$0.attributes.contains(.noDriver) } + [
// Not currently listed as a driver option, but it used to be. Include
// for better compatibility.
.enableExperimentalCxxInterop
]
)
private static let swiftFrontendSpec = Command.FlagSpec(
swiftOptions.filter { $0.attributes.contains(.frontend) }
)
var flagSpec: Command.FlagSpec {
switch self {
case .clang:
Self.clangSpec
case .swiftc:
Self.swiftcSpec
case .swiftFrontend:
Self.swiftFrontendSpec
}
}
}