1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
import ArgumentParser
import Foundation
import WIT
import WITExtractor
import WITOverlayGenerator
@main
struct WITTool: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "wit-tool",
abstract: "WIT-related tool set",
subcommands: [Validate.self, GenerateOverlay.self, ExtractWIT.self]
)
/// Create a semantics analysis context by loading the given `wit` directory or `.wit` file path.
static func deriveSemanticsContext(path: String, loader: LocalFileLoader) throws -> SemanticsContext {
let packageResolver: PackageResolver
let mainPackage: PackageUnit
if FileManager.default.isDirectory(filePath: path) {
(mainPackage, packageResolver) = try PackageResolver.parse(
directory: path, loader: loader
)
} else {
packageResolver = PackageResolver()
let sourceFile = try SourceFileSyntax.parse(filePath: path, loader: loader)
mainPackage = try packageResolver.register(packageSources: [sourceFile])
}
return SemanticsContext(rootPackage: mainPackage, packageResolver: packageResolver)
}
}
struct Validate: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Validate a WIT package"
)
@Argument
var path: String
func run() throws {
let loader = LocalFileLoader()
let context = try WITTool.deriveSemanticsContext(path: path, loader: loader)
let diagnostics = try context.validate(package: context.rootPackage)
for (fileName, diagnostics) in diagnostics {
let sourceContent = try loader.contentsOfWITFile(at: fileName)
for diagnostic in diagnostics {
guard let (line, column) = diagnostic.location(sourceContent) else {
print("\(fileName): error: \(diagnostic.message)")
continue
}
print("\(fileName):\(line):\(column): error: \(diagnostic.message)")
}
}
}
}
struct GenerateOverlay: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Generate a Swift overlay from a WIT package"
)
enum Target: String, ExpressibleByArgument {
case guest
case host
}
@Option
var target: Target
@Argument
var path: String
@Option(name: .shortAndLong)
var output: String = "-"
func run() throws {
let loader = LocalFileLoader()
let context = try WITTool.deriveSemanticsContext(path: path, loader: loader)
let contents: String
switch target {
case .guest:
contents = try WITOverlayGenerator.generateGuest(context: context)
case .host:
contents = try WITOverlayGenerator.generateHost(context: context)
}
if output == "-" {
print(contents)
} else {
try FileManager.default.createDirectory(
at: URL(fileURLWithPath: output).deletingLastPathComponent(),
withIntermediateDirectories: true
)
try contents.write(toFile: output, atomically: true, encoding: .utf8)
}
}
}
struct ExtractWIT: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "extract-wit"
)
@Option(name: .customLong("swift-api-digester"))
var digesterPath: String
@Option
var moduleName: String
@Option
var namespace: String = "swift"
@Option
var packageName: String
@Option
var witOutputPath: String
@Option
var swiftOutputPath: String
@Argument(parsing: .captureForPassthrough)
var digesterArgs: [String] = []
func run() throws {
let extractor = WITExtractor(
namespace: namespace,
packageName: packageName,
digesterPath: digesterPath,
extraDigesterArguments: digesterArgs
)
let output = try extractor.run(moduleName: moduleName)
try output.witContents.write(toFile: witOutputPath, atomically: true, encoding: .utf8)
for diagnostic in extractor.diagnostics {
try FileHandle.standardError.write(contentsOf: Data((diagnostic.description + "\n").utf8))
}
// Generate overlay shim to export extracted WIT interface
do {
let sourceFile = try SourceFileSyntax.parse(
output.witContents,
fileName: "<extracted>.wit"
)
let packageResolver = PackageResolver()
let packageUnit = try packageResolver.register(packageSources: [sourceFile])
let context = SemanticsContext(rootPackage: packageUnit, packageResolver: packageResolver)
let (interface, _) = try context.lookupInterface(name: output.interfaceName, contextPackage: packageUnit)
let swiftSource = try generateGuestExportInterface(
context: context,
sourceFile: sourceFile,
interface: interface,
sourceSummaryProvider: SwiftSourceSummaryProvider(
summary: output.sourceSummary,
typeMapping: output.typeMapping
)
)
try swiftSource.write(toFile: swiftOutputPath, atomically: true, encoding: .utf8)
}
}
private func writeFile(_ filePath: String, contents: String) throws {
try contents.write(toFile: filePath, atomically: true, encoding: .utf8)
}
}
struct SwiftSourceSummaryProvider: SourceSummaryProvider {
let summary: SwiftSourceSummary
let typeMapping: (String) -> String?
func enumCaseNames(byWITName witName: String) -> [String]? {
guard case let .enumType(enumType) = summary.lookupType(byWITName: witName) else {
return nil
}
return enumType.cases.map(\.name)
}
public func qualifiedSwiftTypeName(byWITName witName: String) -> String? {
typeMapping(witName)
}
}
|