mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Instead of comparing with 'UnicodeScalar', construct 'Byte' from literals and compare with them. Make it less evil :)
202 lines
5.0 KiB
Swift
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]
|
|
}
|
|
}
|