carton/Sources/SwiftToolchain/Builder.swift

124 lines
4.2 KiB
Swift

// Copyright 2020 Carton contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import CartonHelpers
import Foundation
import TSCBasic
import WasmTransformer
public final class Builder {
public let mainWasmPath: AbsolutePath
public let pathsToWatch: [AbsolutePath]
private let arguments: [String]
private let flavor: BuildFlavor
private let terminal: InteractiveWriter
private let fileSystem: FileSystem
public init(
arguments: [String],
mainWasmPath: AbsolutePath,
pathsToWatch: [AbsolutePath] = [],
_ flavor: BuildFlavor,
_ fileSystem: FileSystem,
_ terminal: InteractiveWriter
) {
self.arguments = arguments
self.mainWasmPath = mainWasmPath
self.pathsToWatch = pathsToWatch
self.flavor = flavor
self.terminal = terminal
self.fileSystem = fileSystem
}
private func buildWithoutSanitizing(builderArguments: [String]) async throws {
let buildStarted = Date()
try await Process.run(
builderArguments,
loadingMessage: "Compiling...",
parser: nil,
terminal
)
terminal.logLookup(
"`swift build` completed in ",
String(format: "%.2f seconds", abs(buildStarted.timeIntervalSinceNow))
)
var transformers: [(inout InputByteStream, inout InMemoryOutputWriter) throws -> ()] = []
if flavor.environment == .node || flavor.environment == .defaultBrowser {
// If building for JS-host environments,
// - i64 params in imports are not supported without bigint-i64 feature
// - The param types in imports don't have to be strictly same as host expected
// - Users cannot avoid having such imports come from WASI since they are
// mandatory imports.
//
// So lower i64 param types to be i32. It happens *only for WASI imports*
// since users can avoid such imports coming from other user modules.
let transformer = I64ImportTransformer(shouldLower: {
$0.module == "wasi_snapshot_preview1" || $0.module == "wasi_unstable"
})
transformers.append(transformer.transform)
}
// Strip unnecessary autolink sections, which is only used at link-time
transformers.append(CustomSectionStripper(stripIf: {
$0 == ".swift1_autolink_entries"
}).transform)
switch flavor.sanitize {
case .stackOverflow:
transformers.append(StackOverflowSanitizer().transform)
case .none:
break
}
guard !transformers.isEmpty else { return }
let binary = try fileSystem.readFileContents(mainWasmPath)
let transformStarted = Date()
var inputBinary = binary.contents
for transformer in transformers {
var input = InputByteStream(bytes: inputBinary)
var writer = InMemoryOutputWriter(reservingCapacity: inputBinary.count)
try transformer(&input, &writer)
inputBinary = writer.bytes()
}
terminal.logLookup(
"Binary transformation for Safari compatibility completed in ",
String(format: "%.2f seconds", abs(transformStarted.timeIntervalSinceNow))
)
try fileSystem.writeFileContents(mainWasmPath, bytes: .init(inputBinary))
}
public func run() async throws {
switch flavor.sanitize {
case .none:
return try await buildWithoutSanitizing(builderArguments: arguments)
case .stackOverflow:
let sanitizerFile =
fileSystem.homeDirectory.appending(components: ".carton", "static", "so_sanitizer.wasm")
var modifiedArguments = arguments
modifiedArguments.append(contentsOf: [
"-Xlinker", sanitizerFile.pathString,
// stack-overflow-sanitizer depends on "--stack-first"
"-Xlinker", "--stack-first",
])
return try await buildWithoutSanitizing(builderArguments: modifiedArguments)
}
}
}