Skip to content

Commit a8a85d2

Browse files
jcfrthewtex
authored andcommitted
Re-implement windows script into python
Use of powershell revealed to be complex, especially the error handling part. We tried few approaches: (1) $ErrorActionPreference = 'stop' (2) trap { Write-Error $_; Exit 1 } (3) Use of Resolve-Error function but none of these allowed us to have a streamlined error management.
1 parent fca9c33 commit a8a85d2

File tree

2 files changed

+212
-99
lines changed

2 files changed

+212
-99
lines changed

scripts/windows-build-wheels.ps1

Lines changed: 0 additions & 99 deletions
This file was deleted.

scripts/windows_build_wheels.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
2+
import errno
3+
import os
4+
import shutil
5+
6+
from contextlib import contextmanager
7+
from functools import wraps
8+
from subprocess import check_call
9+
10+
11+
SCRIPT_DIR = os.path.dirname(__file__)
12+
ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
13+
STANDALONE_DIR = os.path.join(ROOT_DIR, "standalone-build")
14+
15+
print("SCRIPT_DIR: %s" % SCRIPT_DIR)
16+
print("ROOT_DIR: %s" % ROOT_DIR)
17+
print("STANDALONE_DIR: %s" % STANDALONE_DIR)
18+
19+
20+
def mkdir_p(path):
21+
"""Ensure directory ``path`` exists. If needed, parent directories
22+
are created.
23+
24+
Adapted from http://stackoverflow.com/a/600612/1539918
25+
"""
26+
try:
27+
os.makedirs(path)
28+
except OSError as exc: # Python >2.5
29+
if exc.errno == errno.EEXIST and os.path.isdir(path):
30+
pass
31+
else: # pragma: no cover
32+
raise
33+
34+
35+
@contextmanager
36+
def push_env(**kwargs):
37+
"""This context manager allow to set/unset environment variables.
38+
"""
39+
saved_env = dict(os.environ)
40+
for var, value in kwargs.items():
41+
if value is not None:
42+
os.environ[var] = value
43+
elif var in os.environ:
44+
del os.environ[var]
45+
yield
46+
os.environ.clear()
47+
for (saved_var, saved_value) in saved_env.items():
48+
os.environ[saved_var] = saved_value
49+
50+
51+
class ContextDecorator(object):
52+
"""A base class or mixin that enables context managers to work as
53+
decorators."""
54+
55+
def __init__(self, **kwargs):
56+
self.__dict__.update(kwargs)
57+
58+
def __enter__(self):
59+
# Note: Returning self means that in "with ... as x", x will be self
60+
return self
61+
62+
def __exit__(self, typ, val, traceback):
63+
pass
64+
65+
def __call__(self, func):
66+
@wraps(func)
67+
def inner(*args, **kwds): # pylint:disable=missing-docstring
68+
with self:
69+
return func(*args, **kwds)
70+
return inner
71+
72+
73+
class push_dir(ContextDecorator):
74+
"""Context manager to change current directory.
75+
"""
76+
def __init__(self, directory=None, make_directory=False):
77+
"""
78+
:param directory:
79+
Path to set as current working directory. If ``None``
80+
is passed, ``os.getcwd()`` is used instead.
81+
82+
:param make_directory:
83+
If True, ``directory`` is created.
84+
"""
85+
self.directory = None
86+
self.make_directory = None
87+
self.old_cwd = None
88+
super(push_dir, self).__init__(
89+
directory=directory, make_directory=make_directory)
90+
91+
def __enter__(self):
92+
self.old_cwd = os.getcwd()
93+
if self.directory:
94+
if self.make_directory:
95+
mkdir_p(self.directory)
96+
os.chdir(self.directory)
97+
return self
98+
99+
def __exit__(self, typ, val, traceback):
100+
os.chdir(self.old_cwd)
101+
102+
103+
def pip_install(python_dir, package):
104+
pip = os.path.join(python_dir, "Scripts", "pip.exe")
105+
print("Installing $package using %s" % pip)
106+
check_call([pip, "install", package])
107+
108+
109+
def prepare_build_env(python_version):
110+
python_dir = "C:/Python%s" % python_version
111+
if not os.path.exists(python_dir):
112+
raise FileNotFoundError("Aborting. python_dir [%s] does not exist." % python_dir)
113+
114+
venv = os.path.join(python_dir, "Scripts", "virtualenv.exe")
115+
venv_dir = os.path.join(ROOT_DIR, "venv-%s" % python_version)
116+
print("Creating python virtual environment: %s" % venv_dir)
117+
if not os.path.exists(venv_dir):
118+
check_call([venv, venv_dir])
119+
pip_install(venv_dir, "scikit-build")
120+
121+
122+
def build_wheel(python_version):
123+
venv_dir = os.path.join(ROOT_DIR, "venv-%s" % python_version)
124+
125+
python_executable = os.path.join(venv_dir, "Scripts", "python.exe")
126+
python_include_dir = os.path.join(venv_dir, "Include")
127+
128+
# XXX It should be possible to query skbuild for the library dir associated
129+
# with a given interpreter.
130+
xy_ver = python_version.split("-")[0]
131+
132+
python_library = "C:/Python%s/libs/python%s.lib" % (python_version, xy_ver)
133+
134+
print("")
135+
print("PYTHON_EXECUTABLE: %s" % python_executable)
136+
print("PYTHON_INCLUDE_DIR: %s" % python_include_dir)
137+
print("PYTHON_LIBRARY: %s" % python_library)
138+
139+
pip = os.path.join(venv_dir, "Scripts", "pip.exe")
140+
141+
ninja_executable = os.path.join(ROOT_DIR, "venv-27-x64", "Scripts", "ninja.exe")
142+
print("NINJA_EXECUTABLE:%s" % ninja_executable)
143+
144+
# Update PATH
145+
path = os.path.join(venv_dir, "Scripts")
146+
with push_env(PATH="%s:%s" % (path, os.environ["PATH"])):
147+
check_call([pip, "install",
148+
"-r", os.path.join(ROOT_DIR, "requirements-dev.txt")])
149+
150+
build_path = "C:/P/IPP/ITK-win_%s" % python_version
151+
152+
# Clean up previous invocations
153+
if os.path.exists(build_path):
154+
shutil.rmtree(build_path)
155+
156+
check_call([
157+
python_executable,
158+
"setup.py", "bdist_wheel", "--build-type", "Release", "-G", "Ninja",
159+
"--",
160+
"-DCMAKE_MAKE_PROGRAM:FILEPATH=%s" % ninja_executable,
161+
"-DITK_SOURCE_DIR:PATH=%s/ITK-source" % STANDALONE_DIR,
162+
"-DITK_BINARY_DIR:PATH=%s" % build_path,
163+
"-DPYTHON_EXECUTABLE:FILEPATH=%s" % python_executable,
164+
"-DPYTHON_INCLUDE_DIR:PATH=%s" % python_include_dir,
165+
"-DPYTHON_LIBRARY:FILEPATH=%s" % python_library
166+
])
167+
168+
check_call([python_executable, "setup.py", "clean"])
169+
170+
# Remove unnecessary files for building against ITK
171+
for root, _, file_list in os.walk(build_path):
172+
for filename in file_list:
173+
extension = os.path.splitext(filename)[1]
174+
if extension in [".cpp", ".xml", ".obj"]:
175+
os.remove(os.path.join(root, filename))
176+
177+
shutil.rmtree(os.path.join(build_path, "Wrapping", "Generators"))
178+
# XXX
179+
# Remove-Item -Recurse -Force $build_path\\Wrapping\\Generators\\castxml*
180+
181+
182+
def build_wheels():
183+
184+
prepare_build_env("27-x64")
185+
prepare_build_env("35-x64")
186+
prepare_build_env("36-x64")
187+
188+
with push_dir(directory=STANDALONE_DIR, make_directory=True):
189+
190+
cmake_executable = "cmake.exe"
191+
tools_venv = os.path.join(ROOT_DIR, "venv-27-x64")
192+
pip_install(tools_venv, "ninja")
193+
ninja_executable = os.path.join(tools_venv, "Scripts", "ninja.exe")
194+
195+
check_call([
196+
cmake_executable,
197+
"-DITKPythonPackage_BUILD_PYTHON:PATH=0",
198+
"-G", "Ninja",
199+
"-DCMAKE_MAKE_PROGRAM:FILEPATH=%s" % ninja_executable,
200+
ROOT_DIR
201+
])
202+
203+
check_call([ninja_executable])
204+
205+
# Compile wheels re-using standalone project and archive cache
206+
build_wheel("27-x64")
207+
build_wheel("35-x64")
208+
build_wheel("36-x64")
209+
210+
211+
if __name__ == "__main__":
212+
build_wheels()

0 commit comments

Comments
 (0)