How can I run a script as part of a Swift Package Manager build?

Viewed 1591

I am converting existing iOS frameworks to modules using Swift Package Manager (SPM); in one case I have a script that was in the Build Phases of the original framework version, that I need to run as part of the new Swift Package Manager build process.

I have tried adding a Pre-action script to the Build portion of the scheme for the module in question (per Ben-xD's comment at https://forums.swift.org/t/how-to-run-a-build-phase-script-when-building-a-standalone-swift-package-in-xcode/40117/8, though his later comment indicates it doesn't work). I found that approach does work if I build the module directly. However, if the module is being built as a dependency of a client app or module (or even as a dependency of its unit tests) that script does not seem to get called.

I am currently relying on the Package.swift manifest file to manage all the module settings, and am using this within Xcode to build and test.

Is there a way to run scripts as part of an SPM module build, so that they will get run reliably, whether the module is built directly or indirectly as a dependency? (Possibly something that can be added to the Package.swift file?)

Update: I have done some additional searching, and came across this: https://forums.swift.org/t/pitch-swiftpm-extensible-build-tools/44715/5 suggesting that it's not possible, but may be in the future. Until then, wondering if there is any workaround to do something as simple as take a file that is autogenerated as part of the SPM build, but rename it before adding it to the resource bundle for the package.

1 Answers

What you might want to try is to execute your script through command line. this snippet could be useful for you:

import Foundation

extension Process {
    public func shell(command: String) -> String {
        launchPath = "/bin/bash"
        arguments = ["-c", command]
        
        let outputPipe = Pipe()
        standardOutput = outputPipe
        launch()
        
        let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
        guard let outputData = String(data: data, encoding: String.Encoding.utf8) else { return "" }
        
        return outputData.characters.reduce("") { (result, value) in
            return result + String(value)
        }
    }
}


public func launch(command: String, arguments: [String]) -> String {
    let process = Process()
    let command = "\(command) \(arguments.joined(separator: " "))"
    return process.shell(command: command)
}
Related