Skip to content

Commit 602a222

Browse files
committed
Add help intro
Add an "intro" topic to swift-help, printing the version, welcome message, and available subcommands. When running `swift` by itself, show the intro before entering the REPL. When running `swift-help` directly or via `swift --help`, show all of the available flags as previously. rdar://78280423
1 parent 3f23cee commit 602a222

File tree

10 files changed

+155
-27
lines changed

10 files changed

+155
-27
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ let package = Package(
6262
.testTarget(
6363
name: "SwiftDriverTests",
6464
dependencies: ["SwiftDriver", "SwiftDriverExecution", "swift-driver",
65-
"TestUtilities"]),
65+
"TestUtilities", "swift-help"]),
6666

6767
/// IncrementalImport tests
6868
.testTarget(

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ add_library(SwiftDriver
7575
Jobs/Planning.swift
7676
Jobs/PrintTargetInfoJob.swift
7777
Jobs/ReplJob.swift
78+
Jobs/SwiftHelpIntroJob.swift
7879
Jobs/Toolchain+InterpreterSupport.swift
7980
Jobs/Toolchain+LinkerSupport.swift
8081
Jobs/VerifyDebugInfoJob.swift

Sources/SwiftDriver/Driver/CompilerMode.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232

3333
/// Dump information about a precompiled Clang module
3434
case dumpPCM
35+
36+
/// Introduce the user to Swift concepts depending on context.
37+
case intro
3538
}
3639

3740
/// Information about batch mode, which is used to determine how to form
@@ -46,7 +49,7 @@ extension CompilerMode {
4649
/// Whether this compilation mode uses -primary-file to specify its inputs.
4750
public var usesPrimaryFileInputs: Bool {
4851
switch self {
49-
case .immediate, .repl, .singleCompile, .compilePCM, .dumpPCM:
52+
case .immediate, .repl, .singleCompile, .compilePCM, .dumpPCM, .intro:
5053
return false
5154

5255
case .standardCompile, .batchCompile:
@@ -57,7 +60,7 @@ extension CompilerMode {
5760
/// Whether this compilation mode compiles the whole target in one job.
5861
public var isSingleCompilation: Bool {
5962
switch self {
60-
case .immediate, .repl, .standardCompile, .batchCompile:
63+
case .immediate, .repl, .standardCompile, .batchCompile, .intro:
6164
return false
6265

6366
case .singleCompile, .compilePCM, .dumpPCM:
@@ -67,7 +70,7 @@ extension CompilerMode {
6770

6871
public var isStandardCompilationForPlanning: Bool {
6972
switch self {
70-
case .immediate, .repl, .compilePCM, .dumpPCM:
73+
case .immediate, .repl, .compilePCM, .dumpPCM, .intro:
7174
return false
7275
case .batchCompile, .standardCompile, .singleCompile:
7376
return true
@@ -93,7 +96,7 @@ extension CompilerMode {
9396
switch self {
9497
case .batchCompile, .singleCompile, .standardCompile, .compilePCM, .dumpPCM:
9598
return true
96-
case .immediate, .repl:
99+
case .immediate, .repl, .intro:
97100
return false
98101
}
99102
}
@@ -116,6 +119,8 @@ extension CompilerMode: CustomStringConvertible {
116119
return "compile Clang module (.pcm)"
117120
case .dumpPCM:
118121
return "dump Clang module (.pcm)"
122+
case .intro:
123+
return "introduction to Swift and packages"
119124
}
120125
}
121126
}

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,7 @@ extension Driver {
804804
// If it is the "built-in" 'repl', then use the normal driver.
805805
if firstArg == "repl" {
806806
updatedArgs.remove(at: 1)
807+
updatedArgs.append("-repl")
807808
return (.normal(isRepl: true), updatedArgs)
808809
}
809810

@@ -1401,7 +1402,15 @@ extension Driver {
14011402
}
14021403

14031404
if driverKind == .interactive {
1404-
return parsedOptions.hasAnyInput ? .immediate : .repl
1405+
if parsedOptions.hasAnyInput {
1406+
return .immediate
1407+
} else {
1408+
if parsedOptions.contains(Option.repl) {
1409+
return .repl
1410+
} else {
1411+
return .intro
1412+
}
1413+
}
14051414
}
14061415

14071416
let useWMO = parsedOptions.hasFlag(positive: .wholeModuleOptimization, negative: .noWholeModuleOptimization, default: false)
@@ -1996,7 +2005,7 @@ extension Driver {
19962005
}
19972006

19982007
// The REPL and immediate mode do not support module output
1999-
if moduleOutputKind != nil && (compilerMode == .repl || compilerMode == .immediate) {
2008+
if moduleOutputKind != nil && (compilerMode == .repl || compilerMode == .immediate || compilerMode == .intro) {
20002009
diagnosticsEngine.emit(.error_mode_cannot_emit_module)
20012010
moduleOutputKind = nil
20022011
}
@@ -2006,7 +2015,9 @@ extension Driver {
20062015
var moduleNameIsFallback = false
20072016
if let arg = parsedOptions.getLastArgument(.moduleName) {
20082017
moduleName = arg.asSingle
2009-
} else if compilerMode == .repl {
2018+
} else if compilerMode == .repl || compilerMode == .intro {
2019+
// TODO: Remove the `.intro` check once the REPL no longer launches
2020+
// by default.
20102021
// REPL mode should always use the REPL module.
20112022
moduleName = "REPL"
20122023
} else if let outputArg = parsedOptions.getLastArgument(.o) {

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ fileprivate extension CompilerMode {
180180
var supportsIncrementalCompilation: Bool {
181181
switch self {
182182
case .standardCompile, .immediate, .repl, .batchCompile: return true
183-
case .singleCompile, .compilePCM, .dumpPCM: return false
183+
case .singleCompile, .compilePCM, .dumpPCM, .intro: return false
184184
}
185185
}
186186
}

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ extension Driver {
6464
commandLine.appendFlag(.target)
6565
commandLine.appendFlag(targetTriple.triple)
6666
}
67+
case .intro:
68+
break
6769
}
6870

6971
// Pass down -clang-target.
@@ -269,7 +271,7 @@ extension Driver {
269271
}
270272

271273
// Repl Jobs shouldn't include -module-name.
272-
if compilerMode != .repl {
274+
if compilerMode != .repl && compilerMode != .intro {
273275
commandLine.appendFlags("-module-name", moduleOutputInfo.name)
274276
}
275277

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ extension Driver {
668668
}
669669

670670
// The REPL doesn't require input files, but all other modes do.
671-
guard !inputFiles.isEmpty || compilerMode == .repl else {
671+
guard !inputFiles.isEmpty || compilerMode == .repl || compilerMode == .intro else {
672672
if parsedOptions.hasArgument(.v) {
673673
// `swiftc -v` is allowed and prints version information.
674674
return ([], nil)
@@ -704,6 +704,20 @@ extension Driver {
704704
throw PlanningError.dumpPCMWrongInputFiles
705705
}
706706
return ([try generateDumpPCMJob(input: inputFiles.first!)], nil)
707+
case .intro:
708+
// TODO: Remove this check once the REPL no longer launches
709+
// by default.
710+
if !inputFiles.isEmpty {
711+
throw PlanningError.replReceivedInput
712+
}
713+
// end TODO.
714+
715+
var introJobs = try helpIntroJobs()
716+
717+
// TODO: Remove this to stop launching the REPL by default.
718+
introJobs.append(try replJob())
719+
720+
return (introJobs, nil)
707721
}
708722
}
709723
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===--------------- SwiftHelpIntroJob.swift - Swift REPL -----------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
import SwiftOptions
13+
14+
extension Driver {
15+
mutating func helpIntroJobs() throws -> [Job] {
16+
return [
17+
Job(
18+
moduleName: moduleOutputInfo.name,
19+
kind: .versionRequest,
20+
tool: .absolute(try toolchain.getToolPath(.swiftCompiler)),
21+
commandLine: [.flag("--version")],
22+
inputs: [],
23+
primaryInputs: [],
24+
outputs: [],
25+
requiresInPlaceExecution: false),
26+
Job(
27+
moduleName: moduleOutputInfo.name,
28+
kind: .help,
29+
tool: .absolute(try toolchain.getToolPath(.swiftHelp)),
30+
commandLine: [.flag("intro")],
31+
inputs: [],
32+
primaryInputs: [],
33+
outputs: [],
34+
requiresInPlaceExecution: false
35+
),
36+
]
37+
}
38+
}

Sources/swift-help/main.swift

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ import ArgumentParser
1616
enum HelpTopic: ExpressibleByArgument, CustomStringConvertible {
1717
case driver(DriverKind)
1818
case subcommand(Subcommand)
19+
case intro
1920

2021
init?(argument topicName: String) {
2122
if let kind = DriverKind(rawValue: topicName) {
2223
self = .driver(kind)
2324
} else if let subcommand = Subcommand(rawValue: topicName) {
2425
self = .subcommand(subcommand)
26+
} else if topicName == "intro" {
27+
self = .intro
2528
} else {
2629
return nil
2730
}
@@ -33,6 +36,8 @@ enum HelpTopic: ExpressibleByArgument, CustomStringConvertible {
3336
return kind.rawValue
3437
case .subcommand(let command):
3538
return command.rawValue
39+
case .intro:
40+
return "intro"
3641
}
3742
}
3843
}
@@ -43,13 +48,13 @@ enum Subcommand: String, CaseIterable {
4348
var description: String {
4449
switch self {
4550
case .build:
46-
return "SwiftPM - Build sources into binary products"
51+
return "Build Swift packages"
4752
case .package:
48-
return "SwiftPM - Perform operations on Swift packages"
53+
return "Create and work on packages"
4954
case .run:
50-
return "SwiftPM - Build and run an executable product"
55+
return "Run a program from a package"
5156
case .test:
52-
return "SwiftPM - Build and run tests"
57+
return "Run package tests"
5358
}
5459
}
5560
}
@@ -65,19 +70,60 @@ struct SwiftHelp: ParsableCommand {
6570
help: "List hidden (unsupported) options")
6671
var showHidden: Bool = false
6772

73+
enum Color256: CustomStringConvertible {
74+
case reset
75+
case color(foreground: UInt8?, background: UInt8?)
76+
77+
var description: String {
78+
switch self {
79+
case .reset:
80+
return "\u{001B}[0m"
81+
case let .color(foreground, background):
82+
let foreground = foreground.map { "\u{001B}[38;5;\($0)m" } ?? ""
83+
let background = background.map { "\u{001B}[48;5;\($0)m" } ?? ""
84+
return foreground + background
85+
}
86+
}
87+
}
88+
89+
func printIntro() {
90+
let is256Color = ProcessEnv.vars["TERM"] == "xterm-256color"
91+
let orangeRed = is256Color ? "\u{001b}[1;38;5;196m" : ""
92+
let plain = is256Color ? "\u{001b}[0m" : ""
93+
let plainBold = is256Color ? "\u{001b}[1m" : ""
94+
95+
print("""
96+
97+
\(orangeRed)Welcome to Swift!\(plain)
98+
99+
\(plainBold)Subcommands:\(plain)
100+
101+
""")
102+
103+
let maxSubcommandNameLength = Subcommand.allCases.map { $0.rawValue.count }.max()!
104+
105+
for command in Subcommand.allCases {
106+
let padding = String(repeating: " ", count: maxSubcommandNameLength - command.rawValue.count)
107+
print(" \(plainBold)swift \(command.rawValue)\(plain)\(padding) \(command.description)")
108+
}
109+
110+
// `repl` not included in `Subcommand`, also print it here.
111+
do {
112+
let padding = String(repeating: " ", count: maxSubcommandNameLength - "repl".count)
113+
print(" \(plainBold)swift repl\(plain)\(padding) Experiment with Swift code interactively (default)")
114+
}
115+
116+
print("\n Use \(plainBold)`swift --help`\(plain) for descriptions of available options and flags.")
117+
print("\n Use \(plainBold)`swift help <subcommand>`\(plain) for more information about a subcommand.")
118+
print()
119+
}
120+
68121
func run() throws {
69122
let driverOptionTable = OptionTable()
70123
switch topic {
71124
case .driver(let kind):
72125
driverOptionTable.printHelp(driverKind: kind, includeHidden: showHidden)
73-
print("\nSUBCOMMANDS (swift <subcommand> [arguments]):")
74-
let maxSubcommandNameLength = Subcommand.allCases.map(\.rawValue.count).max()!
75-
for subcommand in Subcommand.allCases {
76-
let padding = String(repeating: " ", count: maxSubcommandNameLength - subcommand.rawValue.count)
77-
print(" \(subcommand.rawValue):\(padding) \(subcommand.description)")
78-
}
79-
print("\n Use \"swift help <subcommand>\" for more information about a subcommand")
80-
126+
printIntro()
81127
case .subcommand(let subcommand):
82128
// Try to find the subcommand adjacent to the help tool.
83129
// If we didn't find the tool there, let the OS search for it.
@@ -98,6 +144,8 @@ struct SwiftHelp: ParsableCommand {
98144
} else {
99145
try exec(path: path.pathString, args: [execName, "help"] + subtopics)
100146
}
147+
case .intro:
148+
printIntro()
101149
}
102150
}
103151
}

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ final class SwiftDriverTests: XCTestCase {
6060

6161
let driver5 = try Driver.invocationRunMode(forArgs: ["swift", "repl"])
6262
XCTAssertEqual(driver5.mode, .normal(isRepl: true))
63-
XCTAssertEqual(driver5.args, ["swift"])
63+
XCTAssertEqual(driver5.args, ["swift", "-repl"])
6464

6565
let driver6 = try Driver.invocationRunMode(forArgs: ["swift", "foo", "bar"])
6666
XCTAssertEqual(driver6.mode, .subcommand("swift-foo"))
@@ -125,7 +125,7 @@ final class SwiftDriverTests: XCTestCase {
125125
XCTAssertEqual(driver1.compilerMode, .immediate)
126126

127127
let driver2 = try Driver(args: ["swift"])
128-
XCTAssertEqual(driver2.compilerMode, .repl)
128+
XCTAssertEqual(driver2.compilerMode, .intro)
129129
}
130130

131131
do {
@@ -2295,8 +2295,17 @@ final class SwiftDriverTests: XCTestCase {
22952295
do {
22962296
var driver = try Driver(args: ["swift"])
22972297
let plannedJobs = try driver.planBuild()
2298-
XCTAssertEqual(plannedJobs.count, 1)
2299-
let replJob = plannedJobs.first!
2298+
XCTAssertEqual(plannedJobs.count, 3)
2299+
2300+
let versionJob = plannedJobs[0]
2301+
XCTAssertTrue(versionJob.tool.name.contains("swift"))
2302+
XCTAssertTrue(versionJob.commandLine.contains(.flag("--version")))
2303+
2304+
let helpJob = plannedJobs[1]
2305+
XCTAssertTrue(helpJob.tool.name.contains("swift-help"))
2306+
XCTAssertTrue(helpJob.commandLine.contains(.flag("intro")))
2307+
2308+
let replJob = plannedJobs[2]
23002309
XCTAssertTrue(replJob.tool.name.contains("lldb"))
23012310
XCTAssertTrue(replJob.requiresInPlaceExecution)
23022311
XCTAssert(replJob.commandLine.contains(where: { isExpectedLLDBREPLFlag($0) }))

0 commit comments

Comments
 (0)