Skip to content

Commit d877b72

Browse files
Recipe: Add make-linux-sdk subcommand (#70)
This splits out Linux-specific CLI options into `make-linux-sdk` subcommand, but keeps backwards compatibility by specifying it as the default subcommand. ```console $ swift-sdk-generator make-linux-sdk --verbose --with-docker ... $ swift-sdk-generator --verbose --with-docker ... # backward compatible deprecated: Please explicity specify the subcommand to run. For example: $ swift-sdk-generator make-linux-sdk ``` There were two options in my mind to express recipe-specific options in CLI. The other one was something like below: ``` # Accept recipe options from trailing arguments $ swift-sdk-generator --verbose --target-arch aarch64 -- --with-docker ... ``` But this style exceeds the swift-argument-parser's power and makes the help message slightly odd, so I chose the subcommand style.
1 parent 82189ad commit d877b72

File tree

2 files changed

+157
-111
lines changed

2 files changed

+157
-111
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ need CMake and Ninja preinstalled (e.g. via `brew install cmake ninja`).
5050
Clone this repository into a directory of your choice and make it the current directory. Build and run it with this command:
5151

5252
```
53-
swift run swift-sdk-generator
53+
swift run swift-sdk-generator make-linux-sdk
5454
```
5555

5656
This will download required components and produce a Swift SDK for Ubuntu Jammy in the `Bundles` subdirectory. Follow the steps
@@ -60,7 +60,7 @@ Additional command-line options are available for specifying target platform fea
6060
version, and target CPU architecture. Pass `--help` flag to see all of the available options:
6161

6262
```
63-
swift run swift-sdk-generator --help
63+
swift run swift-sdk-generator make-linux-sdk --help
6464
```
6565

6666
After installing a Swift SDK, verify that it's available to SwiftPM:
@@ -111,22 +111,22 @@ You can base your SDK on a container image, such as one of the
111111
default, the command below will build an SDK based on the Ubuntu
112112
Jammy image:
113113
```
114-
swift run swift-sdk-generator --with-docker
114+
swift run swift-sdk-generator make-linux-sdk --with-docker
115115
```
116116
To build a RHEL images, use the `--linux-distribution-name` option.
117117
The following command will build a `ubi9`-based image:
118118
```
119-
swift run swift-sdk-generator --with-docker --linux-distribution-name rhel
119+
swift run swift-sdk-generator make-linux-sdk --with-docker --linux-distribution-name rhel
120120
```
121121

122122
You can also specify the base container image by name:
123123

124124
```
125-
swift run swift-sdk-generator --with-docker --from-container-image swift:5.9-jammy
125+
swift run swift-sdk-generator make-linux-sdk --with-docker --from-container-image swift:5.9-jammy
126126
```
127127

128128
```
129-
swift run swift-sdk-generator --with-docker --linux-distribution-name rhel --from-container-image swift:5.9-rhel-ubi9
129+
swift run swift-sdk-generator make-linux-sdk --with-docker --linux-distribution-name rhel --from-container-image swift:5.9-rhel-ubi9
130130
```
131131

132132
### Including extra Linux libraries
@@ -151,7 +151,7 @@ docker build -t my-custom-image -f Dockerfile .
151151

152152
Finally, build your custom SDK:
153153
```
154-
swift run swift-sdk-generator --with-docker --from-container-image my-custom-image:latest --sdk-name 5.9-ubuntu-with-sqlite
154+
swift run swift-sdk-generator make-linux-sdk --with-docker --from-container-image my-custom-image:latest --sdk-name 5.9-ubuntu-with-sqlite
155155
```
156156

157157
## Swift SDK distribution

Sources/GeneratorCLI/GeneratorCLI.swift

Lines changed: 150 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -17,117 +17,24 @@ import SwiftSDKGenerator
1717

1818
@main
1919
struct GeneratorCLI: AsyncParsableCommand {
20-
static let configuration = CommandConfiguration(commandName: "swift-sdk-generator")
21-
22-
@Option(help: "An arbitrary version number for informational purposes.")
23-
var bundleVersion = "0.0.1"
24-
25-
@Flag(help: "Delegate to Docker for copying files for the target triple.")
26-
var withDocker: Bool = false
27-
28-
@Option(help: "Container image from which to copy the target triple.")
29-
var fromContainerImage: String? = nil
30-
31-
@Option(
32-
help: """
33-
Name of the SDK bundle. Defaults to a string composed of Swift version, Linux distribution, Linux release \
34-
and target CPU architecture.
35-
"""
36-
)
37-
var sdkName: String? = nil
38-
39-
@Flag(
40-
help: "Experimental: avoid cleaning up toolchain and SDK directories and regenerate the SDK bundle incrementally."
41-
)
42-
var incremental: Bool = false
43-
44-
@Flag(name: .shortAndLong, help: "Provide verbose logging output.")
45-
var verbose = false
46-
47-
@Option(
48-
help: """
49-
Branch of Swift to use when downloading nightly snapshots. Specify `development` for snapshots off the `main` \
50-
branch of Swift open source project repositories.
51-
"""
52-
)
53-
var swiftBranch: String? = nil
54-
55-
@Option(help: "Version of Swift to supply in the bundle.")
56-
var swiftVersion = "5.9.2-RELEASE"
57-
58-
@Option(help: "Version of LLD linker to supply in the bundle.")
59-
var lldVersion = "17.0.5"
60-
61-
@Option(
62-
help: """
63-
Linux distribution to use if the target platform is Linux. Available options: `ubuntu`, `rhel`. Default is `ubuntu`.
64-
""",
65-
transform: LinuxDistribution.Name.init(nameString:)
66-
)
67-
var linuxDistributionName = LinuxDistribution.Name.ubuntu
68-
69-
@Option(
70-
help: """
71-
Version of the Linux distribution used as a target platform. Available options for Ubuntu: `20.04`, \
72-
`22.04` (default when `--linux-distribution-name` is `ubuntu`). Available options for RHEL: `ubi9` (default when \
73-
`--linux-distribution-name` is `rhel`).
74-
"""
75-
)
76-
var linuxDistributionVersion: String?
77-
78-
@Option(
79-
help: """
80-
CPU architecture of the host triple of the bundle. Defaults to a triple of the machine this generator is \
81-
running on if unspecified. Available options: \(
82-
Triple.CPU.allCases.map { "`\($0.rawValue)`" }.joined(separator: ", ")
83-
).
84-
"""
20+
static let configuration = CommandConfiguration(
21+
commandName: "swift-sdk-generator",
22+
subcommands: [MakeLinuxSDK.self],
23+
defaultSubcommand: MakeLinuxSDK.self
8524
)
86-
var hostArch: Triple.CPU? = nil
87-
88-
@Option(
89-
help: """
90-
CPU architecture of the target triple of the bundle. Same as the host triple CPU architecture if unspecified. \
91-
Available options: \(Triple.CPU.allCases.map { "`\($0.rawValue)`" }.joined(separator: ", ")).
92-
"""
93-
)
94-
var targetArch: Triple.CPU? = nil
95-
96-
func run() async throws {
97-
let linuxDistributionDefaultVersion = switch self.linuxDistributionName {
98-
case .rhel:
99-
"ubi9"
100-
case .ubuntu:
101-
"22.04"
102-
}
103-
let linuxDistributionVersion = self.linuxDistributionVersion ?? linuxDistributionDefaultVersion
104-
let linuxDistribution = try LinuxDistribution(name: linuxDistributionName, version: linuxDistributionVersion)
10525

26+
static func run<Recipe: SwiftSDKRecipe>(recipe: Recipe, options: GeneratorOptions) async throws {
10627
let elapsed = try await ContinuousClock().measure {
10728
let logger = Logger(label: "org.swift.swift-sdk-generator")
108-
let hostTriple = try await SwiftSDKGenerator.getHostTriple(explicitArch: hostArch, isVerbose: verbose)
109-
let targetTriple = Triple(
110-
cpu: targetArch ?? hostTriple.cpu,
111-
vendor: .unknown,
112-
os: .linux,
113-
environment: .gnu
114-
)
115-
let recipe = try LinuxRecipe(
116-
targetTriple: targetTriple,
117-
linuxDistribution: linuxDistribution,
118-
swiftVersion: swiftVersion,
119-
swiftBranch: swiftBranch,
120-
lldVersion: lldVersion,
121-
withDocker: withDocker,
122-
fromContainerImage: fromContainerImage
123-
)
29+
30+
let (hostTriple, targetTriple) = try await options.deriveTriples()
12431
let generator = try await SwiftSDKGenerator(
125-
bundleVersion: self.bundleVersion,
32+
bundleVersion: options.bundleVersion,
12633
hostTriple: hostTriple,
12734
targetTriple: targetTriple,
128-
artifactID: self.sdkName ?? recipe.defaultArtifactID,
129-
isIncremental: self.incremental,
130-
isVerbose: self.verbose,
35+
artifactID: options.sdkName ?? recipe.defaultArtifactID,
36+
isIncremental: options.incremental,
37+
isVerbose: options.verbose,
13138
logger: logger
13239
)
13340

@@ -151,6 +58,145 @@ struct GeneratorCLI: AsyncParsableCommand {
15158

15259
extension Triple.CPU: ExpressibleByArgument {}
15360

61+
extension GeneratorCLI {
62+
struct GeneratorOptions: ParsableArguments {
63+
@Option(help: "An arbitrary version number for informational purposes.")
64+
var bundleVersion = "0.0.1"
65+
66+
@Option(
67+
help: """
68+
Name of the SDK bundle. Defaults to a string composed of Swift version, Linux distribution, Linux release \
69+
and target CPU architecture.
70+
"""
71+
)
72+
var sdkName: String? = nil
73+
74+
@Flag(
75+
help: "Experimental: avoid cleaning up toolchain and SDK directories and regenerate the SDK bundle incrementally."
76+
)
77+
var incremental: Bool = false
78+
79+
@Flag(name: .shortAndLong, help: "Provide verbose logging output.")
80+
var verbose = false
81+
82+
@Option(
83+
help: """
84+
Branch of Swift to use when downloading nightly snapshots. Specify `development` for snapshots off the `main` \
85+
branch of Swift open source project repositories.
86+
"""
87+
)
88+
var swiftBranch: String? = nil
89+
90+
@Option(help: "Version of Swift to supply in the bundle.")
91+
var swiftVersion = "5.9.2-RELEASE"
92+
93+
@Option(
94+
help: """
95+
CPU architecture of the host triple of the bundle. Defaults to a triple of the machine this generator is \
96+
running on if unspecified. Available options: \(
97+
Triple.CPU.allCases.map { "`\($0.rawValue)`" }.joined(separator: ", ")
98+
).
99+
"""
100+
)
101+
var hostArch: Triple.CPU? = nil
102+
103+
@Option(
104+
help: """
105+
CPU architecture of the target triple of the bundle. Same as the host triple CPU architecture if unspecified. \
106+
Available options: \(Triple.CPU.allCases.map { "`\($0.rawValue)`" }.joined(separator: ", ")).
107+
"""
108+
)
109+
var targetArch: Triple.CPU? = nil
110+
111+
func deriveTriples() async throws -> (hostTriple: Triple, targetTriple: Triple) {
112+
let hostTriple = try await SwiftSDKGenerator.getHostTriple(explicitArch: hostArch, isVerbose: verbose)
113+
let targetTriple = Triple(
114+
cpu: targetArch ?? hostTriple.cpu,
115+
vendor: .unknown,
116+
os: .linux,
117+
environment: .gnu
118+
)
119+
return (hostTriple, targetTriple)
120+
}
121+
}
122+
123+
struct MakeLinuxSDK: AsyncParsableCommand {
124+
static let configuration = CommandConfiguration(
125+
commandName: "make-linux-sdk",
126+
abstract: "Generate a Swift SDK bundle for Linux."
127+
)
128+
129+
@OptionGroup
130+
var generatorOptions: GeneratorOptions
131+
132+
@Flag(help: "Delegate to Docker for copying files for the target triple.")
133+
var withDocker: Bool = false
134+
135+
@Option(help: "Container image from which to copy the target triple.")
136+
var fromContainerImage: String? = nil
137+
138+
@Option(help: "Version of LLD linker to supply in the bundle.")
139+
var lldVersion = "17.0.5"
140+
141+
@Option(
142+
help: """
143+
Linux distribution to use if the target platform is Linux. Available options: `ubuntu`, `rhel`. Default is `ubuntu`.
144+
""",
145+
transform: LinuxDistribution.Name.init(nameString:)
146+
)
147+
var linuxDistributionName = LinuxDistribution.Name.ubuntu
148+
149+
@Option(
150+
help: """
151+
Version of the Linux distribution used as a target platform. Available options for Ubuntu: `20.04`, \
152+
`22.04` (default when `--linux-distribution-name` is `ubuntu`). Available options for RHEL: `ubi9` (default when \
153+
`--linux-distribution-name` is `rhel`).
154+
"""
155+
)
156+
var linuxDistributionVersion: String?
157+
158+
func run() async throws {
159+
if isInvokedAsDefaultSubcommand() {
160+
print("deprecated: Please explicity specify the subcommand to run. For example: $ swift-sdk-generator make-linux-sdk")
161+
}
162+
let linuxDistributionDefaultVersion = switch self.linuxDistributionName {
163+
case .rhel:
164+
"ubi9"
165+
case .ubuntu:
166+
"22.04"
167+
}
168+
let linuxDistributionVersion = self.linuxDistributionVersion ?? linuxDistributionDefaultVersion
169+
let linuxDistribution = try LinuxDistribution(name: linuxDistributionName, version: linuxDistributionVersion)
170+
let (_, targetTriple) = try await generatorOptions.deriveTriples()
171+
172+
let recipe = try LinuxRecipe(
173+
targetTriple: targetTriple,
174+
linuxDistribution: linuxDistribution,
175+
swiftVersion: generatorOptions.swiftVersion,
176+
swiftBranch: generatorOptions.swiftBranch,
177+
lldVersion: lldVersion,
178+
withDocker: withDocker,
179+
fromContainerImage: fromContainerImage
180+
)
181+
try await GeneratorCLI.run(recipe: recipe, options: generatorOptions)
182+
}
183+
184+
func isInvokedAsDefaultSubcommand() -> Bool {
185+
let arguments = CommandLine.arguments
186+
guard arguments.count >= 2 else {
187+
// No subcommand nor option: $ swift-sdk-generator
188+
return true
189+
}
190+
let maybeSubcommand = arguments[1]
191+
guard maybeSubcommand == Self.configuration.commandName else {
192+
// No subcommand but with option: $ swift-sdk-generator --with-docker
193+
return true
194+
}
195+
return false
196+
}
197+
}
198+
}
199+
154200
// FIXME: replace this with a call on `.formatted()` on `Duration` when it's available in swift-foundation.
155201
import Foundation
156202

0 commit comments

Comments
 (0)