Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci/lint: add python linting #14848

Merged
merged 8 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: lunarmodules/luacheck@v1

python-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: chartboost/ruff-action@v1
14 changes: 7 additions & 7 deletions TOOLS/docutils-wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@


def convert_depfile(output, depfile):
with open(depfile, 'r') as f:
with open(depfile) as f:
deps = f.readlines()

with open(depfile, 'w') as f:
with open(depfile, "w") as f:
f.write(os.path.abspath(output))
f.write(': \\\n')
f.write(": \\\n")
for dep in deps:
dep = dep[:-1]
f.write('\t')
f.write("\t")
f.write(os.path.abspath(dep))
f.write(' \\\n')
f.write(" \\\n")

def remove(path):
try:
Expand All @@ -51,14 +51,14 @@ def remove(path):
output = argv[-1]

for opt, optarg in zip(argv, argv[1:]):
if opt == '--record-dependencies':
if opt == "--record-dependencies":
depfile = optarg

try:
proc = subprocess.run(argv, check=True)
if depfile is not None:
convert_depfile(output, depfile)
except:
except Exception:
remove(output)
if depfile is not None:
remove(depfile)
Expand Down
222 changes: 131 additions & 91 deletions TOOLS/dylib_unhell.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/usr/bin/env python3

import re
import json
import os
import sys
import re
import shutil
import subprocess
import json
import sys
from functools import partial

sys_re = re.compile("^/System")
Expand All @@ -16,17 +16,17 @@ 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
"libobjc." not in libname and \
"libSystem." not in libname and \
"libc." not in libname and \
"libgcc." not in libname and \
os.path.basename(libname) != "Python" and \
os.path.basename(objfile) not in libname and \
"libswift" not 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)
command = f"otool -L '{objfile}' | grep -e '\t' | awk '{{ print $1 }}'"
output = subprocess.check_output(command, shell=True, universal_newlines=True)
libs = set(filter(partial(is_user_lib, objfile), output.split()))

libs_resolved = set()
Expand All @@ -41,83 +41,108 @@ def otool(objfile, rapths):

def get_rapths(objfile):
rpaths = []
command = "otool -l '%s' | grep -A2 LC_RPATH | grep path" % objfile
pathRe = re.compile(r"^\s*path (.*) \(offset \d*\)$")
command = f"otool -l '{objfile}' | grep -A2 LC_RPATH | grep path"
path_re = re.compile(r"^\s*path (.*) \(offset \d*\)$")

try:
result = subprocess.check_output(command, shell = True, universal_newlines=True)
except:
result = subprocess.check_output(command, shell=True, universal_newlines=True)
except Exception:
return rpaths

for line in result.splitlines():
line_clean = pathRe.search(line).group(1).strip()
line_clean = path_re.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))
if line_clean.startswith("@loader_path/"):
line_clean = line_clean[len("@loader_path/"):]
line_clean = os.path.join(os.path.dirname(objfile), line_clean)
line_clean = os.path.normpath(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
command = (
f"otool -l '{binary}' | grep -A2 LC_RPATH | grep path | "
'grep "Xcode\\|CommandLineTools"'
)
Akemi marked this conversation as resolved.
Show resolved Hide resolved
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
path_re = re.compile(r"^\s*path (.*) \(offset \d*\)$")
return [
path_re.search(line).group(1).strip()
for line in result.splitlines()
]
Akemi marked this conversation as resolved.
Show resolved Hide resolved

def resolve_lib_path(objfile, lib, rapths):
if os.path.exists(lib):
return lib

if lib.startswith('@rpath/'):
lib = lib[len('@rpath/'):]
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/'):]
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)
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)
subprocess.check_output(
f"pkg-config vulkan --max-version={version}",
shell=True,
)
return True
except:
except Exception:
return False

def get_homebrew_prefix():
# set default to standard ARM path, intel path is already in the vulkan loader search array
# 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:
result = subprocess.check_output(
["brew", "--prefix"],
universal_newlines=True,
stderr=subprocess.DEVNULL,
).strip()
except Exception:
pass

return result

def install_name_tool_change(old, new, objfile):
subprocess.call(["install_name_tool", "-change", old, new, objfile], stderr=subprocess.DEVNULL)
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)
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 = []):
def libraries(objfile, result=None, result_relative=None, rapths=None):
if result is None:
result = {}

if result_relative is None:
result_relative = set()

if rapths is None:
rapths = []
Akemi marked this conversation as resolved.
Show resolved Hide resolved

rapths = get_rapths(objfile) + rapths
libs_list, libs_relative = otool(objfile, rapths)
result[objfile] = libs_list
Expand All @@ -130,10 +155,10 @@ def libraries(objfile, result = dict(), result_relative = set(), rapths = []):
return result, result_relative

def lib_path(binary):
return os.path.join(os.path.dirname(binary), 'lib')
return os.path.join(os.path.dirname(binary), "lib")

