Skip to content

Commit

Permalink
[Swift Explain] Add a simple tool to break down and annotate Swift co…
Browse files Browse the repository at this point in the history
…mpile commands
  • Loading branch information
artemcm committed Aug 1, 2024
1 parent 598e3e2 commit befaefb
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 5 deletions.
12 changes: 12 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ let package = Package(
.executable(
name: "swift-help",
targets: ["swift-help"]),
.executable(
name: "swift-explain",
targets: ["swift-explain"]),
.executable(
name: "swift-build-sdk-interfaces",
targets: ["swift-build-sdk-interfaces"]),
Expand Down Expand Up @@ -120,6 +123,15 @@ let package = Package(
],
exclude: ["CMakeLists.txt"]),

/// The explain executable
.executableTarget(
name: "swift-explain",
dependencies: [
"SwiftOptions", "SwiftDriver",
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
],
exclude: ["CMakeLists.txt"]),

/// The help executable.
.executableTarget(
name: "swift-build-sdk-interfaces",
Expand Down
1 change: 1 addition & 0 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_subdirectory(SwiftDriverExecution)
add_subdirectory(swift-build-sdk-interfaces)
add_subdirectory(swift-driver)
add_subdirectory(swift-help)
add_subdirectory(swift-explain)

if(SWIFT_DRIVER_BUILD_TOOLS)
add_subdirectory(makeOptions)
Expand Down
17 changes: 13 additions & 4 deletions Sources/SwiftOptions/OptionParsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ extension OptionTable {
///
/// Throws an error if the command line contains any errors.
public func parse(_ arguments: [String],
for driverKind: DriverKind, delayThrows: Bool = false) throws -> ParsedOptions {
for driverKind: DriverKind,
delayThrows: Bool = false,
includeNoDriver: Bool = false) throws -> ParsedOptions {
var trie = PrefixTrie<Option>()
// Add all options, ignoring the .noDriver ones
for opt in options where !opt.attributes.contains(.noDriver) {
trie[opt.spelling] = opt
for opt in options {
if opt.attributes.contains(.noDriver) {
if includeNoDriver {
trie[opt.spelling] = opt
}
} else {
trie[opt.spelling] = opt
}
}

var parsedOptions = ParsedOptions()
Expand Down Expand Up @@ -91,7 +99,8 @@ extension OptionTable {

let verifyOptionIsAcceptedByDriverKind = {
// Make sure this option is supported by the current driver kind.
guard option.isAccepted(by: driverKind) else {
guard option.isAccepted(by: driverKind) ||
(option.attributes.contains(.noDriver) && includeNoDriver) else {
throw OptionParseError.unsupportedOption(
index: index - 1, argument: argument, option: option,
currentDriverKind: driverKind)
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftOptions/ParsedOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public struct ParsedOptions {
public typealias Argument = ParsedOption.Argument

/// The parsed options, which match up an option with its argument(s).
private var parsedOptions: [ParsedOption] = []
public var parsedOptions: [ParsedOption] = []

/// Maps the canonical spelling of an option to all the instances of
/// that option that we've seen in the map, and their index in the
Expand Down
17 changes: 17 additions & 0 deletions Sources/swift-explain/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_executable(swift-explain
main.swift)
target_link_libraries(swift-explain PUBLIC
SwiftOptions
ArgumentParser
TSCBasic)

install(TARGETS swift-explain
DESTINATION bin)
132 changes: 132 additions & 0 deletions Sources/swift-explain/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//===------------- main.swift - Swift Explain Main Entrypoint ------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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
//
//===----------------------------------------------------------------------===//

import SwiftOptions
import SwiftDriver
#if os(Windows)
import CRT
#elseif os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
import Darwin
#else
import Glibc
#endif
import var TSCBasic.localFileSystem
import class TSCBasic.DiagnosticsEngine

let diagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler])

func isToolPath(_ optionalString: String?) -> Bool {
guard let string = optionalString else { return false }
// ugh
return string.hasSuffix("swift-frontend") ||
string.hasSuffix("swiftc") ||
string.hasSuffix("clang") ||
string.hasSuffix("libtool") ||
string.hasSuffix("dsymutil") ||
string.hasSuffix("clang") ||
string.hasSuffix("clang++") ||
string.hasSuffix("swift-autolink-extract") ||
string.hasSuffix("lldb") ||
string.hasSuffix("dwarfdump") ||
string.hasSuffix("swift-help") ||
string.hasSuffix("swift-api-digester")
}

func printCommandIntro(firstArg: String? = nil) {
print("")
print("-- Explained command-line:")
}

// Partition all the arguments into potentially multiple commands
// starting with a path to an executable tool.
func partitionCommandLineIntoToolInvocations(_ args: [String]) -> [[String]] {
var commandLines: [[String]] = []
var currentCommandLine: [String] = []
for arg in args {
if isToolPath(arg) {
if !currentCommandLine.isEmpty {
commandLines.append(currentCommandLine)
}
currentCommandLine = [arg]
continue
}
currentCommandLine.append(arg)
}
commandLines.append(currentCommandLine)
return commandLines
}

do {
var args = CommandLine.arguments
guard args.count > 1 else {
exit(0)
}
args.removeFirst() // Path to swift-explain itself

let commandLines = partitionCommandLineIntoToolInvocations(args)
for commandLine in commandLines {
guard !commandLine.isEmpty else {
continue
}
// Print the intro and the path to the tool executable
printCommandIntro(firstArg: commandLine.first)
let arguments: [String]
if isToolPath(commandLine.first) {
print(commandLine[0])
arguments = Array(commandLine[1...])
} else {
arguments = commandLine
}

// Expand and parse the arguments
let expandedArgs =
try Driver.expandResponseFiles(Array(arguments),
fileSystem: localFileSystem,
diagnosticsEngine: diagnosticsEngine)
let optionTable = OptionTable()
let parsedOptions = try optionTable.parse(Array(expandedArgs),
for: .batch,
delayThrows: true,
includeNoDriver: true)
for opt in parsedOptions.parsedOptions {
switch opt.option.kind {
case .input:
let path = try VirtualPath(path: opt.description)
print(opt.description, terminator: "")
var padding = 80-opt.description.count
if padding < 0 {
padding = 0
}
print(String(repeating: " ", count: padding), terminator: "")
print(" # Input", terminator: "")
if let fileExtension = path.extension {
print(" (\(fileExtension.description))")
} else {
print()
}
default:
print(opt.description, terminator: "")
var padding = 80-opt.description.count
if padding < 0 {
padding = 0
}
print(String(repeating: " ", count: padding), terminator: "")
let helpText = opt.option.helpText ?? opt.option.alias?.helpText ?? "UNKNOWN"
print(" # \(helpText)")
}
}
print("")
}
} catch {
print("error: \(error)")
exit(1)
}

0 comments on commit befaefb

Please sign in to comment.