Skip to content

Commit

Permalink
Merge branch 'main' of github.com:cmcgee1024/swiftly into self-host-g…
Browse files Browse the repository at this point in the history
…h-actions
  • Loading branch information
cmcgee1024 committed Jan 16, 2025
2 parents 8c1cd93 + 8aa8d18 commit 2f46918
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 69 deletions.
1 change: 1 addition & 0 deletions Documentation/SwiftlyDocs.docc/SwiftlyDocs.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Install and manage your Swift programming language toolchains.

- <doc:install-toolchains>
- <doc:use-toolchains>
- <doc:shell-autocompletion>
- <doc:uninstall-toolchains>
- <doc:update-toolchain>
- <doc:automated-install>
Expand Down
64 changes: 64 additions & 0 deletions Documentation/SwiftlyDocs.docc/shell-autocompletion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Add Shell Auto-completions

Swiftly can generate shell auto-completion scripts for your shell to automatically complete subcommands, arguments, options and flags. It does this using the [swift-argument-parser](https://apple.github.io/swift-argument-parser/documentation/argumentparser/installingcompletionscripts/), which has support for Bash, Z shell, and Fish.

You can ask swiftly to generate the script using the hidden `--generate-completion-script` flag with the type of shell like this:

```
swiftly --generate-completion-script <shell>
```

@TabNavigator {
@Tab("zsh") {
If you have [oh-my-zsh](https://ohmyz.sh/) installed then this command will install the swiftly completions into the default directory:

```
swiftly --generate-completion-script zsh > ~/.oh-my-zsh/completions/_swiftly
```

Otherwise, you'll need to add a path for completion scripts to your function path, and turn on completion script autoloading. First, add these lines to ~/.zshrc:

```
fpath=(~/.zsh/completion $fpath)
autoload -U compinit
compinit
```

Next, create the completion directory and add the swiftly completions to it:

```
mkdir -p ~/.zsh/completion && swiftly --generate-completion-script zsh > ~/.zsh/completions/swiftly
```
}

@Tab("bash") {
If you have [bash-completion](https://github.com/scop/bash-completion) installed then this command will install the swiftly completions into the default directory:

```
swiftly --generate-completion-script bash > swiftly.bash
# copy swiftly.bash to /usr/local/etc/bash_completion.d
```

Without bash-completion create a directory for the completion and generate the script in there:

```
mkdir -p ~/.bash_completions && swiftly --generate-completion-script bash > ~/.bash_completions/swiftly.bash
```

Add a line to source that script in your `~/.bash_profile` or `~/.bashrc` file:

```
source ~/.bash_completions/swiftly.bash
```
}

@Tab("fish") {
Generate the completion script to any path in the environment variable `$fish_completion_path`. Typically this command will generate the script in the right place:

```
swiftly --generate-completion-script fish > ~/.config/fish/completions/swiftly.fish
```
}
}

Once you have installed the completions you can type out a swiftly command, and press a special key (e.g. tab) and the shell will show you the available subcommand, argument, or options to make it easier to assemble a working command-line.
6 changes: 3 additions & 3 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "9b0d92b6fbb59080a05ce00f87dc9c6277b32d78e56905abba4c40947edf6d7d",
"originHash" : "531e10b955219c0de91ada74260f59bff8033189f1a9f9f78b199480c61f466a",
"pins" : [
{
"identity" : "async-http-client",
Expand Down Expand Up @@ -150,8 +150,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-tools-support-core.git",
"state" : {
"revision" : "5b130e04cc939373c4713b91704b0c47ceb36170",
"version" : "0.7.1"
"revision" : "b464fcd8d884e599e3202d9bd1eee29a9e504069",
"version" : "0.7.2"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
.package(url: "https://github.com/swift-server/async-http-client", from: "1.21.2"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.64.0"),
.package(url: "https://github.com/apple/swift-tools-support-core.git", from: "0.7.1"),
.package(url: "https://github.com/apple/swift-tools-support-core.git", from: "0.7.2"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"),
// This dependency provides the correct version of the formatter so that you can run `swift run swiftformat Package.swift Plugins/ Sources/ Tests/`
.package(url: "https://github.com/nicklockwood/SwiftFormat", exact: "0.49.18"),
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ Ongoing maintenance and stewardship of this project is led by the [SSWG](https:/

### Installation

Download the swiftly package from [swift.org](https://swift.org/download) and it can install itself with init:
Install swiftly using a script (hosted from this repository) using the command:

```
curl -L https://swiftlang.github.io/swiftly/swiftly-install.sh | bash
```

In the future, download the swiftly package from [swift.org](https://swift.org/download) and it can install itself with init:

```
swiftly init
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftlyCore/SwiftlyCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public protocol InputProvider {
public var inputProvider: (any InputProvider)?

public func readLine(prompt: String) -> String? {
print(prompt, terminator: ": ")
print(prompt, terminator: ": \n")
guard let provider = SwiftlyCore.inputProvider else {
return Swift.readLine(strippingNewline: true)
}
Expand Down
111 changes: 48 additions & 63 deletions Tools/build-swiftly-release/BuildSwiftlyRelease.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import ArgumentParser
import Foundation

public struct SwiftPlatform: Codable {
public var name: String?
public var checksum: String?
}

public struct SwiftRelease: Codable {
public var name: String?
public var platforms: [SwiftPlatform]?
}

// These functions are cloned and adapted from SwiftlyCore until we can do better bootstrapping
public struct Error: LocalizedError {
public let message: String
Expand All @@ -13,6 +23,8 @@ public struct Error: LocalizedError {
}

public func runProgramEnv(_ args: String..., quiet: Bool = false, env: [String: String]?) throws {
if !quiet { print("\(args.joined(separator: " "))") }

let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = args
Expand Down Expand Up @@ -40,6 +52,8 @@ public func runProgramEnv(_ args: String..., quiet: Bool = false, env: [String:
}

public func runProgram(_ args: String..., quiet: Bool = false) throws {
if !quiet { print("\(args.joined(separator: " "))") }

let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = args
Expand Down Expand Up @@ -126,58 +140,6 @@ public func getShell() async throws -> String {
}
#endif

public func isSupportedLinux(useRhelUbi9: Bool) -> Bool {
let osReleaseFiles = ["/etc/os-release", "/usr/lib/os-release"]
var releaseFile: String?
for file in osReleaseFiles {
if FileManager.default.fileExists(atPath: file) {
releaseFile = file
break
}
}

guard let releaseFile = releaseFile else {
return false
}

guard let data = FileManager.default.contents(atPath: releaseFile) else {
return false
}

guard let releaseInfo = String(data: data, encoding: .utf8) else {
return false
}

var id: String?
var idlike: String?
var versionID: String?
for info in releaseInfo.split(separator: "\n").map(String.init) {
if info.hasPrefix("ID=") {
id = String(info.dropFirst("ID=".count)).replacingOccurrences(of: "\"", with: "")
} else if info.hasPrefix("ID_LIKE=") {
idlike = String(info.dropFirst("ID_LIKE=".count)).replacingOccurrences(of: "\"", with: "")
} else if info.hasPrefix("VERSION_ID=") {
versionID = String(info.dropFirst("VERSION_ID=".count)).replacingOccurrences(of: "\"", with: "")
}
}

guard let id = id, let idlike = idlike else {
return false
}

if useRhelUbi9 {
guard let versionID, versionID.hasPrefix("9"), (id + idlike).contains("rhel") else {
return false
}
} else {
guard let versionID = versionID, versionID == "2", (id + idlike).contains("amzn") else {
return false
}
}

return true
}

@main
struct BuildSwiftlyRelease: AsyncParsableCommand {
static let configuration = CommandConfiguration(
Expand All @@ -195,7 +157,7 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
@Option(help: "Package identifier of macOS package")
var identifier: String = "org.swift.swiftly"
#elseif os(Linux)
@Flag(name: .long, help: "Use RHEL UBI9 as the supported Linux to build a release instead of Amazon Linux 2")
@Flag(name: .long, help: "Deprecated option since releases can be built on any swift supported Linux distribution.")
var useRhelUbi9: Bool = false
#endif

Expand Down Expand Up @@ -295,13 +257,6 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
}

func buildLinuxRelease() async throws {
#if os(Linux)
// Check system requirements
guard isSupportedLinux(useRhelUbi9: self.useRhelUbi9) else {
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").")
}
#endif

// TODO: turn these into checks that the system meets the criteria for being capable of using the toolchain + checking for packages, not tools
let curl = try await self.assertTool("curl", message: "Please install curl with `yum install curl`")
let tar = try await self.assertTool("tar", message: "Please install tar with `yum install tar`")
Expand Down Expand Up @@ -340,8 +295,38 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
let cwd = FileManager.default.currentDirectoryPath
FileManager.default.changeCurrentDirectoryPath(libArchivePath)

let swiftVerRegex: Regex<(Substring, Substring)> = try! Regex("Swift version (\\d+\\.\\d+\\.\\d+) ")
let swiftVerOutput = (try await runProgramOutput(swift, "--version")) ?? ""
guard let swiftVerMatch = try swiftVerRegex.firstMatch(in: swiftVerOutput) else {
throw Error(message: "Unable to detect swift version")
}

let swiftVersion = swiftVerMatch.output.1

let sdkName = "swift-\(swiftVersion)-RELEASE_static-linux-0.0.1"

#if arch(arm64)
let arch = "aarch64"
#else
let arch = "x86_64"
#endif

let swiftReleasesJson = (try await runProgramOutput(curl, "https://www.swift.org/api/v1/install/releases.json")) ?? "[]"
let swiftReleases = try JSONDecoder().decode([SwiftRelease].self, from: swiftReleasesJson.data(using: .utf8)!)

guard let swiftRelease = swiftReleases.first(where: { ($0.name ?? "") == swiftVersion }) else {
throw Error(message: "Unable to find swift release using swift.org API: \(swiftVersion)")
}

guard let sdkPlatform = (swiftRelease.platforms ?? [SwiftPlatform]()).first(where: { ($0.name ?? "") == "Static SDK" }) else {
throw Error(message: "Swift release \(swiftVersion) has no Static SDK offering")
}

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")

var customEnv = ProcessInfo.processInfo.environment
customEnv["CC"] = "clang"
customEnv["CC"] = "\(cwd)/Tools/build-swiftly-release/musl-clang"
customEnv["MUSL_PREFIX"] = "\(FileManager.default.homeDirectoryForCurrentUser.path)/.swiftpm/swift-sdks/\(sdkName).artifactbundle/\(sdkName)/swift-linux-musl/musl-1.2.5.sdk/\(arch)/usr"

try runProgramEnv(
"./configure",
Expand Down Expand Up @@ -371,8 +356,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {

FileManager.default.changeCurrentDirectoryPath(cwd)

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

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

Expand Down
57 changes: 57 additions & 0 deletions Tools/build-swiftly-release/musl-clang
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/sh

PREFIX=${MUSL_PREFIX:-"/usr/local/musl"}
if [ ! -d "${PREFIX}" ]; then
echo "invalid prefix: ${PREFIX}"
return 1
fi

CLANG=${REALCLANG:-"clang"}

hasNo() {
pat="$1"
shift 1

for e in "$@"; do
if [ "$e" = "${pat}" ]; then
return 1
fi
done
return 0
}

ARGS="-nostdinc"
TAIL=""

if hasNo '-nostdinc' "$@"; then
ARGS="${ARGS} -isystem ${PREFIX}/include"
fi

if \
hasNo '-c' "$@" && \
hasNo '-S' "$@" && \
hasNo '-E' "$@"
then
ARGS="${ARGS} -nostdlib"
ARGS="${ARGS} -Wl,-dynamic-linker=${PREFIX}/lib/libc.so"
ARGS="${ARGS} -L${PREFIX}/lib -L${PREFIX}/lib/swift/clang/lib/linux"

if hasNo '-nostartfiles' "$@" && \
hasNo '-nostdlib' "$@" && \
hasNo '-nodefaultlibs' "$@"
then
ARGS="${ARGS} ${PREFIX}/lib/crt1.o"
ARGS="${ARGS} ${PREFIX}/lib/crti.o"

TAIL="${TAIL} ${PREFIX}/lib/crtn.o"
fi

if hasNo '-nostdlib' "$@" && \
hasNo '-nodefaultlibs' "$@"
then
TAIL="${TAIL} -lc -lclang_rt.builtins-$(uname -m)"
fi
fi

exec ${CLANG} ${ARGS} "$@" ${TAIL} -static

0 comments on commit 2f46918

Please sign in to comment.