def resources_path(binary):
return os.path.join(os.path.dirname(binary), '../Resources')
return os.path.join(os.path.dirname(binary), "../Resources")

def lib_name(lib):
return os.path.join("@executable_path", "lib", os.path.basename(lib))
Expand Down Expand Up @@ -166,78 +191,88 @@ def process_libraries(libs_dict, libs_dyn, binary):
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()
swift_stdlib_tool = subprocess.check_output(
["xcrun", "--find", "swift-stdlib-tool"],
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)
swift_lib_path = os.path.join(swift_stdlib_tool, "../../lib/swift-5.0/macosx")
swift_lib_path = os.path.abspath(swift_lib_path)

command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)]
command = [
swift_stdlib_tool, "--copy", "--platform", "macosx",
"--scan-executable", binary, "--destination", lib_path(binary),
]

if os.path.exists(swiftLibPath):
command.extend(['--source-libraries', swiftLibPath])
if os.path.exists(swift_lib_path):
command.extend(["--source-libraries", swift_lib_path])

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):
def process_vulkan_loader(binary, loader_name, loader_relative_folder, library_node):
# 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),
loader_system_search_folders = [
os.path.join(os.path.expanduser("~"), ".config", loader_relative_folder),
os.path.join("/etc/xdg", loader_relative_folder),
os.path.join("/usr/local/etc", loader_relative_folder),
os.path.join("/etc", loader_relative_folder),
os.path.join(os.path.expanduser("~"), ".local/share", loader_relative_folder),
os.path.join("/usr/local/share", loader_relative_folder),
os.path.join("/usr/share/vulkan", loader_relative_folder),
os.path.join(get_homebrew_prefix(), "share", loader_relative_folder),
]

loaderSystemFolder = ""
for loaderSystemSearchFolder in loaderSystemSearchFolders:
if os.path.exists(loaderSystemSearchFolder):
loaderSystemFolder = loaderSystemSearchFolder
loader_system_folder = ""
for loader_system_search_folder in loader_system_search_folders:
if os.path.exists(loader_system_search_folder):
loader_system_folder = loader_system_search_folder
break

if not loaderSystemFolder:
print(">>> could not find loader folder " + loaderRelativeFolder)
if not loader_system_folder:
print(">>> could not find loader folder " + loader_relative_folder)
return

loaderBundleFolder = os.path.join(resources_path(binary), loaderRelativeFolder)
loaderSystemPath = os.path.join(loaderSystemFolder, loaderName)
loaderBundlePath = os.path.join(loaderBundleFolder, loaderName)
libraryRelativeFolder = "../../../Frameworks/"
loader_bundle_folder = os.path.join(resources_path(binary), loader_relative_folder)
loader_system_path = os.path.join(loader_system_folder, loader_name)
loader_bundle_path = os.path.join(loader_bundle_folder, loader_name)
library_relative_folder = "../../../Frameworks/"

if not os.path.exists(loaderSystemPath):
print(">>> could not find loader " + loaderName)
if not os.path.exists(loader_system_path):
print(">>> could not find loader " + loader_name)
return

if not os.path.exists(loaderBundleFolder):
os.makedirs(loaderBundleFolder)
if not os.path.exists(loader_bundle_folder):
os.makedirs(loader_bundle_folder)

loaderSystemFile = open(loaderSystemPath, 'r')
loaderJsonData = json.load(loaderSystemFile)
librarySystemPath = os.path.join(loaderSystemFolder, loaderJsonData[libraryNode]["library_path"])
loader_system_file = open(loader_system_path)
loader_json_data = json.load(loader_system_file)
library_path = loader_json_data[library_node]["library_path"]
library_system_path = os.path.join(loader_system_folder, library_path)

if not os.path.exists(librarySystemPath):
print(">>> could not find loader library " + librarySystemPath)
if not os.path.exists(library_system_path):
print(">>> could not find loader library " + library_system_path)
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))
print(">>> modifiying and writing loader json " + loader_name)
loader_bundle_file = open(loader_bundle_path, "w")
loader_library_name = os.path.basename(library_system_path)
library_path = os.path.join(library_relative_folder, loader_library_name)
loader_json_data[library_node]["library_path"] = library_path
json.dump(loader_json_data, loader_bundle_file, indent=4)

print(">>> copying loader library " + loader_library_name)
framework_bundle_folder = os.path.join(
loader_bundle_folder, library_relative_folder,
)
if not os.path.exists(framework_bundle_folder):
os.makedirs(framework_bundle_folder)
library_target_path = os.path.join(framework_bundle_folder, loader_library_name)
shutil.copy(library_system_path, library_target_path)

def remove_dev_tools_rapths(binary):
for path in get_rpaths_dev_tools(binary):
Expand All @@ -262,7 +297,12 @@ def process(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")
process_vulkan_loader(
binary,
"VkLayer_khronos_synchronization2.json",
"vulkan/explicit_layer.d",
"layer",
)

if __name__ == "__main__":
process(sys.argv[1])
Loading
Loading