diff --git a/blasphemy b/blasphemy new file mode 100755 index 0000000..6503206 --- /dev/null +++ b/blasphemy @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +from subprocess import run, check_output +import sys +import os +import argparse + +SEPARATOR = " => " +LIBRARY_STORE = os.path.join("/", "gnu", "store") +CURRENT_PROFILE = os.path.join("/", "run", "current-system", "profile") +USER_PROFILE = os.path.join(os.environ["HOME"], ".guix-profile") + +LOADER = os.path.join(USER_PROFILE, "lib", "ld-linux-x86-64.so.2") + +SEARCH_PATHS = [ + os.path.join(USER_PROFILE, "lib"), + os.path.join(CURRENT_PROFILE, "lib"), + LIBRARY_STORE +] + +libraries = {} + +def file_type(path): + return check_output(["file", "-L", path]).decode().strip() + +def scan_input_binary(input_binary): + not_found = [] + discovered = {} + for line in check_output(["ldd", input_binary]).decode().split("\n"): + line = line.strip() + if not SEPARATOR in line: + continue + (library, path) = line.split(SEPARATOR) + if path == "not found": + not_found.append(library) + else: + discovered[library] = path.split(" ")[0] + return (discovered, not_found) + +def find_library(library): + for path in SEARCH_PATHS: + print(">> >> >> Looking for {0} in: {1}".format(library, path), file=sys.stderr) + found_library = find_library_in_search_path(library, path) + if found_library: + return found_library + +def find_library_in_search_path(library, path): + for (dirpath, dirnames, filenames) in os.walk(path): + if library in filenames: + library_path = os.path.join(dirpath, library) + if "ELF 64-bit" in file_type(library_path): + return library_path + +def find_libraries(already_found, to_find): + resolved = {} + for library in to_find: + print(">> Looking for: {0}".format(library), file=sys.stderr) + if library in already_found: + print(">> >> Using already-found path for {0}: {1}".format( + library, already_found[library] + ), file=sys.stderr) + resolved[library] = already_found[library] + else: + print(">> >> Performing search for: {0}".format(library), file=sys.stderr) + library_path = find_library(library) + if library_path: + print(">> >> >> >> Resolved {0} as: {1}".format(library, library_path), file=sys.stderr) + resolved[library] = library_path + else: + print(">> >> >> >> Failed to resolve {0}".format(library), file=sys.stderr) + raise Exception("Failed to resolve {0}".format(library)) + return resolved + +def make_ld_library_path(paths): + return ":".join(set(paths)) + +def generate_wrapper(paths, command): + return """#!/bin/sh +export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:{path}" +{command} $@""".format(path=make_ld_library_path(paths), command=" ".join(command)) + +def print_paths(paths): + print("---") + print(":".join(set(paths))) + +parser = argparse.ArgumentParser() +parser.add_argument("--wrap", action="store_true", help="output wrapper script to stdout") +parser.add_argument("--run", action="store_true", help="run command with specified args") +parser.add_argument("--command", help="command to wrap or run (default to the first input binary)") +parser.add_argument("--arguments", nargs="+", default=[], help="Additional arguments to pass to command") +parser.add_argument("--inputs", nargs="+", default=[], help="input binaries to scan for dependencies") +args = parser.parse_args() + +paths = [] +pre_command = [LOADER] +command = args.command + +for input_binary in args.inputs: + (discovered, not_found) = scan_input_binary(input_binary) + libraries.update(discovered) + found = find_libraries(libraries, not_found) + libraries.update(found) + paths = paths + [os.path.dirname(path) for path in found.values()] + +if (not command) and args.inputs: + command = os.path.abspath(args.inputs[0]) + +#command[0] = os.path.abspath(command[0]) + +command = pre_command + [command] + args.arguments +if args.run: + environment = {"LD_LIBRARY_PATH": make_ld_library_path(paths)} + environment.update(os.environ) + completed_process = run(command, env=environment) + sys.exit(completed_process.returncode) +elif args.wrap: + print(generate_wrapper(paths, command)) +else: + print_paths(paths)