Files
swift-mirror/utils/swift-xcodegen/Sources/SwiftXcodeGen/Ninja/NinjaBuildFile.swift
Rintaro Ishizaki 2d64ef2528 [XcodeGen] Make 'Byte' comform 'ExpressibleByUnicodeScalarLiteral'
Instead of comparing with 'UnicodeScalar', construct 'Byte' from
literals and compare with them. Make it less evil :)
2025-01-28 21:43:15 -08:00

202 lines
5.0 KiB
Swift

//===--- NinjaBuildFile.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 NinjaBuildFile {
var bindings: Bindings
var rules: [String: Rule]
var buildEdges: [BuildEdge] = []
init(
bindings: [String: String],
rules: [String: Rule],
buildEdges: [BuildEdge]
) {
self.bindings = Bindings(storage: bindings)
self.buildEdges = buildEdges
self.rules = rules
}
}
extension NinjaBuildFile {
var buildConfiguration: BuildConfiguration? {
bindings[.configuration]
.flatMap { BuildConfiguration(rawValue: $0) }
}
}
extension NinjaBuildFile {
struct Bindings: Hashable {
let values: [String: String]
init(storage: [String : String]) {
self.values = storage
}
subscript(key: String) -> String? {
values[key]
}
}
struct Rule: Equatable {
let name: String
var bindings: Bindings
init(name: String, bindings: [String: String]) {
self.name = name
self.bindings = Bindings(storage: bindings)
}
}
struct BuildEdge: Hashable {
let ruleName: String
let inputs: [String]
let outputs: [String]
let dependencies: [String]
var bindings: Bindings
var isPhony: Bool {
ruleName == "phony"
}
init(
ruleName: String,
inputs: [String], outputs: [String], dependencies: [String],
bindings: [String: String]
) {
self.ruleName = ruleName
self.inputs = inputs
self.outputs = outputs
self.dependencies = dependencies
self.bindings = Bindings(storage: bindings)
}
static func phony(for outputs: [String], inputs: [String]) -> Self {
return Self(
ruleName: "phony", inputs: inputs, outputs: outputs, dependencies: [], bindings: [:]
)
}
}
}
fileprivate enum NinjaCommandLineError: Error {
case unknownRule(String)
case missingCommandBinding
}
extension NinjaBuildFile {
func commandLine(for edge: BuildEdge) throws -> String {
guard let rule = self.rules[edge.ruleName] else {
throw NinjaCommandLineError.unknownRule(edge.ruleName)
}
// Helper to get a substitution value for ${key}.
// Note that we don't do built-in substitutions (e.g. $in, $out) for now.
func value(for key: String) -> String? {
edge.bindings[key] ?? rule.bindings[key] ?? self.bindings[key]
}
func eval(string: String) -> String {
var result = ""
string.scanningUTF8 { scanner in
while scanner.hasInput {
if let prefix = scanner.eat(while: { $0 != "$" }) {
result += String(utf8: prefix)
}
guard scanner.tryEat("$") else {
// Reached the end.
break
}
let substituted: String? = scanner.tryEating { scanner in
// Parse the variable name.
let key: String
if scanner.tryEat("{"), let keyName = scanner.eat(while: { $0 != "}" }), scanner.tryEat("}") {
key = String(utf8: keyName)
} else if let keyName = scanner.eat(while: { $0.isNinjaVarName }) {
key = String(utf8: keyName)
} else {
return nil
}
return value(for: key)
}
if let substituted {
// Recursive substitutions.
result += eval(string: substituted)
} else {
// Was not a variable, restore '$' and move on.
result += "$"
}
}
}
return result
}
guard let commandLine = rule.bindings["command"] else {
throw NinjaCommandLineError.missingCommandBinding
}
return eval(string: commandLine)
}
}
extension Byte {
fileprivate var isNinjaVarName: Bool {
switch self {
case "0"..."9", "a"..."z", "A"..."Z", "_", "-":
return true
default:
return false
}
}
}
extension NinjaBuildFile: CustomDebugStringConvertible {
var debugDescription: String {
buildEdges.map(\.debugDescription).joined(separator: "\n")
}
}
extension NinjaBuildFile.BuildEdge: CustomDebugStringConvertible {
var debugDescription: String {
"""
{
inputs: \(inputs)
outputs: \(outputs)
dependencies: \(dependencies)
bindings: \(bindings)
isPhony: \(isPhony)
}
"""
}
}
extension NinjaBuildFile.Bindings {
enum Key: String {
case configuration = "CONFIGURATION"
case defines = "DEFINES"
case flags = "FLAGS"
case includes = "INCLUDES"
case swiftModule = "SWIFT_MODULE"
case swiftModuleName = "SWIFT_MODULE_NAME"
case swiftLibraryName = "SWIFT_LIBRARY_NAME"
case swiftSources = "SWIFT_SOURCES"
}
subscript(key: Key) -> String? {
return self[key.rawValue]
}
}