203 lines
6.9 KiB
Swift
203 lines
6.9 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.
|
|
|
|
import Foundation
|
|
import PackagePlugin
|
|
|
|
struct CartonPluginError: Swift.Error, CustomStringConvertible {
|
|
let description: String
|
|
|
|
init(_ message: String) {
|
|
self.description = "Error: " + message
|
|
}
|
|
}
|
|
|
|
/// Derive default product from the package
|
|
internal func deriveDefaultProduct(package: Package) throws -> String {
|
|
let executableProducts = package.products(ofType: ExecutableProduct.self)
|
|
guard !executableProducts.isEmpty else {
|
|
throw CartonPluginError(
|
|
"Make sure there's at least one executable product in your Package.swift")
|
|
}
|
|
guard executableProducts.count == 1 else {
|
|
throw CartonPluginError(
|
|
"Failed to disambiguate the product. Pass one of \(executableProducts.map(\.name).joined(separator: ", ")) to the --product option"
|
|
)
|
|
|
|
}
|
|
return executableProducts[0].name
|
|
}
|
|
|
|
/// Returns the list of resource bundle paths for the given targets
|
|
internal func deriveResourcesPaths(
|
|
productArtifactPath: Path,
|
|
sourceTargets: [any PackagePlugin.Target],
|
|
package: Package
|
|
) -> [Path] {
|
|
return deriveResourcesPaths(
|
|
buildDirectory: productArtifactPath.removingLastComponent(),
|
|
sourceTargets: sourceTargets, package: package
|
|
)
|
|
}
|
|
|
|
internal func deriveResourcesPaths(
|
|
buildDirectory: Path,
|
|
sourceTargets: [any PackagePlugin.Target],
|
|
package: Package
|
|
) -> [Path] {
|
|
sourceTargets.compactMap { target -> Path? in
|
|
// NOTE: The resource bundle file name is constructed from `displayName` instead of `id` for some reason
|
|
// https://github.com/apple/swift-package-manager/blob/swift-5.9.2-RELEASE/Sources/PackageLoading/PackageBuilder.swift#L908
|
|
let bundleName = package.displayName + "_" + target.name + ".resources"
|
|
let resourcesPath = buildDirectory.appending(subpath: bundleName)
|
|
guard FileManager.default.fileExists(atPath: resourcesPath.string) else { return nil }
|
|
return resourcesPath
|
|
}
|
|
}
|
|
|
|
extension Environment {
|
|
static func parse(from extractor: inout ArgumentExtractor) throws -> Environment {
|
|
guard let rawValue = extractor.extractOption(named: "environment").last else {
|
|
return Environment.command
|
|
}
|
|
let (parsed, diagnostic) = Environment.parse(rawValue)
|
|
if let diagnostic {
|
|
Diagnostics.warning(diagnostic)
|
|
}
|
|
guard let parsed else {
|
|
throw CartonPluginError(
|
|
"Environment '\(rawValue)' is not recognized. Use one of \(Environment.allCases.map(\.rawValue).joined(separator: ", "))"
|
|
)
|
|
}
|
|
return parsed
|
|
}
|
|
|
|
func applyBuildParameters(_ parameters: inout PackageManager.BuildParameters) {
|
|
var output = Environment.Parameters()
|
|
applyBuildParameters(&output)
|
|
parameters.otherSwiftcFlags += output.otherSwiftcFlags
|
|
parameters.otherLinkerFlags += output.otherLinkerFlags
|
|
}
|
|
}
|
|
|
|
func applyExtraBuildFlags(from extractor: inout ArgumentExtractor, parameters: inout PackageManager.BuildParameters) {
|
|
parameters.otherCFlags += extractor.extractSingleDashOption(named: "Xcc")
|
|
parameters.otherCxxFlags += extractor.extractSingleDashOption(named: "Xcxx")
|
|
parameters.otherSwiftcFlags += extractor.extractSingleDashOption(named: "Xswiftc")
|
|
parameters.otherLinkerFlags += extractor.extractSingleDashOption(named: "Xlinker")
|
|
}
|
|
|
|
extension ArgumentExtractor {
|
|
fileprivate mutating func extractSingleDashOption(named name: String) -> [String] {
|
|
let parts = remainingArguments.split(separator: "--", maxSplits: 1, omittingEmptySubsequences: false)
|
|
var args = Array(parts[0])
|
|
let literals = Array(parts.count == 2 ? parts[1] : [])
|
|
|
|
var values: [String] = []
|
|
var idx = 0
|
|
while idx < args.count {
|
|
var arg = args[idx]
|
|
if arg == "-\(name)" {
|
|
args.remove(at: idx)
|
|
if idx < args.count {
|
|
let val = args[idx]
|
|
values.append(val)
|
|
args.remove(at: idx)
|
|
}
|
|
}
|
|
else if arg.starts(with: "-\(name)=") {
|
|
arg.removeFirst(2 + name.count + 1)
|
|
values.append(arg)
|
|
args.remove(at: idx)
|
|
}
|
|
else {
|
|
idx += 1
|
|
}
|
|
}
|
|
|
|
self = ArgumentExtractor(args + literals)
|
|
return values
|
|
}
|
|
}
|
|
|
|
extension PackageManager.BuildResult {
|
|
/// Find `.wasm` executable artifact
|
|
internal func findWasmArtifact(for product: String) throws
|
|
-> PackageManager.BuildResult.BuiltArtifact
|
|
{
|
|
let executables = self.builtArtifacts.filter {
|
|
$0.kind == .executable && $0.path.lastComponent == "\(product).wasm"
|
|
}
|
|
guard !executables.isEmpty else {
|
|
throw CartonPluginError(
|
|
"Failed to find '\(product).wasm' from executable artifacts of product '\(product)'")
|
|
}
|
|
guard executables.count == 1, let executable = executables.first else {
|
|
throw CartonPluginError(
|
|
"Failed to disambiguate executable product artifacts from \(executables.map(\.path.string).joined(separator: ", "))"
|
|
)
|
|
}
|
|
return executable
|
|
}
|
|
}
|
|
|
|
internal func checkSwiftVersion() throws {
|
|
var doesSwiftPMSupportXCompilationWithPlugin: Bool {
|
|
#if swift(>=5.9.2)
|
|
return true
|
|
#else
|
|
return false
|
|
#endif
|
|
}
|
|
|
|
let magicEnvVar = "CARTON_SKIP_SWIFTPM_VERSION_CHECK"
|
|
guard ProcessInfo.processInfo.environment[magicEnvVar] == nil else {
|
|
// Skip SwiftPM version check
|
|
return
|
|
}
|
|
|
|
guard doesSwiftPMSupportXCompilationWithPlugin else {
|
|
throw CartonPluginError(
|
|
"""
|
|
SwiftPM version below 5.9.2 is not supported by carton plugin due to the lack of cross-compilation support \
|
|
with SwiftPM plugins.
|
|
You can skip this check by setting the environment variable \(magicEnvVar) to any value \
|
|
if you are sure that your SwiftPM version supports cross-compilation with plugins.
|
|
""")
|
|
}
|
|
}
|
|
|
|
internal func checkHelpFlag(_ arguments: [String], subcommand: String, context: PluginContext)
|
|
throws
|
|
{
|
|
if arguments.contains("--help") || arguments.contains("-h") {
|
|
let frontend = try makeCartonFrontendProcess(
|
|
context: context, arguments: [subcommand, "--help"])
|
|
try frontend.checkRun(printsLoadingMessage: false, forwardExit: true)
|
|
}
|
|
}
|
|
|
|
internal func makeCartonFrontendProcess(context: PluginContext, arguments: [String]) throws
|
|
-> Process
|
|
{
|
|
let frontend = try context.tool(named: "carton-frontend")
|
|
|
|
Diagnostics.remark(
|
|
"Running " + ([frontend.path.string] + arguments).map { "\"\($0)\"" }.joined(separator: " "))
|
|
let process = Process()
|
|
process.executableURL = URL(fileURLWithPath: frontend.path.string)
|
|
process.arguments = arguments
|
|
return process
|
|
}
|