From 2778796f0b108a44423f2441b5d65811efcb76c6 Mon Sep 17 00:00:00 2001 From: Peter Xia Date: Sat, 18 Jul 2026 21:45:31 -0700 Subject: [PATCH 1/4] osxbundle: rewrites osxtools.py and dylib_unhell.py in Swift --- TOOLS/osxbundle.py | 1 + TOOLS/osxbundle.swift | 577 ++++++++++++++++++++++++++++++++++++++++++ meson.build | 2 +- 3 files changed, 579 insertions(+), 1 deletion(-) create mode 100755 TOOLS/osxbundle.swift diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py index c74eb84d1ba16..9b48f08fbad7b 100755 --- a/TOOLS/osxbundle.py +++ b/TOOLS/osxbundle.py @@ -60,6 +60,7 @@ def bundle_version(build_path): return version def main(): + print("DEPRECATED, use osxbundle.swift instead.") usage = "usage: %prog [options] arg" parser = OptionParser(usage) parser.add_option("-s", "--skip-deps", action="store_false", dest="deps", diff --git a/TOOLS/osxbundle.swift b/TOOLS/osxbundle.swift new file mode 100755 index 0000000000000..e7fd6f44e7736 --- /dev/null +++ b/TOOLS/osxbundle.swift @@ -0,0 +1,577 @@ +#!/usr/bin/env swift +// Creates mpv app bundle from binary. + +import Foundation + +// MARK: - process helpers + +struct RuntimeError: Error, CustomStringConvertible { + let message: String + init(_ message: String) { self.message = message } + var description: String { message } +} + +func checkOutput(_ cmd: String, _ args: [String], suppressStderr: Bool = false) throws -> String { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = [cmd] + args + let outPipe = Pipe() + process.standardOutput = outPipe + if suppressStderr { + process.standardError = FileHandle.nullDevice + } + try process.run() + let data = outPipe.fileHandleForReading.readDataToEndOfFile() + process.waitUntilExit() + if process.terminationStatus != 0 { + throw RuntimeError("\(cmd) \(args.joined(separator: " ")) failed") + } + return String(data: data, encoding: .utf8) ?? "" +} + +@discardableResult +func call(_ cmd: String, _ args: [String], suppressStderr: Bool = false, suppressStdout: Bool = false) -> Int32 { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = [cmd] + args + if suppressStderr { + process.standardError = FileHandle.nullDevice + } + if suppressStdout { + process.standardOutput = FileHandle.nullDevice + } + do { + try process.run() + } catch { + return -1 + } + process.waitUntilExit() + return process.terminationStatus +} + +// MARK: - path helpers + +func pathJoin(_ a: String, _ b: String) -> String { + if b.hasPrefix("/") { return b } + if a.isEmpty { return b } + if a.hasSuffix("/") { return a + b } + return a + "/" + b +} + +func normalizePath(_ path: String) -> String { + let isAbsolute = path.hasPrefix("/") + var components: [String] = [] + for part in path.split(separator: "/") { + if part == "." { continue } + if part == ".." { + if let last = components.last, last != ".." { + components.removeLast() + } else if !isAbsolute { + components.append("..") + } + continue + } + components.append(String(part)) + } + let joined = components.joined(separator: "/") + if joined.isEmpty { + return isAbsolute ? "/" : "." + } + return (isAbsolute ? "/" : "") + joined +} + +func absolutePath(_ path: String) -> String { + if path.hasPrefix("/") { return normalizePath(path) } + return normalizePath(pathJoin(FileManager.default.currentDirectoryPath, path)) +} + +// Copies the file the symlink points to, not the symlink itself +func copyFollowingSymlinks(from src: String, to dst: String) throws { + let data = try Data(contentsOf: URL(fileURLWithPath: src)) + try data.write(to: URL(fileURLWithPath: dst)) + if let perms = try? FileManager.default.attributesOfItem(atPath: src)[.posixPermissions] { + try? FileManager.default.setAttributes([.posixPermissions: perms], ofItemAtPath: dst) + } +} + +func basename(_ path: String) -> String { + return (path as NSString).lastPathComponent +} + +func dirname(_ path: String) -> String { + return (path as NSString).deletingLastPathComponent +} + +// MARK: - dylib_unhell + +func isUserLib(_ objfile: String, _ libname: String) -> Bool { + return !libname.hasPrefix("/System") && + !libname.hasPrefix("/usr/lib/") && + !libname.hasPrefix("@executable_path") && + !libname.contains("libobjc.") && + !libname.contains("libSystem.") && + !libname.contains("libc.") && + !libname.contains("libgcc.") && + basename(libname) != "Python" && + !libname.contains(basename(objfile)) && + !libname.contains("libswift") +} + +func resolveLibPath(_ objfile: String, _ lib: String, _ rpaths: [String]) throws -> String { + if FileManager.default.fileExists(atPath: lib) { + return lib + } + + if lib.hasPrefix("@rpath/") { + let rel = String(lib.dropFirst("@rpath/".count)) + for rpath in rpaths { + let libPath = pathJoin(rpath, rel) + if FileManager.default.fileExists(atPath: libPath) { + return libPath + } + } + } else if lib.hasPrefix("@loader_path/") { + let rel = String(lib.dropFirst("@loader_path/".count)) + // mirrors the python join(objfile, rel) + normpath, whose ".." + // collapses against objfile's own basename to land in its directory + let libPath = normalizePath(pathJoin(objfile, rel)) + if FileManager.default.fileExists(atPath: libPath) { + return libPath + } + } + + throw RuntimeError("Could not resolve library: \(lib)") +} + +func otoolLibs(_ objfile: String, _ rpaths: [String]) throws -> (Set, Set) { + let output = try checkOutput("otool", ["-L", objfile]) + var libs = Set() + for line in output.split(separator: "\n", omittingEmptySubsequences: false) { + guard line.hasPrefix("\t") else { continue } + guard let lib = line.trimmingCharacters(in: .whitespaces) + .split(separator: " ").first.map(String.init) else { continue } + if isUserLib(objfile, lib) { + libs.insert(lib) + } + } + + var resolved = Set() + var relative = Set() + for lib in libs { + let libPath = try resolveLibPath(objfile, lib, rpaths) + resolved.insert(libPath) + if libPath != lib { + relative.insert(lib) + } + } + return (resolved, relative) +} + +func iterRpaths(_ objfile: String) throws -> [String] { + let output = try checkOutput("otool", ["-l", objfile]) + var rpaths: [String] = [] + for line in output.split(separator: "\n") { + let trimmed = line.trimmingCharacters(in: .whitespaces) + guard trimmed.hasPrefix("path "), let offsetRange = trimmed.range(of: " (offset ") else { continue } + let start = trimmed.index(trimmed.startIndex, offsetBy: 5) + rpaths.append(String(trimmed[start.. [String] { + let loaderPath = dirname(objfile) + return try iterRpaths(objfile).map { rpath in + guard let range = rpath.range(of: "@loader_path") else { return normalizePath(rpath) } + return normalizePath(rpath.replacingCharacters(in: range, with: loaderPath)) + } +} + +func getRpathsDevTools(_ binary: String) throws -> [String] { + return try iterRpaths(binary).filter { $0.contains("Xcode") || $0.contains("CommandLineTools") } +} + +func checkVulkanMaxVersion(_ version: String) -> Bool { + return call("pkg-config", ["vulkan", "--max-version=\(version)"], suppressStderr: true, suppressStdout: true) == 0 +} + +func getHomebrewPrefix() -> String { + if let output = try? checkOutput("brew", ["--prefix"], suppressStderr: true) { + let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines) + if !trimmed.isEmpty { return trimmed } + } + return "/opt/homebrew" +} + +func installNameToolChange(_ old: String, _ new: String, _ objfile: String) { + call("install_name_tool", ["-change", old, new, objfile], suppressStderr: true) +} + +func installNameToolId(_ name: String, _ objfile: String) { + call("install_name_tool", ["-id", name, objfile], suppressStderr: true) +} + +func installNameToolAddRpath(_ rpath: String, _ binary: String) { + call("install_name_tool", ["-add_rpath", rpath, binary]) +} + +func installNameToolDeleteRpath(_ rpath: String, _ binary: String) { + call("install_name_tool", ["-delete_rpath", rpath, binary]) +} + +func gatherLibraries( + _ objfile: String, + result: inout [String: Set], + resultRelative: inout Set, + rpaths: [String] +) throws { + let allRpaths = try getRpaths(objfile) + rpaths + let (libsList, libsRelative) = try otoolLibs(objfile, allRpaths) + result[objfile] = libsList + resultRelative.formUnion(libsRelative) + + for lib in libsList where result[lib] == nil { + try gatherLibraries(lib, result: &result, resultRelative: &resultRelative, rpaths: allRpaths) + } +} + +func libPath(_ binary: String) -> String { + return pathJoin(dirname(binary), "lib") +} + +func resourcesPath(_ binary: String) -> String { + return pathJoin(dirname(binary), "../Resources") +} + +func libName(_ lib: String) -> String { + return pathJoin("@executable_path", pathJoin("lib", basename(lib))) +} + +func processLibraries(_ libsDict: [String: Set], _ libsDyn: Set, _ binary: String) throws { + var libsSet = Set(libsDict.keys) + libsSet.remove(binary) + + let libDir = libPath(binary) + + for src in libsSet { + let name = libName(src) + let dst = pathJoin(libDir, basename(src)) + + if FileManager.default.fileExists(atPath: dst) { + try FileManager.default.removeItem(atPath: dst) + } + print(">>> copying \(basename(src)) from \(src)") + try copyFollowingSymlinks(from: src, to: dst) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: dst) + installNameToolId(name, dst) + + if libsDict[binary]?.contains(src) == true { + installNameToolChange(src, name, binary) + } + + for p in libsSet where libsDict[src]?.contains(p) == true { + installNameToolChange(p, libName(p), dst) + } + + for lib in libsDyn { + installNameToolChange(lib, libName(lib), dst) + } + } + + for lib in libsDyn { + installNameToolChange(lib, libName(lib), binary) + } +} + +func processSwiftLibraries(_ binary: String) throws { + let swiftStdlibTool = try checkOutput("xcrun", ["--find", "swift-stdlib-tool"]) + .trimmingCharacters(in: .whitespacesAndNewlines) + let swiftLibPath = absolutePath(pathJoin(swiftStdlibTool, "../../lib/swift-5.0/macosx")) + + var args = [ + "--copy", "--platform", "macosx", + "--scan-executable", binary, "--destination", libPath(binary), + ] + + if FileManager.default.fileExists(atPath: swiftLibPath) { + args += ["--source-libraries", swiftLibPath] + } + + _ = try checkOutput(swiftStdlibTool, args) + + print(">> setting additional rpath for swift libraries") + installNameToolAddRpath("@executable_path/lib", binary) +} + +func processVulkanLoader( + _ binary: String, + _ loaderName: String, + _ loaderRelativeFolder: String, + _ libraryNode: String +) throws { + let homebrewPrefix = getHomebrewPrefix() + let home = NSHomeDirectory() + let loaderSystemSearchFolders = [ + pathJoin(home, pathJoin(".config", loaderRelativeFolder)), + pathJoin("/etc/xdg", loaderRelativeFolder), + pathJoin("/usr/local/etc", loaderRelativeFolder), + pathJoin("/etc", loaderRelativeFolder), + pathJoin(home, pathJoin(".local/share", loaderRelativeFolder)), + pathJoin("/usr/local/share", loaderRelativeFolder), + pathJoin("/usr/share", loaderRelativeFolder), + pathJoin(homebrewPrefix, pathJoin("etc", loaderRelativeFolder)), + pathJoin(homebrewPrefix, pathJoin("share", loaderRelativeFolder)), + ] + + var loaderSystemFolder = "" + var loaderSystemPath = "" + for folder in loaderSystemSearchFolders { + if FileManager.default.fileExists(atPath: folder) { + loaderSystemFolder = folder + let candidate = pathJoin(folder, loaderName) + if FileManager.default.fileExists(atPath: candidate) { + loaderSystemPath = candidate + break + } + } + } + + if loaderSystemFolder.isEmpty { + print(">>> could not find loader folder " + loaderRelativeFolder) + return + } + if loaderSystemPath.isEmpty { + print(">>> could not find loader " + loaderName) + return + } + + let loaderBundleFolder = pathJoin(resourcesPath(binary), loaderRelativeFolder) + let loaderBundlePath = pathJoin(loaderBundleFolder, loaderName) + let libraryRelativeFolder = "../../../Frameworks/" + + if !FileManager.default.fileExists(atPath: loaderBundleFolder) { + try FileManager.default.createDirectory(atPath: loaderBundleFolder, withIntermediateDirectories: true) + } + + let loaderData = try Data(contentsOf: URL(fileURLWithPath: loaderSystemPath)) + guard var loaderJson = try JSONSerialization.jsonObject(with: loaderData) as? [String: Any], + var node = loaderJson[libraryNode] as? [String: Any], + let libraryPathValue = node["library_path"] as? String + else { + print(">>> could not parse loader json " + loaderName) + return + } + + let librarySystemPath = pathJoin(loaderSystemFolder, libraryPathValue) + if !FileManager.default.fileExists(atPath: librarySystemPath) { + print(">>> could not find loader library " + librarySystemPath) + return + } + + print(">>> modifying and writing loader json " + loaderName) + let loaderLibraryName = basename(librarySystemPath) + node["library_path"] = pathJoin(libraryRelativeFolder, loaderLibraryName) + loaderJson[libraryNode] = node + let outData = try JSONSerialization.data(withJSONObject: loaderJson, options: [.prettyPrinted]) + try outData.write(to: URL(fileURLWithPath: loaderBundlePath)) + + print(">>> copying loader library " + loaderLibraryName) + let frameworkBundleFolder = pathJoin(loaderBundleFolder, libraryRelativeFolder) + if !FileManager.default.fileExists(atPath: frameworkBundleFolder) { + try FileManager.default.createDirectory(atPath: frameworkBundleFolder, withIntermediateDirectories: true) + } + let libraryTargetPath = pathJoin(frameworkBundleFolder, loaderLibraryName) + if FileManager.default.fileExists(atPath: libraryTargetPath) { + try FileManager.default.removeItem(atPath: libraryTargetPath) + } + try copyFollowingSymlinks(from: librarySystemPath, to: libraryTargetPath) +} + +func removeDevToolsRpaths(_ binary: String) throws { + for path in try getRpathsDevTools(binary) { + installNameToolDeleteRpath(path, binary) + } +} + +func dylibUnhell(_ binaryArg: String) throws { + let binary = absolutePath(binaryArg) + let ldir = libPath(binary) + if !FileManager.default.fileExists(atPath: ldir) { + try FileManager.default.createDirectory(atPath: ldir, withIntermediateDirectories: true) + } + + print(">> gathering all linked libraries") + var libs: [String: Set] = [:] + var libsRel: Set = [] + try gatherLibraries(binary, result: &libs, resultRelative: &libsRel, rpaths: []) + + print(">> copying and processing all linked libraries") + try processLibraries(libs, libsRel, binary) + + print(">> removing rpath definitions towards dev tools") + try removeDevToolsRpaths(binary) + + print(">> copying and processing swift libraries") + try processSwiftLibraries(binary) + + print(">> copying and processing vulkan loader") + try processVulkanLoader(binary, "MoltenVK_icd.json", "vulkan/icd.d", "ICD") + if checkVulkanMaxVersion("1.3.261.1") { + try processVulkanLoader( + binary, "VkLayer_khronos_synchronization2.json", "vulkan/explicit_layer.d", "layer" + ) + } +} + +// MARK: - osxbundle + +func bundlePath(_ binaryName: String) -> String { + return "\(binaryName).app" +} + +func bundleNameOf(_ binaryName: String) -> String { + return basename(bundlePath(binaryName)) +} + +func targetPlist(_ binaryName: String) -> String { + return pathJoin(bundlePath(binaryName), "Contents/Info.plist") +} + +func targetDirectory(_ binaryName: String) -> String { + return pathJoin(bundlePath(binaryName), "Contents/MacOS") +} + +func targetBinary(_ binaryName: String) -> String { + return pathJoin(targetDirectory(binaryName), basename(binaryName)) +} + +func copyBundle(_ binaryName: String, _ srcPath: String) throws { + let dst = bundlePath(binaryName) + var isDir: ObjCBool = false + if FileManager.default.fileExists(atPath: dst, isDirectory: &isDir), isDir.boolValue { + try FileManager.default.removeItem(atPath: dst) + } + let src = pathJoin(pathJoin(srcPath, "TOOLS/osxbundle"), bundleNameOf(binaryName)) + try FileManager.default.copyItem(atPath: src, toPath: dst) +} + +func copyBinary(_ binaryName: String) throws { + let dst = targetBinary(binaryName) + if FileManager.default.fileExists(atPath: dst) { + try FileManager.default.removeItem(atPath: dst) + } + try FileManager.default.copyItem(atPath: binaryName, toPath: dst) +} + +func applyPlistTemplate(_ plistFile: String, _ version: String, _ category: String) throws { + print(">> setting bundle category to " + category) + let content = try String(contentsOfFile: plistFile, encoding: .utf8) + let newContent = content + .replacingOccurrences(of: "${VERSION}", with: version) + .replacingOccurrences(of: "${CATEGORY}", with: category) + try newContent.write(toFile: plistFile, atomically: true, encoding: .utf8) +} + +func signBundle(_ binaryName: String) throws { + for signDir in ["Contents/Frameworks", "Contents/MacOS"] { + let resolvedDir = pathJoin(bundlePath(binaryName), signDir) + guard let enumerator = FileManager.default.enumerator(atPath: resolvedDir) else { continue } + for case let relPath as String in enumerator { + let fullPath = pathJoin(resolvedDir, relPath) + var isDirectory: ObjCBool = false + if FileManager.default.fileExists(atPath: fullPath, isDirectory: &isDirectory), !isDirectory.boolValue { + call("codesign", ["--force", "-s", "-", fullPath]) + } + } + } + call("codesign", ["--force", "-s", "-", bundlePath(binaryName)]) +} + +func bundleVersion(_ buildPath: String) -> String { + let versionHPath = pathJoin(buildPath, "common/version.h") + guard let content = try? String(contentsOfFile: versionHPath, encoding: .utf8) else { + return "UNKNOWN" + } + guard let regex = try? NSRegularExpression(pattern: "#define\\s+VERSION\\s+\"v(.+)\""), + let match = regex.firstMatch(in: content, range: NSRange(content.startIndex..., in: content)), + let range = Range(match.range(at: 1), in: content) + else { + return "UNKNOWN" + } + return String(content[range]) +} + +// MARK: - CLI + +func printUsageAndExit(_ message: String) -> Never { + let usage = "usage: osxbundle.swift [-s|--skip-deps] [-c|--category video|games] binary [src_path]\n" + FileHandle.standardError.write(usage.data(using: .utf8)!) + if !message.isEmpty { + FileHandle.standardError.write((message + "\n").data(using: .utf8)!) + } + exit(2) +} + +var deps = true +var category = "video" +var positional: [String] = [] + +let args = Array(CommandLine.arguments.dropFirst()) +var i = 0 +while i < args.count { + let arg = args[i] + switch arg { + case "-s", "--skip-deps": + deps = false + case "-c", "--category": + i += 1 + guard i < args.count else { printUsageAndExit("option \(arg) requires an argument") } + category = args[i] + default: + if arg.hasPrefix("--category=") { + category = String(arg.dropFirst("--category=".count)) + } else if arg.hasPrefix("-") && arg != "-" { + printUsageAndExit("unknown option: \(arg)") + } else { + positional.append(arg) + } + } + i += 1 +} + +guard ["video", "games"].contains(category) else { + printUsageAndExit("invalid category: \(category) (choose from 'video', 'games')") +} + +guard positional.count == 1 || positional.count == 2 else { + printUsageAndExit("incorrect number of arguments") +} + +let binaryName = positional[0] +let buildPath = dirname(binaryName) +let srcPath = positional.count > 1 ? positional[1] : "." + +do { + let version = bundleVersion(buildPath).trimmingCharacters(in: .whitespacesAndNewlines) + + print("Creating macOS application bundle (version: \(version))...") + print("> copying bundle skeleton") + try copyBundle(binaryName, srcPath) + print("> copying binary") + try copyBinary(binaryName) + print("> generating Info.plist") + try applyPlistTemplate(targetPlist(binaryName), version, category) + + if deps { + print("> bundling dependencies") + try dylibUnhell(targetBinary(binaryName)) + } + + print("> signing bundle with ad-hoc pseudo identity") + try signBundle(binaryName) + + print("done.") +} catch { + FileHandle.standardError.write("error: \(error)\n".data(using: .utf8)!) + exit(1) +} diff --git a/meson.build b/meson.build index 80bd39d7ec3bf..5896d8b508f13 100644 --- a/meson.build +++ b/meson.build @@ -1889,7 +1889,7 @@ if get_option('cplayer') endif if darwin - osxbundle = find_program(join_paths(tools_directory, 'osxbundle.py'), required: true) + osxbundle = find_program(join_paths(tools_directory, 'osxbundle.swift'), required: true) custom_target('macos-bundle', output: 'mpv.app', depends: version_h, From 6123ee2fab6b7010a75835f2d4458f308fc2f3ac Mon Sep 17 00:00:00 2001 From: Peter Xia Date: Sat, 18 Jul 2026 21:46:23 -0700 Subject: [PATCH 2/4] osxbundle: makes isUserLib more robust So it no longer depends on library install path. Some users (like me) have custom homebrew prefix that the old way might cause libraries to be incorrectly skipped. --- TOOLS/osxbundle.swift | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/TOOLS/osxbundle.swift b/TOOLS/osxbundle.swift index e7fd6f44e7736..3455238ad7500 100755 --- a/TOOLS/osxbundle.swift +++ b/TOOLS/osxbundle.swift @@ -104,17 +104,27 @@ func dirname(_ path: String) -> String { // MARK: - dylib_unhell -func isUserLib(_ objfile: String, _ libname: String) -> Bool { +func isUserLib(_ libname: String) -> Bool { + let base = basename(libname) return !libname.hasPrefix("/System") && !libname.hasPrefix("/usr/lib/") && !libname.hasPrefix("@executable_path") && - !libname.contains("libobjc.") && - !libname.contains("libSystem.") && - !libname.contains("libc.") && - !libname.contains("libgcc.") && - basename(libname) != "Python" && - !libname.contains(basename(objfile)) && - !libname.contains("libswift") + !base.contains("libobjc.") && + !base.contains("libSystem.") && + !base.contains("libc.") && + !base.contains("libgcc.") && + base != "Python" && + !base.contains("libswift") +} + +// dylibs list their own LC_ID_DYLIB as the first `otool -L` entry; executables +// have none. Matching it exactly (via `otool -D`) avoids excluding real +// dependencies whose path merely happens to contain the binary's basename. +func otoolSelfId(_ objfile: String) throws -> String? { + let output = try checkOutput("otool", ["-D", objfile]) + let lines = output.split(separator: "\n").map(String.init) + guard lines.count >= 2 else { return nil } + return lines[1].trimmingCharacters(in: .whitespaces) } func resolveLibPath(_ objfile: String, _ lib: String, _ rpaths: [String]) throws -> String { @@ -145,12 +155,14 @@ func resolveLibPath(_ objfile: String, _ lib: String, _ rpaths: [String]) throws func otoolLibs(_ objfile: String, _ rpaths: [String]) throws -> (Set, Set) { let output = try checkOutput("otool", ["-L", objfile]) + let selfId = try otoolSelfId(objfile) var libs = Set() for line in output.split(separator: "\n", omittingEmptySubsequences: false) { guard line.hasPrefix("\t") else { continue } guard let lib = line.trimmingCharacters(in: .whitespaces) .split(separator: " ").first.map(String.init) else { continue } - if isUserLib(objfile, lib) { + if lib == selfId { continue } + if isUserLib(lib) { libs.insert(lib) } } From de5e7cb38d5358c1eedc34ab420a624b15e07a9d Mon Sep 17 00:00:00 2001 From: Peter Xia Date: Sat, 18 Jul 2026 22:00:52 -0700 Subject: [PATCH 3/4] macos-bundle: rename osxbundle to macos-bundle --- TOOLS/{osxbundle.swift => macos-bundle.swift} | 2 +- TOOLS/osxbundle.py | 2 +- meson.build | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename TOOLS/{osxbundle.swift => macos-bundle.swift} (99%) diff --git a/TOOLS/osxbundle.swift b/TOOLS/macos-bundle.swift similarity index 99% rename from TOOLS/osxbundle.swift rename to TOOLS/macos-bundle.swift index 3455238ad7500..5ff0c83a78793 100755 --- a/TOOLS/osxbundle.swift +++ b/TOOLS/macos-bundle.swift @@ -516,7 +516,7 @@ func bundleVersion(_ buildPath: String) -> String { // MARK: - CLI func printUsageAndExit(_ message: String) -> Never { - let usage = "usage: osxbundle.swift [-s|--skip-deps] [-c|--category video|games] binary [src_path]\n" + let usage = "usage: macos-bundle.swift [-s|--skip-deps] [-c|--category video|games] binary [src_path]\n" FileHandle.standardError.write(usage.data(using: .utf8)!) if !message.isEmpty { FileHandle.standardError.write((message + "\n").data(using: .utf8)!) diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py index 9b48f08fbad7b..70513fb9ed509 100755 --- a/TOOLS/osxbundle.py +++ b/TOOLS/osxbundle.py @@ -60,7 +60,7 @@ def bundle_version(build_path): return version def main(): - print("DEPRECATED, use osxbundle.swift instead.") + print("DEPRECATED, use macos-bundle.swift instead.") usage = "usage: %prog [options] arg" parser = OptionParser(usage) parser.add_option("-s", "--skip-deps", action="store_false", dest="deps", diff --git a/meson.build b/meson.build index 5896d8b508f13..524c514930004 100644 --- a/meson.build +++ b/meson.build @@ -1889,11 +1889,11 @@ if get_option('cplayer') endif if darwin - osxbundle = find_program(join_paths(tools_directory, 'osxbundle.swift'), required: true) + macos_bundle = find_program(join_paths(tools_directory, 'macos-bundle.swift'), required: true) custom_target('macos-bundle', output: 'mpv.app', depends: version_h, - command: [osxbundle, mpv, '@SOURCE_ROOT@', '-c', get_option('macos-bundle-category')], + command: [macos_bundle, mpv, '@SOURCE_ROOT@', '-c', get_option('macos-bundle-category')], ) endif From ac03d5e64d4f4d4dc275d3d3ea6d99253f4fea3f Mon Sep 17 00:00:00 2001 From: Peter Xia Date: Sun, 19 Jul 2026 10:48:48 -0700 Subject: [PATCH 4/4] macos-bundle: swiftlint --- TOOLS/macos-bundle.swift | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/TOOLS/macos-bundle.swift b/TOOLS/macos-bundle.swift index 5ff0c83a78793..36653327ed87c 100755 --- a/TOOLS/macos-bundle.swift +++ b/TOOLS/macos-bundle.swift @@ -302,7 +302,7 @@ func processSwiftLibraries(_ binary: String) throws { var args = [ "--copy", "--platform", "macosx", - "--scan-executable", binary, "--destination", libPath(binary), + "--scan-executable", binary, "--destination", libPath(binary) ] if FileManager.default.fileExists(atPath: swiftLibPath) { @@ -332,19 +332,17 @@ func processVulkanLoader( pathJoin("/usr/local/share", loaderRelativeFolder), pathJoin("/usr/share", loaderRelativeFolder), pathJoin(homebrewPrefix, pathJoin("etc", loaderRelativeFolder)), - pathJoin(homebrewPrefix, pathJoin("share", loaderRelativeFolder)), + pathJoin(homebrewPrefix, pathJoin("share", loaderRelativeFolder)) ] var loaderSystemFolder = "" var loaderSystemPath = "" - for folder in loaderSystemSearchFolders { - if FileManager.default.fileExists(atPath: folder) { - loaderSystemFolder = folder - let candidate = pathJoin(folder, loaderName) - if FileManager.default.fileExists(atPath: candidate) { - loaderSystemPath = candidate - break - } + for folder in loaderSystemSearchFolders where FileManager.default.fileExists(atPath: folder) { + loaderSystemFolder = folder + let candidate = pathJoin(folder, loaderName) + if FileManager.default.fileExists(atPath: candidate) { + loaderSystemPath = candidate + break } } @@ -517,9 +515,9 @@ func bundleVersion(_ buildPath: String) -> String { func printUsageAndExit(_ message: String) -> Never { let usage = "usage: macos-bundle.swift [-s|--skip-deps] [-c|--category video|games] binary [src_path]\n" - FileHandle.standardError.write(usage.data(using: .utf8)!) + FileHandle.standardError.write(Data(usage.utf8)) if !message.isEmpty { - FileHandle.standardError.write((message + "\n").data(using: .utf8)!) + FileHandle.standardError.write(Data((message + "\n").utf8)) } exit(2) } @@ -584,6 +582,6 @@ do { print("done.") } catch { - FileHandle.standardError.write("error: \(error)\n".data(using: .utf8)!) + FileHandle.standardError.write(Data("error: \(error)\n".utf8)) exit(1) }