201 lines
7.7 KiB
Swift
201 lines
7.7 KiB
Swift
// Copyright 2024 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.
|
|
|
|
// This executable is a thin wrapper around the Swift Package Manager and the Carton's SwiftPM Plugins.
|
|
// The responsibilities of this executable are:
|
|
// - to install appropriate SwiftWasm toolchain if it's not installed and to use it for the later invocations
|
|
// * This step will be eventually removed once SwiftPM provides a good way to manage Swift SDKs declaratively
|
|
// and Xcode toolchain provides WebAssembly target. (OSS toolchain already provides it)
|
|
// - to grant the SwiftPM Plugin process appropriate permissions to write to the file system
|
|
// * "dev" and "test" subcommands require listening TCP sockets but SwiftPM doesn't provide a way to
|
|
// express this requirement in the package manifest
|
|
// * "bundle" subcommand requires writing to the file system to "./Bundle" directory. This is to keep
|
|
// soft compatibility with the default behavior of the previous version of Carton
|
|
// - to give the SwiftPM build system the target triple by default
|
|
// * SwiftPM doesn't provide a way to control the target triple from plugin process
|
|
// - to pre-build "{package-name}PackageTests" product before running plugin process
|
|
// * SwiftPM doesn't support building only "all tests" product from plugin process, so we have to
|
|
// build it before running the CartonTest plugin process
|
|
//
|
|
// This executable should be eventually removed once SwiftPM provides a way to express those requirements.
|
|
|
|
import CartonCore
|
|
import CartonHelpers
|
|
import Foundation
|
|
import SwiftToolchain
|
|
|
|
struct CartonDriverError: Error & CustomStringConvertible {
|
|
init(_ description: String) {
|
|
self.description = description
|
|
}
|
|
var description: String
|
|
}
|
|
|
|
func derivePackageCommandArguments(
|
|
swiftExec: URL,
|
|
subcommand: String,
|
|
scratchPath: String,
|
|
extraArguments: [String]
|
|
) throws -> [String] {
|
|
var packageArguments: [String] = [
|
|
"package", "--triple", "wasm32-unknown-wasi", "--scratch-path", scratchPath,
|
|
]
|
|
let pluginArguments: [String] = ["plugin"]
|
|
var cartonPluginArguments: [String] = extraArguments
|
|
|
|
let pid = ProcessInfo.processInfo.processIdentifier
|
|
|
|
switch subcommand {
|
|
case "bundle":
|
|
packageArguments += ["--disable-sandbox"]
|
|
// TODO: Uncomment this line once we stop creating .carton directory in the home directory
|
|
// pluginArguments += ["--allow-writing-to-package-directory"]
|
|
|
|
// Place before user-given extra arguments to allow overriding default options
|
|
cartonPluginArguments = ["--output", "Bundle"] + cartonPluginArguments
|
|
case "dev":
|
|
packageArguments += ["--disable-sandbox"]
|
|
cartonPluginArguments += ["--pid", pid.description]
|
|
case "test":
|
|
// 1. Ask the plugin process to generate the build command based on the given options
|
|
let commandFile = try makeTemporaryFile(prefix: "test-build")
|
|
try Foundation.Process.checkRun(
|
|
swiftExec,
|
|
arguments: packageArguments + pluginArguments + [
|
|
"carton-test",
|
|
"internal-get-build-command",
|
|
] + cartonPluginArguments + ["--output", commandFile.path]
|
|
)
|
|
|
|
// 2. Build the test product
|
|
let buildArguments = try String(contentsOf: commandFile).split(separator: "\n")
|
|
if !buildArguments.isEmpty {
|
|
let buildCommand = buildArguments.map(String.init) + [
|
|
// NOTE: "swift-build" uses llbuild manifest cache by default even though
|
|
// target triple changed.
|
|
"--disable-build-manifest-caching",
|
|
"--triple", "wasm32-unknown-wasi", "--scratch-path", scratchPath,
|
|
]
|
|
try Foundation.Process.checkRun(
|
|
swiftExec,
|
|
arguments: buildCommand
|
|
)
|
|
}
|
|
|
|
// "--environment browser" launches a http server
|
|
packageArguments += ["--disable-sandbox"]
|
|
cartonPluginArguments += ["--pid", pid.description]
|
|
default: break
|
|
}
|
|
|
|
return packageArguments + pluginArguments + ["carton-\(subcommand)"] + cartonPluginArguments
|
|
}
|
|
|
|
var errnoString: String {
|
|
String(cString: strerror(errno))
|
|
}
|
|
|
|
var temporaryDirectory: URL {
|
|
URL(fileURLWithPath: NSTemporaryDirectory())
|
|
}
|
|
|
|
func makeTemporaryFile(prefix: String, in directory: URL? = nil) throws -> URL {
|
|
let directory = directory ?? temporaryDirectory
|
|
var template = directory.appendingPathComponent("\(prefix)XXXXXX").path
|
|
let result = try template.withUTF8 { template in
|
|
let copy = UnsafeMutableBufferPointer<CChar>.allocate(capacity: template.count + 1)
|
|
defer { copy.deallocate() }
|
|
template.copyBytes(to: copy)
|
|
copy[template.count] = 0
|
|
guard mkstemp(copy.baseAddress!) != -1 else {
|
|
let error = errnoString
|
|
throw CartonDriverError("Failed to make a temporary file at \(template): \(error)")
|
|
}
|
|
return String(cString: copy.baseAddress!)
|
|
}
|
|
return URL(fileURLWithPath: result)
|
|
}
|
|
|
|
func pluginSubcommand(subcommand: String, argv0: String, arguments: [String]) async throws {
|
|
let scratchPath = URL(fileURLWithPath: ".build/carton")
|
|
if FileManager.default.fileExists(atPath: scratchPath.path) {
|
|
try FileManager.default.createDirectory(at: scratchPath, withIntermediateDirectories: true)
|
|
}
|
|
|
|
let terminal = InteractiveWriter.stdout
|
|
let toolchainSystem = try ToolchainSystem(fileSystem: localFileSystem)
|
|
let swiftPath = try await toolchainSystem.inferSwiftPath(terminal)
|
|
let extraArguments = arguments
|
|
|
|
let swiftExec = URL(fileURLWithPath: swiftPath.swift.pathString)
|
|
let pluginArguments = try derivePackageCommandArguments(
|
|
swiftExec: swiftExec,
|
|
subcommand: subcommand,
|
|
scratchPath: scratchPath.path,
|
|
extraArguments: extraArguments
|
|
)
|
|
|
|
var env: [String: String] = ProcessInfo.processInfo.environment
|
|
if ToolchainSystem.isSnapshotVersion(swiftPath.version),
|
|
swiftPath.toolchain.extension == "xctoolchain"
|
|
{
|
|
env["DYLD_LIBRARY_PATH"] = swiftPath.toolchain.appending(
|
|
components: ["usr", "lib", "swift", "macosx"]
|
|
).pathString
|
|
}
|
|
|
|
try Foundation.Process.checkRun(
|
|
swiftExec,
|
|
arguments: pluginArguments,
|
|
environment: env,
|
|
forwardExit: true
|
|
)
|
|
}
|
|
|
|
public func main(arguments: [String]) async throws {
|
|
let argv0 = arguments[0]
|
|
let arguments = arguments.dropFirst()
|
|
let pluginSubcommands = ["bundle", "dev", "test"]
|
|
let subcommands = pluginSubcommands + ["package", "--version"]
|
|
guard let subcommand = arguments.first, subcommands.contains(subcommand) else {
|
|
if arguments.first == "init" {
|
|
print(
|
|
"Warning: 'init' subcommand has been removed, use 'swift package init' and add 'carton' as a dependency in Package.swift instead."
|
|
)
|
|
}
|
|
print("Usage: swift run carton <subcommand> [options]")
|
|
print("Available subcommands: \(subcommands.joined(separator: ", "))")
|
|
exit(1)
|
|
}
|
|
|
|
switch subcommand {
|
|
case _ where pluginSubcommands.contains(subcommand):
|
|
try await pluginSubcommand(
|
|
subcommand: subcommand, argv0: argv0, arguments: Array(arguments.dropFirst()))
|
|
case "package":
|
|
let terminal = InteractiveWriter.stdout
|
|
let toolchainSystem = try ToolchainSystem(fileSystem: localFileSystem)
|
|
let swiftPath = try await toolchainSystem.inferSwiftPath(terminal)
|
|
|
|
try Foundation.Process.checkRun(
|
|
URL(fileURLWithPath: swiftPath.swift.pathString),
|
|
arguments: ["package"] + arguments.dropFirst(),
|
|
forwardExit: true
|
|
)
|
|
case "--version":
|
|
print(cartonVersion)
|
|
default: fatalError("Unimplemented subcommand!?")
|
|
}
|
|
}
|