Skip to content

Commit a8bd58a

Browse files
committed
Initial Windows build configuration
1 parent 464d60e commit a8bd58a

File tree

2 files changed

+196
-1
lines changed

2 files changed

+196
-1
lines changed

.appveyor.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
environment:
2+
matrix:
3+
- PYTHON: "C:\\PYTHON38-x64"
4+
build: off
5+
install:
6+
- set PATH=C:\msys64\usr\bin;%PATH%
7+
# Load PGP Keys for Msys64
8+
- bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz"
9+
- bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig"
10+
- bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig"
11+
- bash -lc "pacman -U --noconfirm --config <(echo) msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz"
12+
# Kill gpg-agent due to bug in MSYS
13+
- TaskKill /IM gpg-agent.exe /F
14+
# Update known packages
15+
- bash -lc "pacman --needed --noconfirm -Syu"
16+
- bash -lc "pacman --needed --noconfirm -Sy"
17+
# Install Python build dependencies
18+
- "%PYTHON%\\python.exe -m pip install scikit-build nose"
19+
# Now using MinGW pkg-config and related executables
20+
- set PATH=C:\msys64\mingw64\bin;%PATH%
21+
# Download Dependencies (dependency walker)
22+
- appveyor DownloadFile https://github.com/lucasg/Dependencies/releases/download/v1.10/Dependencies_x64_Release.zip
23+
- 7z e Dependencies_x64_Release.zip -odeps -y
24+
# Add %PYTHON% to the path for libffi-7.dll
25+
- set PATH=%PYTHON%;C:\msys64\mingw64\lib;%PATH%
26+
build_script:
27+
- "%PYTHON%\\python.exe setup.py build bdist_wheel"
28+
# Simplify PATH so as to test binary distribution
29+
- set PATH=C:\WINDOWS\system32;C:\WINDOWS
30+
test_script:
31+
- "%PYTHON%\\python.exe -m nose"
32+
artifacts:
33+
- path: "/dist/*.whl"

setup.py

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,168 @@
33
License: GNU GPLv3
44
Copyright (c) 2021 RedFantom
55
"""
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")
7168

8169

9170
setup(
@@ -18,4 +179,5 @@
18179
long_description="", # TODO
19180
long_description_content_type="text/markdown",
20181
zip_safe=False,
182+
**kwargs
21183
)

0 commit comments

Comments
 (0)