|
3 | 3 | License: GNU GPLv3 |
4 | 4 | Copyright (c) 2021 RedFantom |
5 | 5 | """ |
6 | | -from skbuild import setup |
| 6 | +import sys |
| 7 | + |
| 8 | + |
| 9 | +def read(file_name): |
| 10 | + with open(file_name) as fi: |
| 11 | + contents = fi.read() |
| 12 | + return contents |
| 13 | + |
| 14 | + |
| 15 | +def printf(*args, **kwargs): |
| 16 | + kwargs.update({"flush": True}) |
| 17 | + print(*args, **kwargs) |
| 18 | + |
| 19 | + |
| 20 | +if "linux" in sys.platform: |
| 21 | + try: |
| 22 | + from skbuild import setup |
| 23 | + from skbuild.command.build import build |
| 24 | + except ImportError: |
| 25 | + printf("scikit-build is required to build this project") |
| 26 | + printf("install with `python -m pip install scikit-build`") |
| 27 | + raise |
| 28 | + |
| 29 | + |
| 30 | + class BuildCommand(build): |
| 31 | + """ |
| 32 | + Intercept the build command to build the required modules in ./build |
| 33 | +
|
| 34 | + extrafont depends on a library built from source. Building this library |
| 35 | + requires the following to be installed, Ubuntu package names: |
| 36 | + - fontconfig |
| 37 | + - libfontconfig1 |
| 38 | + - libfontconfig1-dev |
| 39 | + - tcl-dev |
| 40 | + - tk-dev |
| 41 | + """ |
| 42 | + |
| 43 | + def run(self): |
| 44 | + build.run(self) |
| 45 | + |
| 46 | + kwargs = {"install_requires": ["scikit-build"], "cmdClass": {"build": BuildCommand}} |
| 47 | + |
| 48 | +elif "win" in sys.platform: |
| 49 | + import os |
| 50 | + import shutil |
| 51 | + from setuptools import setup |
| 52 | + import subprocess as sp |
| 53 | + from typing import List, Optional |
| 54 | + |
| 55 | + dependencies = ["cmake", "tk", "toolchain", "fontconfig"] |
| 56 | + |
| 57 | + for dep in dependencies: |
| 58 | + printf("Installing dependency {}...".format(dep), end=" ") |
| 59 | + sp.call(["pacman", "--needed", "--noconfirm", "-S", "mingw-w64-x86_64-{}".format(dep)], stdout=sp.PIPE) |
| 60 | + printf("Done.") |
| 61 | + sp.call(["cmake", ".", "-G", "MinGW Makefiles"]) |
| 62 | + sp.call(["mingw32-make"]) |
| 63 | + |
| 64 | + |
| 65 | + class DependencyWalker(object): |
| 66 | + """ |
| 67 | + Walk the dependencies of a DLL file and find all DLL files |
| 68 | +
|
| 69 | + DLL files are searched for in all the directories specified by |
| 70 | + - The PATH environment variable |
| 71 | + - The DLL_SEARCH_PATHS environment variable |
| 72 | + """ |
| 73 | + |
| 74 | + def __init__(self, dll_file: str, dependencies_exe="deps\\dependencies.exe", specials=dict()): |
| 75 | + if not os.path.exists(dependencies_exe): |
| 76 | + printf("dependencies.exe is required to find all dependency DLLs") |
| 77 | + raise FileNotFoundError("Invalid path specified for dependencies.exe") |
| 78 | + self._exe = dependencies_exe |
| 79 | + if not os.path.exists(dll_file): |
| 80 | + raise FileNotFoundError("'{}' does not specify a valid path to first file".format(dll_file)) |
| 81 | + self._dll_file = dll_file |
| 82 | + self._dll_cache = {} |
| 83 | + self._specials = specials |
| 84 | + self.walked = {} |
| 85 | + |
| 86 | + @property |
| 87 | + def dependency_dll_files(self) -> List[str]: |
| 88 | + """Return a list of abspaths to the dependency DLL files""" |
| 89 | + printf("Walking dependencies of {}".format(self._dll_file)) |
| 90 | + dlls = [self._dll_file] + list(map(self._find_dll_abs_path, self._specials.keys())) |
| 91 | + done = [] |
| 92 | + while set(dlls) != set(done): # As long as not all dlls are done, keep searching |
| 93 | + for dll in set(dlls) - set(done): # Go only over not-yet done DLLs |
| 94 | + if dll is None: |
| 95 | + done.append(None) |
| 96 | + continue |
| 97 | + printf("Looking for dependencies of {}".format(dll)) |
| 98 | + p = sp.Popen([self._exe, "-imports", dll], stdout=sp.PIPE) |
| 99 | + stdout, stderr = p.communicate() |
| 100 | + new_dlls = self._parse_dependencies_output(stdout) |
| 101 | + for new_dll in new_dlls: |
| 102 | + p = self._find_dll_abs_path(new_dll) |
| 103 | + if p is None: |
| 104 | + continue |
| 105 | + elif "system32" in p: |
| 106 | + continue |
| 107 | + elif p not in dlls: |
| 108 | + dlls.append(p) |
| 109 | + done.append(dll) |
| 110 | + return list(set(dlls) - set((None,))) |
| 111 | + |
| 112 | + @staticmethod |
| 113 | + def _parse_dependencies_output(output: bytes) -> List[str]: |
| 114 | + """Parse the output of the dependencies.exe command""" |
| 115 | + dlls: List[str] = list() |
| 116 | + for line in map(str.strip, output.decode().split("\n")): |
| 117 | + if not line.startswith("Import from module"): |
| 118 | + continue |
| 119 | + line = line[len("Import from module"):].strip(":").strip() |
| 120 | + dlls.append(line) |
| 121 | + return dlls |
| 122 | + |
| 123 | + def _find_dll_abs_path(self, dll_name: str) -> Optional[str]: |
| 124 | + """Find the absolute path of a specific DLL file specified""" |
| 125 | + if dll_name in self._dll_cache: |
| 126 | + return self._dll_cache[dll_name] |
| 127 | + printf("Looking for path of {}...".format(dll_name), end="") |
| 128 | + for var in ("PATH", "DLL_SEARCH_DIRECTORIES"): |
| 129 | + printf(".", end="") |
| 130 | + val = os.environ.get(var, "") |
| 131 | + for dir in val.split(";"): |
| 132 | + if not os.path.exists(dir) and os.path.isdir(dir): |
| 133 | + continue |
| 134 | + if dir not in self.walked: |
| 135 | + self.walked[dir] = list(os.walk(dir)) |
| 136 | + for dirpath, subdirs, files in self.walked[dir]: |
| 137 | + if dll_name in files: |
| 138 | + p = os.path.join(dirpath, dll_name) |
| 139 | + printf(" Found: {}".format(p)) |
| 140 | + self._dll_cache[dll_name] = p |
| 141 | + return p |
| 142 | + printf("Not found.") |
| 143 | + self._dll_cache[dll_name] = None |
| 144 | + return None |
| 145 | + |
| 146 | + def copy_to_target(self, target: str): |
| 147 | + for p in self.dependency_dll_files: |
| 148 | + if os.path.basename(p) in self._specials: |
| 149 | + t = os.path.join(target, *self._specials[os.path.basename(p)].split("/"), os.path.basename(p)) |
| 150 | + d = os.path.dirname(t) |
| 151 | + if not os.path.exists(d): |
| 152 | + os.makedirs(d) |
| 153 | + printf("Copying special {} -> {}".format(p, t)) |
| 154 | + shutil.copyfile(p, t) |
| 155 | + else: |
| 156 | + printf("Copying {}".format(p)) |
| 157 | + shutil.copyfile(p, os.path.join(target, os.path.basename(p))) |
| 158 | + |
| 159 | + specials = {} |
| 160 | + DependencyWalker("libextrafont.dll", specials=specials).copy_to_target("extrafont") |
| 161 | + kwargs = {"package_data": {"extrafont": ["*.dll", "pkgIndex.tcl", "extrafont.tcl"] + ["{}/{}".format(dir.strip("/"), base) for base, dir in specials.items()]}} |
| 162 | + |
| 163 | +else: |
| 164 | + printf("Only Linux and Windows are currently supported by the build system") |
| 165 | + printf("If you wish to help design a build method for your OS, please") |
| 166 | + printf("contact the project author.") |
| 167 | + raise RuntimeError("Unsupported platform") |
7 | 168 |
|
8 | 169 |
|
9 | 170 | setup( |
|
18 | 179 | long_description="", # TODO |
19 | 180 | long_description_content_type="text/markdown", |
20 | 181 | zip_safe=False, |
| 182 | + **kwargs |
21 | 183 | ) |
0 commit comments