Skip to content

Commit

Permalink
Add WebAssembly SDK recipe and make-wasm-sdk subcommand (#74)
Browse files Browse the repository at this point in the history
* Add WebAssembly SDK recipe and `make-wasm-sdk` subcommand

This patch introduces an experimental WebAssembly SDK recipe and `make-wasm-sdk`
subcommand. The make command takes host Swift toolchain package, target
Swift stdlib (`lib/swift{,_static}`), and WASI sysroot like follows:

```console
swift run swift-sdk-generator make-wasm-sdk \
  --target wasm32-unknown-wasi \
  --host-swift-package-path Downloads/swift-DEVELOPMENT-SNAPSHOT-2024-01-12-a \
  --target-swift-package-path Downloads/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-01-12-a \
  --wasi-sysroot Downloads/wasi-sysroot
```

* Use `GeneratorOptions`'s common fields

* rsync `lib/clang` into the SDK bundle

The directory was missed to be taken from target toolchain but it wasn't
revealed on local testing since the host toolchain unintentionally
contained the directory.

---------

Co-authored-by: Max Desiatov <[email protected]>
  • Loading branch information
kateinoigakukun and MaxDesiatov authored Jan 25, 2024
1 parent cf9e76a commit de216d1
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 1 deletion.
50 changes: 49 additions & 1 deletion Sources/GeneratorCLI/GeneratorCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import ArgumentParser
import Logging
import ServiceLifecycle
import SwiftSDKGenerator
import struct SystemPackage.FilePath

@main
struct GeneratorCLI: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "swift-sdk-generator",
subcommands: [MakeLinuxSDK.self],
subcommands: [MakeLinuxSDK.self, MakeWasmSDK.self],
defaultSubcommand: MakeLinuxSDK.self
)

Expand Down Expand Up @@ -238,6 +239,46 @@ extension GeneratorCLI {
return false
}
}

struct MakeWasmSDK: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "make-wasm-sdk",
abstract: "Experimental: Generate a Swift SDK bundle for WebAssembly.",
discussion: """
The default `--target` triple is wasm32-unknown-wasi
"""
)

@OptionGroup
var generatorOptions: GeneratorOptions

@Option(
help: """
Path to the WASI sysroot directory containing the WASI libc headers and libraries.
"""
)
var wasiSysroot: String

func deriveTargetTriple(hostTriple: Triple) -> Triple {
self.generatorOptions.target ?? Triple("wasm32-unknown-wasi")
}

func run() async throws {
guard let hostSwiftPackagePath = generatorOptions.hostSwiftPackagePath,
let targetSwiftPackagePath = generatorOptions.targetSwiftPackagePath else {
throw StringError("Missing expected argument '--host-swift-package-path' or '--target-swift-package-path'")
}
let recipe = WebAssemblyRecipe(
hostSwiftPackagePath: FilePath(hostSwiftPackagePath),
targetSwiftPackagePath: FilePath(targetSwiftPackagePath),
wasiSysroot: FilePath(wasiSysroot),
swiftVersion: generatorOptions.swiftVersion
)
let hostTriple = try self.generatorOptions.deriveHostTriple()
let targetTriple = self.deriveTargetTriple(hostTriple: hostTriple)
try await GeneratorCLI.run(recipe: recipe, hostTriple: hostTriple, targetTriple: targetTriple, options: generatorOptions)
}
}
}

// FIXME: replace this with a call on `.formatted()` on `Duration` when it's available in swift-foundation.
Expand Down Expand Up @@ -269,3 +310,10 @@ struct SwiftSDKGeneratorService: Service {
try await generator.run(recipe: recipe)
}
}

struct StringError: Error, CustomStringConvertible {
let description: String
init(_ description: String) {
self.description = description
}
}
5 changes: 5 additions & 0 deletions Sources/SwiftSDKGenerator/Generator/SwiftSDKGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ public actor SwiftSDKGenerator {
try await Shell.run("rsync -a \(source) \(destination)", shouldLogCommands: self.isVerbose)
}

