diff --git a/Generator/Project.swift b/Generator/Project.swift index 446dab75..e31d2701 100644 --- a/Generator/Project.swift +++ b/Generator/Project.swift @@ -14,6 +14,7 @@ let target = Target.target( "Stencil", "SwiftSyntax", "SwiftParser", + "SwiftIfConfig", "ArgumentParser", "TOMLKit", "XcodeProj", diff --git a/Generator/Sources/CLI/Generator.swift b/Generator/Sources/CLI/Generator.swift index 15429972..e6a006a7 100644 --- a/Generator/Sources/CLI/Generator.swift +++ b/Generator/Sources/CLI/Generator.swift @@ -32,10 +32,11 @@ final class Generator { .filter { $0.exists } .map(TextFile.init(path:)) + let buildConfiguration = CuckooGeneratorBuildConfiguration(customConditions: module.buildConditions) let files: [FileRepresentation] = await inputFiles.concurrentCompactMap { file in do { log(.verbose, message: "Processing file: \(file.path)") - let crawler = try Crawler.crawl(url: file.path.url) + let crawler = try Crawler.crawl(url: file.path.url, buildConfiguration: buildConfiguration) log(.verbose, message: "Successfully processed file: \(file.path)") return FileRepresentation(file: file, imports: crawler.imports, tokens: crawler.tokens) } catch { diff --git a/Generator/Sources/CLI/Module.swift b/Generator/Sources/CLI/Module.swift index c1221114..78dd7fe6 100644 --- a/Generator/Sources/CLI/Module.swift +++ b/Generator/Sources/CLI/Module.swift @@ -15,6 +15,7 @@ final class Module: @unchecked Sendable { let filenameFormat: String? let options: Options let xcodeproj: Xcodeproj? + let buildConditions: Set init(name: String, output: String?, configurationPath: Path, dto: DTO) throws { guard Module.overriddenOutput != nil || output != nil || dto.output != nil else { @@ -39,6 +40,7 @@ final class Module: @unchecked Sendable { protocolsOnly: dto.options?.protocolsOnly ?? false, omitHeaders: dto.options?.omitHeaders ?? false ) + self.buildConditions = Set(dto.buildConditions ?? []) if let xcodeproj = dto.xcodeproj { if let target = xcodeproj.target { @@ -103,6 +105,7 @@ extension Module { let filenameFormat: String? let options: Options? let xcodeproj: Xcodeproj? + let buildConditions: [String]? struct Options: Decodable { let glob: Bool? @@ -132,6 +135,7 @@ extension Module: CustomDebugStringConvertible { filenameFormat.map { "filename format: \($0.bold)" }, "options:\(options.debugDescription.components(separatedBy: "\n").map { "\n\t- \($0)" }.joined())", xcodeproj.map { "xcodeproj:\($0.debugDescription.components(separatedBy: "\n").map { "\n\t- \($0)" }.joined())" }, + buildConditions.isEmpty ? nil : "build conditions:\(buildConditions.sorted().map { "\n\t-\($0.bold)" }.joined())", ] .compactMap { $0 } .joined(separator: "\n") diff --git a/Generator/Sources/Internal/Crawlers/Crawler.swift b/Generator/Sources/Internal/Crawlers/Crawler.swift index 5e43bc3c..f0ebb34a 100644 --- a/Generator/Sources/Internal/Crawlers/Crawler.swift +++ b/Generator/Sources/Internal/Crawlers/Crawler.swift @@ -1,9 +1,10 @@ import Foundation import SwiftSyntax import SwiftParser +import SwiftIfConfig final class Crawler: SyntaxVisitor { - static func crawl(url: URL) throws -> Crawler { + static func crawl(url: URL, buildConfiguration: CuckooGeneratorBuildConfiguration = .init(customConditions: [])) throws -> Crawler { let file = try String(contentsOf: url) let syntaxTree = Parser.parse(source: file) #if DEBUG @@ -11,7 +12,7 @@ final class Crawler: SyntaxVisitor { // The `testString` is at the bottom of this file. // let syntaxTree = Parser.parse(source: testString) #endif - let crawler = Self(container: nil, url: url) + let crawler = Self(container: nil, url: url, buildConfiguration: buildConfiguration) crawler.walk(syntaxTree) return crawler } @@ -22,10 +23,12 @@ final class Crawler: SyntaxVisitor { private var container: Reference? private let url: URL + private let buildConfiguration: CuckooGeneratorBuildConfiguration - private init(container: Reference?, url: URL) { + private init(container: Reference?, url: URL, buildConfiguration: CuckooGeneratorBuildConfiguration) { self.container = container self.url = url + self.buildConfiguration = buildConfiguration super.init(viewMode: .sourceAccurate) } @@ -61,7 +64,7 @@ final class Crawler: SyntaxVisitor { guard token.accessibility.isAccessible else { return .skipChildren } - let crawler = Crawler(container: Reference(token), url: url) + let crawler = Crawler(container: Reference(token), url: url, buildConfiguration: buildConfiguration) crawler.walk(members: node.memberBlock) token.members = crawler.tokens tokens.append(token) @@ -85,7 +88,7 @@ final class Crawler: SyntaxVisitor { guard token.accessibility.isAccessible else { return .skipChildren } - let crawler = Crawler(container: Reference(token), url: url) + let crawler = Crawler(container: Reference(token), url: url, buildConfiguration: buildConfiguration) crawler.walk(members: node.memberBlock) token.members = crawler.tokens tokens.append(token) @@ -105,7 +108,7 @@ final class Crawler: SyntaxVisitor { guard token.accessibility.isAccessible else { return .skipChildren } - let crawler = Crawler(container: Reference(token), url: url) + let crawler = Crawler(container: Reference(token), url: url, buildConfiguration: buildConfiguration) crawler.walk(members: node.memberBlock) token.members = crawler.tokens tokens.append(token) @@ -125,7 +128,7 @@ final class Crawler: SyntaxVisitor { guard token.accessibility.isAccessible else { return .skipChildren } - let crawler = Crawler(container: Reference(token), url: url) + let crawler = Crawler(container: Reference(token), url: url, buildConfiguration: buildConfiguration) crawler.walk(members: node.memberBlock) token.members = crawler.tokens tokens.append(token) @@ -145,7 +148,7 @@ final class Crawler: SyntaxVisitor { guard token.accessibility.isAccessible else { return .skipChildren } - let crawler = Crawler(container: Reference(token), url: url) + let crawler = Crawler(container: Reference(token), url: url, buildConfiguration: buildConfiguration) crawler.walk(members: node.memberBlock) token.members = crawler.tokens tokens.append(token) @@ -178,8 +181,21 @@ final class Crawler: SyntaxVisitor { } override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind { - // TODO: Implement #if functionality. - return .visitChildren + let (activeClause, _) = node.activeClause(in: buildConfiguration) + guard let elements = activeClause?.elements else { return .skipChildren } + switch elements { + case .decls(let memberList): + for member in memberList { + walk(member) + } + case .statements(let stmtList): + for stmt in stmtList { + walk(stmt) + } + default: + break + } + return .skipChildren } override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { diff --git a/Generator/Sources/Internal/Crawlers/CuckooGeneratorBuildConfiguration.swift b/Generator/Sources/Internal/Crawlers/CuckooGeneratorBuildConfiguration.swift new file mode 100644 index 00000000..f8140c43 --- /dev/null +++ b/Generator/Sources/Internal/Crawlers/CuckooGeneratorBuildConfiguration.swift @@ -0,0 +1,55 @@ +import SwiftIfConfig +import SwiftSyntax + +/// A BuildConfiguration implementation for the Cuckoo generator that evaluates `#if` conditions +/// based on user-supplied custom conditions and sensible defaults for known platform/compiler queries. +struct CuckooGeneratorBuildConfiguration: BuildConfiguration { + /// Custom conditions set via `-D` flags (e.g. `-D DEBUG`). + let customConditions: Set + + func isCustomConditionSet(name: String) throws -> Bool { + customConditions.contains(name) + } + + func hasFeature(name: String) throws -> Bool { + false + } + + func hasAttribute(name: String) throws -> Bool { + false + } + + func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool { + false + } + + func isActiveTargetOS(name: String) throws -> Bool { + false + } + + func isActiveTargetArchitecture(name: String) throws -> Bool { + false + } + + func isActiveTargetEnvironment(name: String) throws -> Bool { + false + } + + func isActiveTargetRuntime(name: String) throws -> Bool { + false + } + + func isActiveTargetPointerAuthentication(name: String) throws -> Bool { + false + } + + var targetPointerBitWidth: Int { 64 } + + var targetAtomicBitWidths: [Int] { [32, 64] } + + var endianness: Endianness { .little } + + var languageVersion: VersionTuple { VersionTuple(6, 0) } + + var compilerVersion: VersionTuple { VersionTuple(6, 0) } +} diff --git a/Package.swift b/Package.swift index 46ba7fdc..e44d00d3 100644 --- a/Package.swift +++ b/Package.swift @@ -58,6 +58,7 @@ let package = Package( // .product(name: "SwiftFormat", package: "swift-format"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftIfConfig", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "TOMLKit", package: "TOMLKit"), .product(name: "XcodeProj", package: "XcodeProj"),