Skip to content

Commit acd2139

Browse files
authored
Adopt the static linux sdk (#196)
With the Static Linux SDK swiftly can be compiled on a particular Linux and processor architecture and the binary can run on a very wide variety of versions and distributions. This eliminates the need to build swiftly on "older" Linux distributions to produce a binary that runs on the "newer" ones> Bump the version of swift-tools-support-core so that it compiles successfully in a Static SDK environment with Musl. Fix the readLine function to add a newline to the prompt so that it prints it out before waiting on user input on stdin. Update the build-swiftly-release tool. First, detect the version of swift so that the matching version of the Static SDK is downloaded and installed into the toolchain. Use the Musl that is inside it to configure and compile libarchive along with a special must-clang wrapper script that sets the correct compile options to use Musl instead of the system's glibc. Finally, build the swiftly binary with the Static SDK / Musl linking it together with the Musl compiled libarchive. Deprecate the old flag that would decide between RHEL UBI9 or Amazon Linux 2 as the required release building platform and eliminate the supported Linux check.
1 parent 150e93a commit acd2139

File tree

5 files changed

+110
-68
lines changed

5 files changed

+110
-68
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
1818
.package(url: "https://github.com/swift-server/async-http-client", from: "1.21.2"),
1919
.package(url: "https://github.com/apple/swift-nio.git", from: "2.64.0"),
20-
.package(url: "https://github.com/apple/swift-tools-support-core.git", from: "0.7.1"),
20+
.package(url: "https://github.com/apple/swift-tools-support-core.git", from: "0.7.2"),
2121
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"),
2222
// This dependency provides the correct version of the formatter so that you can run `swift run swiftformat Package.swift Plugins/ Sources/ Tests/`
2323
.package(url: "https://github.com/nicklockwood/SwiftFormat", exact: "0.49.18"),

Sources/SwiftlyCore/SwiftlyCore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public protocol InputProvider {
4545
public var inputProvider: (any InputProvider)?
4646

4747
public func readLine(prompt: String) -> String? {
48-
print(prompt, terminator: ": ")
48+
print(prompt, terminator: ": \n")
4949
guard let provider = SwiftlyCore.inputProvider else {
5050
return Swift.readLine(strippingNewline: true)
5151
}

Tools/build-swiftly-release/BuildSwiftlyRelease.swift

Lines changed: 48 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import ArgumentParser
22
import Foundation
33

4+
public struct SwiftPlatform: Codable {
5+
public var name: String?
6+
public var checksum: String?
7+
}
8+
9+
public struct SwiftRelease: Codable {
10+
public var name: String?
11+
public var platforms: [SwiftPlatform]?
12+
}
13+
414
// These functions are cloned and adapted from SwiftlyCore until we can do better bootstrapping
515
public struct Error: LocalizedError {
616
public let message: String
@@ -13,6 +23,8 @@ public struct Error: LocalizedError {
1323
}
1424

1525
public func runProgramEnv(_ args: String..., quiet: Bool = false, env: [String: String]?) throws {
26+
if !quiet { print("\(args.joined(separator: " "))") }
27+
1628
let process = Process()
1729
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
1830
process.arguments = args
@@ -40,6 +52,8 @@ public func runProgramEnv(_ args: String..., quiet: Bool = false, env: [String:
4052
}
4153

4254
public func runProgram(_ args: String..., quiet: Bool = false) throws {
55+
if !quiet { print("\(args.joined(separator: " "))") }
56+
4357
let process = Process()
4458
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
4559
process.arguments = args
@@ -126,58 +140,6 @@ public func getShell() async throws -> String {
126140
}
127141
#endif
128142

129-
public func isSupportedLinux(useRhelUbi9: Bool) -> Bool {
130-
let osReleaseFiles = ["/etc/os-release", "/usr/lib/os-release"]
131-
var releaseFile: String?
132-
for file in osReleaseFiles {
133-
if FileManager.default.fileExists(atPath: file) {
134-
releaseFile = file
135-
break
136-
}
137-
}
138-
139-
guard let releaseFile = releaseFile else {
140-
return false
141-
}
142-
143-
guard let data = FileManager.default.contents(atPath: releaseFile) else {
144-
return false
145-
}
146-
147-
guard let releaseInfo = String(data: data, encoding: .utf8) else {
148-
return false
149-
}
150-
151-
var id: String?
152-
var idlike: String?
153-
var versionID: String?
154-
for info in releaseInfo.split(separator: "\n").map(String.init) {
155-
if info.hasPrefix("ID=") {
156-
id = String(info.dropFirst("ID=".count)).replacingOccurrences(of: "\"", with: "")
157-
} else if info.hasPrefix("ID_LIKE=") {
158-
idlike = String(info.dropFirst("ID_LIKE=".count)).replacingOccurrences(of: "\"", with: "")
159-
} else if info.hasPrefix("VERSION_ID=") {
160-
versionID = String(info.dropFirst("VERSION_ID=".count)).replacingOccurrences(of: "\"", with: "")
161-
}
162-
}
163-
164-
guard let id = id, let idlike = idlike else {
165-
return false
166-
}
167-
168-
if useRhelUbi9 {
169-
guard let versionID, versionID.hasPrefix("9"), (id + idlike).contains("rhel") else {
170-
return false
171-
}
172-
} else {
173-
guard let versionID = versionID, versionID == "2", (id + idlike).contains("amzn") else {
174-
return false
175-
}
176-
}
177-
178-
return true
179-
}
180-
181143
@main
182144
struct BuildSwiftlyRelease: AsyncParsableCommand {
183145
static let configuration = CommandConfiguration(
@@ -195,7 +157,7 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
195157
@Option(help: "Package identifier of macOS package")
196158
var identifier: String = "org.swift.swiftly"
197159
#elseif os(Linux)
198-
@Flag(name: .long, help: "Use RHEL UBI9 as the supported Linux to build a release instead of Amazon Linux 2")
160+
@Flag(name: .long, help: "Deprecated option since releases can be built on any swift supported Linux distribution.")
199161
var useRhelUbi9: Bool = false
200162
#endif
201163

@@ -295,13 +257,6 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
295257
}
296258

297259
func buildLinuxRelease() async throws {
298-
#if os(Linux)
299-
// Check system requirements
300-
guard isSupportedLinux(useRhelUbi9: self.useRhelUbi9) else {
301-
throw Error(message: "Linux releases must be made from specific distributions so that the binary can be used everyone else because it has the oldest version of glibc for maximum compatibility with other versions of Linux. Please try again with \(!self.useRhelUbi9 ? "Amazon Linux 2" : "RedHat UBI 9").")
302-
}
303-
#endif
304-
305260
// TODO: turn these into checks that the system meets the criteria for being capable of using the toolchain + checking for packages, not tools
306261
let curl = try await self.assertTool("curl", message: "Please install curl with `yum install curl`")
307262
let tar = try await self.assertTool("tar", message: "Please install tar with `yum install tar`")
@@ -340,8 +295,38 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
340295
let cwd = FileManager.default.currentDirectoryPath
341296
FileManager.default.changeCurrentDirectoryPath(libArchivePath)
342297

298+
let swiftVerRegex: Regex<(Substring, Substring)> = try! Regex("Swift version (\\d+\\.\\d+\\.\\d+) ")
299+
let swiftVerOutput = (try await runProgramOutput(swift, "--version")) ?? ""
300+
guard let swiftVerMatch = try swiftVerRegex.firstMatch(in: swiftVerOutput) else {
301+
throw Error(message: "Unable to detect swift version")
302+
}
303+
304+
let swiftVersion = swiftVerMatch.output.1
305+
306+
let sdkName = "swift-\(swiftVersion)-RELEASE_static-linux-0.0.1"
307+
308+
#if arch(arm64)
309+
let arch = "aarch64"
310+
#else
311+
let arch = "x86_64"
312+
#endif
313+
314+
let swiftReleasesJson = (try await runProgramOutput(curl, "https://www.swift.org/api/v1/install/releases.json")) ?? "[]"
315+
let swiftReleases = try JSONDecoder().decode([SwiftRelease].self, from: swiftReleasesJson.data(using: .utf8)!)
316+
317+
guard let swiftRelease = swiftReleases.first(where: { ($0.name ?? "") == swiftVersion }) else {
318+
throw Error(message: "Unable to find swift release using swift.org API: \(swiftVersion)")
319+
}
320+
321+
guard let sdkPlatform = (swiftRelease.platforms ?? [SwiftPlatform]()).first(where: { ($0.name ?? "") == "Static SDK" }) else {
322+
throw Error(message: "Swift release \(swiftVersion) has no Static SDK offering")
323+
}
324+
325+
try runProgram(swift, "sdk", "install", "https://download.swift.org/swift-\(swiftVersion)-release/static-sdk/swift-\(swiftVersion)-RELEASE/swift-\(swiftVersion)-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz", "--checksum", sdkPlatform.checksum ?? "deadbeef")
326+
343327
var customEnv = ProcessInfo.processInfo.environment
344-
customEnv["CC"] = "clang"
328+
customEnv["CC"] = "\(cwd)/Tools/build-swiftly-release/musl-clang"
329+
customEnv["MUSL_PREFIX"] = "\(FileManager.default.homeDirectoryForCurrentUser.path)/.swiftpm/swift-sdks/\(sdkName).artifactbundle/\(sdkName)/swift-linux-musl/musl-1.2.5.sdk/\(arch)/usr"
345330

346331
try runProgramEnv(
347332
"./configure",
@@ -371,8 +356,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
371356

372357
FileManager.default.changeCurrentDirectoryPath(cwd)
373358

374-
// Statically link standard libraries for maximum portability of the swiftly binary
375-
try runProgram(swift, "build", "--product=swiftly", "--pkg-config-path=\(pkgConfigPath)/lib/pkgconfig", "--static-swift-stdlib", "--configuration=release")
359+
try runProgram(swift, "build", "--swift-sdk", "\(arch)-swift-linux-musl", "--product=swiftly", "--pkg-config-path=\(pkgConfigPath)/lib/pkgconfig", "--static-swift-stdlib", "--configuration=release")
360+
try runProgram(swift, "sdk", "remove", sdkName)
376361

377362
let releaseDir = cwd + "/.build/release"
378363

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/bin/sh
2+
3+
PREFIX=${MUSL_PREFIX:-"/usr/local/musl"}
4+
if [ ! -d "${PREFIX}" ]; then
5+
echo "invalid prefix: ${PREFIX}"
6+
return 1
7+
fi
8+
9+
CLANG=${REALCLANG:-"clang"}
10+
11+
hasNo() {
12+
pat="$1"
13+
shift 1
14+
15+
for e in "$@"; do
16+
if [ "$e" = "${pat}" ]; then
17+
return 1
18+
fi
19+
done
20+
return 0
21+
}
22+
23+
ARGS="-nostdinc"
24+
TAIL=""
25+
26+
if hasNo '-nostdinc' "$@"; then
27+
ARGS="${ARGS} -isystem ${PREFIX}/include"
28+
fi
29+
30+
if \
31+
hasNo '-c' "$@" && \
32+
hasNo '-S' "$@" && \
33+
hasNo '-E' "$@"
34+
then
35+
ARGS="${ARGS} -nostdlib"
36+
ARGS="${ARGS} -Wl,-dynamic-linker=${PREFIX}/lib/libc.so"
37+
ARGS="${ARGS} -L${PREFIX}/lib -L${PREFIX}/lib/swift/clang/lib/linux"
38+
39+
if hasNo '-nostartfiles' "$@" && \
40+
hasNo '-nostdlib' "$@" && \
41+
hasNo '-nodefaultlibs' "$@"
42+
then
43+
ARGS="${ARGS} ${PREFIX}/lib/crt1.o"
44+
ARGS="${ARGS} ${PREFIX}/lib/crti.o"
45+
46+
TAIL="${TAIL} ${PREFIX}/lib/crtn.o"
47+
fi
48+
49+
if hasNo '-nostdlib' "$@" && \
50+
hasNo '-nodefaultlibs' "$@"
51+
then
52+
TAIL="${TAIL} -lc -lclang_rt.builtins-$(uname -m)"
53+
fi
54+
fi
55+
56+
exec ${CLANG} ${ARGS} "$@" ${TAIL} -static
57+

0 commit comments

Comments
 (0)