func rsyncContents(from source: FilePath, to destination: FilePath) async throws {
try self.createDirectoryIfNeeded(at: destination)
try await Shell.run("rsync -a \(source)/ \(destination)", shouldLogCommands: self.isVerbose)
}

func createSymlink(at source: FilePath, pointingTo destination: FilePath) throws {
try self.fileManager.createSymbolicLink(
atPath: source.string,
Expand Down
82 changes: 82 additions & 0 deletions Sources/SwiftSDKGenerator/SwiftSDKRecipes/WebAssemblyRecipe.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2022-2024 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 AsyncHTTPClient
import GeneratorEngine
import struct SystemPackage.FilePath

public struct WebAssemblyRecipe: SwiftSDKRecipe {
let hostSwiftPackagePath: FilePath
let targetSwiftPackagePath: FilePath
let wasiSysroot: FilePath
let swiftVersion: String

public init(
hostSwiftPackagePath: FilePath,
targetSwiftPackagePath: FilePath,
wasiSysroot: FilePath,
swiftVersion: String
) {
self.hostSwiftPackagePath = hostSwiftPackagePath
self.targetSwiftPackagePath = targetSwiftPackagePath
self.wasiSysroot = wasiSysroot
self.swiftVersion = swiftVersion
}

public var defaultArtifactID: String {
"\(self.swiftVersion)_wasm"
}

public func applyPlatformOptions(toolset: inout Toolset) {
// We only support static linking for WebAssembly for now, so make it the default.
toolset.swiftCompiler = Toolset.ToolProperties(extraCLIOptions: ["-static-stdlib"])
}

public func makeSwiftSDK(
generator: SwiftSDKGenerator,
engine: Engine,
httpClient: HTTPClient
) async throws -> SwiftSDKProduct {
let pathsConfiguration = generator.pathsConfiguration
logGenerationStep("Copying Swift binaries for the host triple...")
try await generator.rsync(from: self.hostSwiftPackagePath.appending("usr"), to: pathsConfiguration.toolchainDirPath)
try await self.copyTargetSwift(from: self.targetSwiftPackagePath.appending("usr/lib"), generator: generator)

let autolinkExtractPath = generator.pathsConfiguration.toolchainBinDirPath.appending("swift-autolink-extract")

// WebAssembly object file requires `swift-autolink-extract`
if await !generator.doesFileExist(at: autolinkExtractPath) {
logGenerationStep("Fixing `swift-autolink-extract` symlink...")
try await generator.createSymlink(at: autolinkExtractPath, pointingTo: "swift")
}

// Copy the WASI sysroot into the SDK bundle.
let sdkDirPath = pathsConfiguration.swiftSDKRootPath.appending("WASI.sdk")
try await generator.rsyncContents(from: self.wasiSysroot, to: sdkDirPath)

return SwiftSDKProduct(sdkDirPath: sdkDirPath)
}

func copyTargetSwift(from distributionPath: FilePath, generator: SwiftSDKGenerator) async throws {
let pathsConfiguration = generator.pathsConfiguration
logGenerationStep("Copying Swift core libraries for the target triple into Swift SDK bundle...")
for (pathWithinPackage, pathWithinSwiftSDK) in [
("clang", pathsConfiguration.toolchainDirPath.appending("usr/lib")),
("swift/wasi", pathsConfiguration.toolchainDirPath.appending("usr/lib/swift")),
("swift_static/wasi", pathsConfiguration.toolchainDirPath.appending("usr/lib/swift_static")),
("swift_static/shims", pathsConfiguration.toolchainDirPath.appending("usr/lib/swift_static")),
("swift_static/CoreFoundation", pathsConfiguration.toolchainDirPath.appending("usr/lib/swift_static")),
] {
try await generator.rsync(from: distributionPath.appending(pathWithinPackage), to: pathWithinSwiftSDK)
}
}
}

0 comments on commit de216d1

Please sign in to comment.