Swift macOS命令行工具调用Homebrew安装的kotlinc命令报错
问题背景
我正在使用Swift为应用开发macOS平台的Command Line Tool Target,需要在该Target中执行通过Homebrew安装的Kotlin命令。
待执行的Kotlin命令如下:
kotlinc -script ./kotlin_file.kts
该命令直接在终端中启动运行时无任何问题,但从Xcode运行该Command Line Tool Target时会触发报错,不同实现方式对应不同报错信息,已尝试多种实现方法,具体如下:
方法1
调用代码:
let test = execute1("kotlinc", args: ["-script", "./kotlin_file.kts"])
实现逻辑:
private func execute1(command: String, args: [String] = []) -> String { let proc = Process() proc.launchPath = command proc.arguments = args let pipe = Pipe() proc.standardOutput = pipe proc.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() return String(data: data, encoding: String.Encoding.utf8)! }
报错信息:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'launch path not accessible'
方法2
调用代码:
let test = execute2("kotlinc", args: ["-script", "./kotlin_file.kts"])
实现逻辑:
private func execute2(_ path: String, args: [String] = []) { let process = Process() process.executableURL = URL(fileURLWithPath: path) process.arguments = args let outputPipe = Pipe() process.standardOutput = outputPipe try? process.run() let output = outputPipe.fileHandleForReading.readDataToEndOfFile() let str = String(decoding: output, as: UTF8.self) print(str) }
问题:程序会卡在readDataToEndOfFile步骤持续无响应。
方法3
将Kotlin命令写入script.sh文件后在Target中执行该shell文件,触发如下报错:
kotlinc: command not found
解决方案
该需求可以实现,所有报错的核心原因有两个:
- 从Xcode启动的Command Line Tool不会加载终端默认的shell环境变量,
PATH中不包含Homebrew的二进制路径(Apple Silicon Mac默认是/opt/homebrew/bin,Intel Mac默认是/usr/local/bin),因此找不到kotlinc命令。 - 没有同时重定向标准错误输出,进程往stderr写内容时如果管道缓冲区满就会阻塞,导致卡在读取输出的步骤。
正确实现代码
import Foundation @discardableResult func runCommand(_ command: String, args: [String] = [], workingDirectory: String? = nil) throws -> (stdout: String, stderr: String, exitCode: Int32) { let process = Process() // 通过zsh执行命令,自动适配shell环境 process.executableURL = URL(fileURLWithPath: "/bin/zsh") // 提前将Homebrew路径加入PATH兜底,拼接完整执行命令 let fullCommand = "export PATH=\"/opt/homebrew/bin:/usr/local/bin:$PATH\" && \(command) \(args.joined(separator: " "))" process.arguments = ["-c", fullCommand] // 配置脚本所在工作目录 if let workingDirectory = workingDirectory { process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory) } let stdoutPipe = Pipe() let stderrPipe = Pipe() process.standardOutput = stdoutPipe process.standardError = stderrPipe try process.run() // 同时读取标准输出和标准错误,避免管道阻塞 let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() process.waitUntilExit() let stdout = String(data: stdoutData, encoding: .utf8) ?? "" let stderr = String(data: stderrData, encoding: .utf8) ?? "" return (stdout, stderr, process.terminationStatus) } // 调用示例 do { let result = try runCommand("kotlinc", args: ["-script", "./kotlin_file.kts"]) print("标准输出:\(result.stdout)") if result.exitCode != 0 { print("执行错误:\(result.stderr)") } } catch { print("进程启动失败:\(error.localizedDescription)") }
注意事项
- 如果需要执行shell脚本,直接将命令参数替换为脚本路径即可,比如
runCommand("./script.sh"),执行前需要给脚本添加可执行权限,终端执行chmod +x script.sh即可。 - 如果
kotlin_file.kts不在Xcode产物的默认执行目录下,要么传入文件绝对路径,要么在调用runCommand时通过workingDirectory参数指定脚本所在目录,避免找不到脚本文件。 - 不要用
try?吞掉process.run()的抛出错误,启动失败时可以直接拿到错误信息快速排查问题。
内容的提问来源于stack exchange,提问作者Xys
相关产品推荐
相关产品推荐

