diff options
Diffstat (limited to 'TOOLS')
47 files changed, 2645 insertions, 1269 deletions
diff --git a/TOOLS/__init__.py b/TOOLS/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 --- a/TOOLS/__init__.py +++ /dev/null diff --git a/TOOLS/appveyor-build.sh b/TOOLS/appveyor-build.sh deleted file mode 100755 index f6c94fb90b..0000000000 --- a/TOOLS/appveyor-build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/bash -set -e - -export DEST_OS=win32 -export CC=gcc -export PKG_CONFIG=/usr/bin/pkg-config -export PERL=/usr/bin/perl -export PYTHON=/usr/bin/python3 - -"$PYTHON" bootstrap.py -"$PYTHON" waf configure \ - --check-c-compiler=gcc \ - --disable-cdda \ - --enable-egl-angle \ - --enable-jpeg \ - --enable-lcms2 \ - --enable-libarchive \ - --enable-libass \ - --enable-lua \ - --enable-rubberband \ - --enable-uchardet -"$PYTHON" waf build diff --git a/TOOLS/appveyor-install.sh b/TOOLS/appveyor-install.sh deleted file mode 100755 index b0d276eab7..0000000000 --- a/TOOLS/appveyor-install.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/bash -set -e - -case $MSYSTEM in -MINGW32) - export MINGW_PACKAGE_PREFIX=mingw-w64-i686 - ;; -MINGW64) - export MINGW_PACKAGE_PREFIX=mingw-w64-x86_64 - ;; -esac - -# Write an empty fonts.conf to speed up fc-cache -export FONTCONFIG_FILE=/dummy-fonts.conf -cat >"$FONTCONFIG_FILE" <<EOF -<?xml version="1.0"?> -<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> -<fontconfig></fontconfig> -EOF - -# Install build dependencies for mpv -pacman -S --noconfirm --needed \ - $MINGW_PACKAGE_PREFIX-gcc \ - $MINGW_PACKAGE_PREFIX-angleproject-git \ - $MINGW_PACKAGE_PREFIX-ffmpeg \ - $MINGW_PACKAGE_PREFIX-lcms2 \ - $MINGW_PACKAGE_PREFIX-libarchive \ - $MINGW_PACKAGE_PREFIX-libass \ - $MINGW_PACKAGE_PREFIX-libjpeg-turbo \ - $MINGW_PACKAGE_PREFIX-lua51 \ - $MINGW_PACKAGE_PREFIX-rubberband \ - $MINGW_PACKAGE_PREFIX-uchardet - -# Delete unused packages to reduce space used in the Appveyor cache -pacman -Sc --noconfirm diff --git a/TOOLS/docutils-wrapper.py b/TOOLS/docutils-wrapper.py new file mode 100755 index 0000000000..31ba976e64 --- /dev/null +++ b/TOOLS/docutils-wrapper.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +Wrapper around docutils rst2x commands, +converting their dependency files to a format understood by meson/ninja. +""" + +# +# This file is part of mpv. +# +# mpv is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# mpv is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with mpv. If not, see <http://www.gnu.org/licenses/>. +# + +import os +import subprocess +import sys + + +def convert_depfile(output, depfile): + with open(depfile, 'r') as f: + deps = f.readlines() + + with open(depfile, 'w') as f: + f.write(os.path.abspath(output)) + f.write(': \\\n') + for dep in deps: + dep = dep[:-1] + f.write('\t') + f.write(os.path.abspath(dep)) + f.write(' \\\n') + +def remove(path): + try: + os.remove(path) + except FileNotFoundError: + pass + +argv = sys.argv[1:] + +depfile = None +output = argv[-1] + +for opt, optarg in zip(argv, argv[1:]): + if opt == '--record-dependencies': + depfile = optarg + +try: + proc = subprocess.run(argv, check=True) + if depfile is not None: + convert_depfile(output, depfile) +except: + remove(output) + if depfile is not None: + remove(depfile) + sys.exit(1) + +sys.exit(proc.returncode) diff --git a/TOOLS/dylib-unhell.py b/TOOLS/dylib-unhell.py deleted file mode 100755 index 3ba0fc39e6..0000000000 --- a/TOOLS/dylib-unhell.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python - -import re -import os -import sys -import shutil -import subprocess -from functools import partial - -sys_re = re.compile("^/System") -usr_re = re.compile("^/usr/lib/") -exe_re = re.compile("@executable_path") - -def is_user_lib(objfile, libname): - return not sys_re.match(libname) and \ - not usr_re.match(libname) and \ - not exe_re.match(libname) and \ - not "libobjc." in libname and \ - not "libSystem." in libname and \ - not "libc." in libname and \ - not "libgcc." in libname and \ - not os.path.basename(libname) == 'Python' and \ - not os.path.basename(objfile) in libname - -def otool(objfile): - command = "otool -L %s | grep -e '\t' | awk '{ print $1 }'" % objfile - output = subprocess.check_output(command, shell = True) - return filter(partial(is_user_lib, objfile), output.split()) - -def install_name_tool_change(old, new, objfile): - subprocess.call(["install_name_tool", "-change", old, new, objfile]) - -def install_name_tool_id(name, objfile): - subprocess.call(["install_name_tool", "-id", name, objfile]) - -def libraries(objfile, result = dict()): - libs_list = otool(objfile) - result[objfile] = set(libs_list) - - for lib in libs_list: - if lib not in result: - libraries(lib, result) - - return result - -def leafs(libs_dict, processed = []): - result = [] - processed = set(processed) - - for objfile, libs in libs_dict.items(): - if libs <= processed: - result.append(objfile) - - return result - -def lib_path(binary): - return os.path.join(os.path.dirname(binary), 'lib') - -def lib_name(lib): - return os.path.join("@executable_path", "lib", os.path.basename(lib)) - -def process_libraries(libs_dict, binary, processed = []): - ls = leafs(libs_dict, processed) - diff = set(ls) - set(processed) - if diff == set([binary]): - return - - for src in diff: - name = lib_name(src) - dst = os.path.join(lib_path(binary), os.path.basename(src)) - - shutil.copy(src, dst) - os.chmod(dst, 0o755) - install_name_tool_id(name, dst) - - if src in libs_dict[binary]: - install_name_tool_change(src, name, binary) - - for p in processed: - if p in libs_dict[src]: - install_name_tool_change(p, lib_name(p), dst) - - process_libraries(libs_dict, binary, ls) - -def main(): - binary = os.path.abspath(sys.argv[1]) - if not os.path.exists(lib_path(binary)): - os.makedirs(lib_path(binary)) - libs = libraries(binary) - print(libs) - process_libraries(libs, binary) - -if __name__ == "__main__": - main() diff --git a/TOOLS/dylib_unhell.py b/TOOLS/dylib_unhell.py new file mode 100755 index 0000000000..c885969284 --- /dev/null +++ b/TOOLS/dylib_unhell.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 + +import re +import os +import sys +import shutil +import subprocess +import json +from functools import partial + +sys_re = re.compile("^/System") +usr_re = re.compile("^/usr/lib/") +exe_re = re.compile("@executable_path") + +def is_user_lib(objfile, libname): + return not sys_re.match(libname) and \ + not usr_re.match(libname) and \ + not exe_re.match(libname) and \ + not "libobjc." in libname and \ + not "libSystem." in libname and \ + not "libc." in libname and \ + not "libgcc." in libname and \ + not os.path.basename(libname) == 'Python' and \ + not os.path.basename(objfile) in libname and \ + not "libswift" in libname + +def otool(objfile, rapths): + command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile + output = subprocess.check_output(command, shell = True, universal_newlines=True) + libs = set(filter(partial(is_user_lib, objfile), output.split())) + + libs_resolved = set() + libs_relative = set() + for lib in libs: + lib_path = resolve_lib_path(objfile, lib, rapths) + libs_resolved.add(lib_path) + if lib_path != lib: + libs_relative.add(lib) + + return libs_resolved, libs_relative + +def get_rapths(objfile): + rpaths = [] + command = "otool -l '%s' | grep -A2 LC_RPATH | grep path" % objfile + pathRe = re.compile(r"^\s*path (.*) \(offset \d*\)$") + + try: + result = subprocess.check_output(command, shell = True, universal_newlines=True) + except: + return rpaths + + for line in result.splitlines(): + line_clean = pathRe.search(line).group(1).strip() + # resolve @loader_path + if line_clean.startswith('@loader_path/'): + line_clean = line_clean[len('@loader_path/'):] + line_clean = os.path.normpath(os.path.join(os.path.dirname(objfile), line_clean)) + rpaths.append(line_clean) + + return rpaths + +def get_rpaths_dev_tools(binary): + command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\\|CommandLineTools\"" % binary + result = subprocess.check_output(command, shell = True, universal_newlines=True) + pathRe = re.compile(r"^\s*path (.*) \(offset \d*\)$") + output = [] + + for line in result.splitlines(): + output.append(pathRe.search(line).group(1).strip()) + + return output + +def resolve_lib_path(objfile, lib, rapths): + if os.path.exists(lib): + return lib + + if lib.startswith('@rpath/'): + lib = lib[len('@rpath/'):] + for rpath in rapths: + lib_path = os.path.join(rpath, lib) + if os.path.exists(lib_path): + return lib_path + elif lib.startswith('@loader_path/'): + lib = lib[len('@loader_path/'):] + lib_path = os.path.normpath(os.path.join(objfile, lib)) + if os.path.exists(lib_path): + return lib_path + + raise Exception('Could not resolve library: ' + lib) + +def check_vulkan_max_version(version): + try: + result = subprocess.check_output("pkg-config vulkan --max-version=" + version, shell = True) + return True + except: + return False + +def get_homebrew_prefix(): + # set default to standard ARM path, intel path is already in the vulkan loader search array + result = "/opt/homebrew" + try: + result = subprocess.check_output("brew --prefix", universal_newlines=True, shell=True, stderr=subprocess.DEVNULL).strip() + except: + pass + + return result + +def install_name_tool_change(old, new, objfile): + subprocess.call(["install_name_tool", "-change", old, new, objfile], stderr=subprocess.DEVNULL) + +def install_name_tool_id(name, objfile): + subprocess.call(["install_name_tool", "-id", name, objfile], stderr=subprocess.DEVNULL) + +def install_name_tool_add_rpath(rpath, binary): + subprocess.call(["install_name_tool", "-add_rpath", rpath, binary]) + +def install_name_tool_delete_rpath(rpath, binary): + subprocess.call(["install_name_tool", "-delete_rpath", rpath, binary]) + +def libraries(objfile, result = dict(), result_relative = set(), rapths = []): + rapths = get_rapths(objfile) + rapths + libs_list, libs_relative = otool(objfile, rapths) + result[objfile] = libs_list + result_relative |= libs_relative + + for lib in libs_list: + if lib not in result: + libraries(lib, result, result_relative, rapths) + + return result, result_relative + +def lib_path(binary): + return os.path.join(os.path.dirname(binary), 'lib') + +def resources_path(binary): + return os.path.join(os.path.dirname(binary), '../Resources') + +def lib_name(lib): + return os.path.join("@executable_path", "lib", os.path.basename(lib)) + +def process_libraries(libs_dict, libs_dyn, binary): + libs_set = set(libs_dict) + # Remove binary from libs_set to prevent a duplicate of the binary being + # added to the libs directory. + libs_set.remove(binary) + + for src in libs_set: + name = lib_name(src) + dst = os.path.join(lib_path(binary), os.path.basename(src)) + + shutil.copy(src, dst) + os.chmod(dst, 0o755) + install_name_tool_id(name, dst) + + if src in libs_dict[binary]: + install_name_tool_change(src, name, binary) + + for p in libs_set: + if p in libs_dict[src]: + install_name_tool_change(p, lib_name(p), dst) + + for lib in libs_dyn: + install_name_tool_change(lib, lib_name(lib), dst) + + for lib in libs_dyn: + install_name_tool_change(lib, lib_name(lib), binary) + +def process_swift_libraries(binary): + command = ['xcrun', '--find', 'swift-stdlib-tool'] + swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip() + # from xcode11 on the dynamic swift libs reside in a separate directory from + # the std one, might need versioned paths for future swift versions + swiftLibPath = os.path.join(swiftStdlibTool, '../../lib/swift-5.0/macosx') + swiftLibPath = os.path.abspath(swiftLibPath) + + command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)] + + if os.path.exists(swiftLibPath): + command.extend(['--source-libraries', swiftLibPath]) + + subprocess.check_output(command, universal_newlines=True) + + print(">> setting additional rpath for swift libraries") + install_name_tool_add_rpath("@executable_path/lib", binary) + +def process_vulkan_loader(binary, loaderName, loaderRelativeFolder, libraryNode): + # https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderDriverInterface.md#example-macos-driver-search-path + # https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderLayerInterface.md#macos-layer-discovery + loaderSystemSearchFolders = [ + os.path.join(os.path.expanduser("~"), ".config", loaderRelativeFolder), + os.path.join("/etc/xdg", loaderRelativeFolder), + os.path.join("/usr/local/etc", loaderRelativeFolder), + os.path.join("/etc", loaderRelativeFolder), + os.path.join(os.path.expanduser("~"), ".local/share", loaderRelativeFolder), + os.path.join("/usr/local/share", loaderRelativeFolder), + os.path.join("/usr/share/vulkan", loaderRelativeFolder), + os.path.join(get_homebrew_prefix(), 'share', loaderRelativeFolder), + ] + + loaderSystemFolder = "" + for loaderSystemSearchFolder in loaderSystemSearchFolders: + if os.path.exists(loaderSystemSearchFolder): + loaderSystemFolder = loaderSystemSearchFolder + break + + if not loaderSystemFolder: + print(">>> could not find loader folder " + loaderRelativeFolder) + return + + loaderBundleFolder = os.path.join(resources_path(binary), loaderRelativeFolder) + loaderSystemPath = os.path.join(loaderSystemFolder, loaderName) + loaderBundlePath = os.path.join(loaderBundleFolder, loaderName) + libraryRelativeFolder = "../../../Frameworks/" + + if not os.path.exists(loaderSystemPath): + print(">>> could not find loader " + loaderName) + return + + if not os.path.exists(loaderBundleFolder): + os.makedirs(loaderBundleFolder) + + loaderSystemFile = open(loaderSystemPath, 'r') + loaderJsonData = json.load(loaderSystemFile) + librarySystemPath = os.path.join(loaderSystemFolder, loaderJsonData[libraryNode]["library_path"]) + + if not os.path.exists(librarySystemPath): + print(">>> could not find loader library " + librarySystemPath) + return + + print(">>> modifiying and writing loader json " + loaderName) + loaderBundleFile = open(loaderBundlePath, 'w') + loaderLibraryName = os.path.basename(librarySystemPath) + loaderJsonData[libraryNode]["library_path"] = os.path.join(libraryRelativeFolder, loaderLibraryName) + json.dump(loaderJsonData, loaderBundleFile, indent=4) + + print(">>> copying loader library " + loaderLibraryName) + frameworkBundleFolder = os.path.join(loaderBundleFolder, libraryRelativeFolder) + if not os.path.exists(frameworkBundleFolder): + os.makedirs(frameworkBundleFolder) + shutil.copy(librarySystemPath, os.path.join(frameworkBundleFolder, loaderLibraryName)) + +def remove_dev_tools_rapths(binary): + for path in get_rpaths_dev_tools(binary): + install_name_tool_delete_rpath(path, binary) + +def process(binary): + binary = os.path.abspath(binary) + if not os.path.exists(lib_path(binary)): + os.makedirs(lib_path(binary)) + print(">> gathering all linked libraries") + libs, libs_rel = libraries(binary) + + print(">> copying and processing all linked libraries") + process_libraries(libs, libs_rel, binary) + + print(">> removing rpath definitions towards dev tools") + remove_dev_tools_rapths(binary) + + print(">> copying and processing swift libraries") + process_swift_libraries(binary) + + print(">> copying and processing vulkan loader") + process_vulkan_loader(binary, "MoltenVK_icd.json", "vulkan/icd.d", "ICD") + if check_vulkan_max_version("1.3.261.1"): + process_vulkan_loader(binary, "VkLayer_khronos_synchronization2.json", "vulkan/explicit_layer.d", "layer") + +if __name__ == "__main__": + process(sys.argv[1]) diff --git a/TOOLS/file2string.py b/TOOLS/file2string.py index b40317218c..5b1c4a95d1 100755 --- a/TOOLS/file2string.py +++ b/TOOLS/file2string.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Convert the contents of a file into a C string constant. # Note that the compiler will implicitly add an extra 0 byte at the end @@ -22,27 +22,23 @@ # License along with mpv. If not, see <http://www.gnu.org/licenses/>. # -from __future__ import unicode_literals import sys -# Indexing a byte string yields int on Python 3.x, and a str on Python 2.x -def pord(c): - return ord(c) if type(c) == str else c - def file2string(infilename, infile, outfile): outfile.write("// Generated from %s\n\n" % infilename) - conv = ['\\' + ("%03o" % c) for c in range(256)] + conv = ["\\%03o" % c for c in range(256)] safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \ - "0123456789!#%&'()*+,-./:;<=>?[]^_{|}~ " + "0123456789!#%&'()*+,-./:;<=>[]^_{|}~ " for c in safe_chars: conv[ord(c)] = c for c, esc in ("\nn", "\tt", r"\\", '""'): conv[ord(c)] = '\\' + esc for line in infile: - outfile.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n') + outfile.write('"' + ''.join(conv[c] for c in line) + '"\n') if __name__ == "__main__": + outfile = open(sys.argv[2], "w") with open(sys.argv[1], 'rb') as infile: - file2string(sys.argv[1], infile, sys.stdout) + file2string(sys.argv[1], infile, outfile) diff --git a/TOOLS/gen-interface-changes.py b/TOOLS/gen-interface-changes.py new file mode 100755 index 0000000000..3a415192af --- /dev/null +++ b/TOOLS/gen-interface-changes.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +# Generate a new interface-changes.rst based on the entries in +# the interface-changes directory. + +# +# This file is part of mpv. +# +# mpv is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# mpv is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with mpv. If not, see <http://www.gnu.org/licenses/>. +# + +import pathlib +import sys +import textwrap +from shutil import which +from subprocess import check_output + +def add_new_entries(docs_dir, out, git): + changes_dir = pathlib.Path(docs_dir) / "interface-changes" + files = [] + for f in pathlib.Path(changes_dir).glob("*.txt"): + if f.is_file() and not f.name == "example.txt": + timestamp = check_output([git, "log", "--format=%ct", "-n", "1", "--", + f], encoding="UTF-8") + if timestamp: + content = f.read_text() + files.append(content) + else: + print(f"Skipping file not tracked by git: {f.name}") + + # Sort the changes by "severity", which roughly corresponds to + # alphabetical order by accident (e.g. remove > deprecate > change > add) + for file in reversed(sorted(files)): + for line in file.splitlines(): + line = textwrap.fill(line.rstrip(), width=80, + initial_indent=" - ", + subsequent_indent=" ") + out.write(line + "\n") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} <version>") + sys.exit(1) + + git = which('git') + if not git: + print("Unable to find git binary") + sys.exit(1) + + # Accept passing only the major version number and the full 0 version. + major_version = -1 + if sys.argv[1].isdigit(): + major_version = sys.argv[1] + else: + ver_split = sys.argv[1].split(".") + if len(ver_split) == 3 and ver_split[1].isdigit(): + major_version = ver_split[1] + + if major_version == -1: + print(f"Invalid version number: {sys.argv[1]}") + sys.exit(1) + + docs_dir = pathlib.Path(sys.argv[0]).resolve().parents[1] / "DOCS" + interface_changes = docs_dir / "interface-changes.rst" + with open(interface_changes, "r") as f: + lines = [line.rstrip() for line in f] + + ver_line = " --- mpv 0." + major_version + ".0 ---" + next_ver_line = " --- mpv 0." + str(int(major_version) + 1) + ".0 ---" + with open(interface_changes, "w", newline="\n") as f: + for line in lines: + if line == ver_line: + f.write(next_ver_line + "\n") + f.write(line + "\n") + if line == ver_line: + add_new_entries(docs_dir, f, git) diff --git a/TOOLS/gen-mpv-desktop.py b/TOOLS/gen-mpv-desktop.py new file mode 100755 index 0000000000..7bbb33e5be --- /dev/null +++ b/TOOLS/gen-mpv-desktop.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +# Modify X-KDE-Protocols in the mpv.desktop file based on output from +# mpv --list-protocols. + +# +# This file is part of mpv. +# +# mpv is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# mpv is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with mpv. If not, see <http://www.gnu.org/licenses/>. +# + +import sys +from subprocess import check_output + +if __name__ == "__main__": + with open(sys.argv[1], "r", encoding="UTF-8") as f: + next(f) + mpv_desktop = dict([line.split("=", 1) for line in f]) + + if not mpv_desktop["X-KDE-Protocols"]: + raise ValueError("Missing X-KDE-Protocols entry in mpv.desktop file") + + mpv_protocols = check_output([sys.argv[2], "--no-config", "--list-protocols"], encoding="UTF-8") + mpv_protocols = set(line.strip(" :/") for line in mpv_protocols.splitlines() if "://" in line) + if len(mpv_protocols) == 0: + raise ValueError("Unable to parse any protocols from mpv '--list-protocols'") + + protocol_list = set(mpv_desktop["X-KDE-Protocols"].strip().split(",")) + mpv_desktop["X-KDE-Prot |