Skip to content

Commit 3d864e7

Browse files
committed
Add CIBW_REPAIR_COMMAND env variable
to allow different options for auditwheel/delocate, alternative commands and a future Windows equivalent. Fix pypa#191 .
1 parent 96a2505 commit 3d864e7

File tree

7 files changed

+119
-76
lines changed

7 files changed

+119
-76
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ Options
9595
| | [`CIBW_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) [`CIBW_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) | Choose the Python versions to build |
9696
| **Build environment** | [`CIBW_ENVIRONMENT`](https://cibuildwheel.readthedocs.io/en/stable/options/#environment) | Set environment variables needed during the build |
9797
| | [`CIBW_BEFORE_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build |
98+
| | [`CIBW_REPAIR_COMMAND`](https://cibuildwheel.readthedocs.io/en/stable/options/#repair-command) | Execute a shell command to repair each (non-pure Python) built wheel |
9899
| | [`CIBW_MANYLINUX_X86_64_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) [`CIBW_MANYLINUX_I686_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#manylinux-image) | Specify alternative manylinux docker images |
99-
| **Testing** | [`CIBW_TEST_COMMAND`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-command) | Execute a shell command to test all built wheels |
100+
| **Testing** | [`CIBW_TEST_COMMAND`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-command) | Execute a shell command to test each built wheel |
100101
| | [`CIBW_TEST_REQUIRES`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-requires) | Install Python dependencies before running the tests |
101102
| | [`CIBW_TEST_EXTRAS`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-extras) | Install your wheel for testing using extras_require |
102103
| **Other** | [`CIBW_BUILD_VERBOSITY`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-extras) | Increase/decrease the output of pip wheel |
@@ -122,7 +123,7 @@ Here are some repos that use cibuildwheel.
122123
Legal note
123124
----------
124125

125-
Since `cibuildwheel` runs the wheel through delocate or auditwheel, it might automatically bundle dynamically linked libraries from the build machine.
126+
Since `cibuildwheel` repairs the wheel with `delocate` or `auditwheel`, it might automatically bundle dynamically linked libraries from the build machine.
126127

127128
It helps ensure that the library can run without any dependencies outside of the pip toolchain.
128129

cibuildwheel/__main__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ def main():
8989
before_build = get_option_from_environment('CIBW_BEFORE_BUILD', platform=platform)
9090
build_verbosity = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='')
9191
build_config, skip_config = os.environ.get('CIBW_BUILD', '*'), os.environ.get('CIBW_SKIP', '')
92+
if platform == 'linux':
93+
repair_command_default = 'auditwheel repair -w {dest_dir} {wheel}'
94+
elif platform == 'macos':
95+
repair_command_default = 'delocate-listdeps {wheel} && delocate-wheel -w {dest_dir} {wheel}'
96+
else:
97+
repair_command_default = ''
98+
repair_command = get_option_from_environment('CIBW_REPAIR_COMMAND', platform=platform, default=repair_command_default)
9299
environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='')
93100

94101
if test_extras:
@@ -130,6 +137,7 @@ def main():
130137
before_build=before_build,
131138
build_verbosity=build_verbosity,
132139
build_selector=build_selector,
140+
repair_command=repair_command,
133141
environment=environment,
134142
)
135143

cibuildwheel/linux.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def get_python_configurations(build_selector):
3030
return [c for c in python_configurations if build_selector(c.identifier)]
3131

3232

33-
def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, environment, manylinux_images):
33+
def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, manylinux_images):
3434
try:
3535
subprocess.check_call(['docker', '--version'])
3636
except:
@@ -60,30 +60,28 @@ def build(project_dir, output_dir, test_command, test_requires, test_extras, bef
6060
{environment_exports}
6161
6262
for PYBIN in {pybin_paths}; do
63-
# Setup
64-
rm -rf /tmp/built_wheel
65-
rm -rf /tmp/delocated_wheels
66-
mkdir /tmp/built_wheel
67-
mkdir /tmp/delocated_wheels
68-
6963
if [ ! -z {before_build} ]; then
7064
PATH="$PYBIN:$PATH" sh -c {before_build}
7165
fi
7266
73-
# Build that wheel
67+
# Build the wheel
68+
rm -rf /tmp/built_wheel
69+
mkdir /tmp/built_wheel
7470
PATH="$PYBIN:$PATH" "$PYBIN/pip" wheel . -w /tmp/built_wheel --no-deps {build_verbosity_flag}
7571
built_wheel=(/tmp/built_wheel/*.whl)
7672
77-
# Delocate the wheel
73+
# repair the wheel
74+
rm -rf /tmp/repaired_wheels
75+
mkdir /tmp/repaired_wheels
7876
# NOTE: 'built_wheel' here is a bash array of glob matches; "$built_wheel" returns
7977
# the first element
80-
if [[ "$built_wheel" == *none-any.whl ]]; then
81-
# pure python wheel - just copy
82-
mv "$built_wheel" /tmp/delocated_wheels
78+
if [[ "$built_wheel" == *none-any.whl ]] || [ -z {repair_command} ]; then
79+
# pure Python wheel or empty repair command
80+
mv "$built_wheel" /tmp/repaired_wheels
8381
else
84-
auditwheel repair "$built_wheel" -w /tmp/delocated_wheels
82+
built_wheel=$built_wheel sh -c {repair_command}
8583
fi
86-
delocated_wheels=(/tmp/delocated_wheels/*.whl)
84+
repaired_wheels=(/tmp/repaired_wheels/*.whl)
8785
8886
if [ ! -z {test_command} ]; then
8987
# Set up a virtual environment to install and test from, to make sure
@@ -105,7 +103,7 @@ def build(project_dir, output_dir, test_command, test_requires, test_extras, bef
105103
# functionally the same, differing only in name, wheel metadata, and possibly include
106104
# different external shared libraries. so it doesn't matter which one we run the tests on.
107105
# Let's just pick the first one.
108-
pip install "${{delocated_wheels[0]}}"{test_extras}
106+
pip install "${{repaired_wheels[0]}}"{test_extras}
109107
110108
# Install any requirements to run the tests
111109
if [ ! -z "{test_requires}" ]; then
@@ -127,8 +125,8 @@ def build(project_dir, output_dir, test_command, test_requires, test_extras, bef
127125
fi
128126
129127
# we're all done here; move it to output
130-
mv "${{delocated_wheels[@]}}" /output
131-
for delocated_wheel in "${{delocated_wheels[@]}}"; do chown {uid}:{gid} "/output/$(basename "$delocated_wheel")"; done
128+
mv "${{repaired_wheels[@]}}" /output
129+
for repaired_wheel in "${{repaired_wheels[@]}}"; do chown {uid}:{gid} "/output/$(basename "$repaired_wheel")"; done
132130
done
133131
'''.format(
134132
pybin_paths=' '.join(c.path+'/bin' for c in platform_configs),
@@ -141,6 +139,9 @@ def build(project_dir, output_dir, test_command, test_requires, test_extras, bef
141139
prepare_command(before_build, project='/project') if before_build else ''
142140
),
143141
build_verbosity_flag=' '.join(get_build_verbosity_extra_flags(build_verbosity)),
142+
repair_command=shlex_quote(
143+
prepare_command(repair_command, wheel='"$built_wheel"', dest_dir='/tmp/repaired_wheels') if repair_command else ''
144+
),
144145
environment_exports='\n'.join(environment.as_shell_commands()),
145146
uid=os.getuid(),
146147
gid=os.getgid(),

cibuildwheel/macos.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ def get_python_configurations(build_selector):
2525
return [c for c in python_configurations if build_selector(c.identifier)]
2626

2727

28-
def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, environment):
28+
def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment):
29+
abs_project_dir = os.path.abspath(project_dir)
30+
temp_dir = tempfile.mkdtemp(prefix='cibuildwheel')
31+
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
32+
repaired_wheel_dir = os.path.join(temp_dir, 'repaired_wheel')
33+
2934
python_configurations = get_python_configurations(build_selector)
3035
get_pip_url = 'https://bootstrap.pypa.io/get-pip.py'
3136
get_pip_script = '/tmp/get-pip.py'
@@ -44,8 +49,6 @@ def call(args, env=None, cwd=None, shell=False):
4449

4550
return subprocess.check_call(args, env=env, cwd=cwd, shell=shell)
4651

47-
abs_project_dir = os.path.abspath(project_dir)
48-
4952
# get latest pip once and for all
5053
call(['curl', '-L', '-o', get_pip_script, get_pip_url])
5154

@@ -94,32 +97,29 @@ def call(args, env=None, cwd=None, shell=False):
9497
call(['pip', 'install', 'wheel'], env=env)
9598
call(['pip', 'install', 'delocate'], env=env)
9699

97-
# setup dirs
98-
if os.path.exists('/tmp/built_wheel'):
99-
shutil.rmtree('/tmp/built_wheel')
100-
os.makedirs('/tmp/built_wheel')
101-
if os.path.exists('/tmp/delocated_wheel'):
102-
shutil.rmtree('/tmp/delocated_wheel')
103-
os.makedirs('/tmp/delocated_wheel')
104-
105100
# run the before_build command
106101
if before_build:
107102
before_build_prepared = prepare_command(before_build, project=abs_project_dir)
108103
call(before_build_prepared, env=env, shell=True)
109104

110105
# build the wheel
111-
call(['pip', 'wheel', abs_project_dir, '-w', '/tmp/built_wheel', '--no-deps'] + get_build_verbosity_extra_flags(build_verbosity), env=env)
112-
built_wheel = glob('/tmp/built_wheel/*.whl')[0]
113-
114-
if built_wheel.endswith('none-any.whl'):
115-
# pure python wheel - just move
116-
shutil.move(built_wheel, '/tmp/delocated_wheel')
106+
if os.path.exists(built_wheel_dir):
107+
shutil.rmtree(built_wheel_dir)
108+
os.makedirs(built_wheel_dir)
109+
call(['pip', 'wheel', abs_project_dir, '-w', built_wheel_dir, '--no-deps'] + get_build_verbosity_extra_flags(build_verbosity), env=env)
110+
built_wheel = glob(os.path.join(built_wheel_dir, '*.whl'))[0]
111+
112+
# repair the wheel
113+
if os.path.exists(repaired_wheel_dir):
114+
shutil.rmtree(repaired_wheel_dir)
115+
os.makedirs(repaired_wheel_dir)
116+
if built_wheel.endswith('none-any.whl') or not repair_command:
117+
# pure Python wheel or empty repair command
118+
shutil.move(built_wheel, repaired_wheel_dir)
117119
else:
118-
# list the dependencies
119-
call(['delocate-listdeps', built_wheel], env=env)
120-
# rebuild the wheel with shared libraries included and place in output dir
121-
call(['delocate-wheel', '-w', '/tmp/delocated_wheel', built_wheel], env=env)
122-
delocated_wheel = glob('/tmp/delocated_wheel/*.whl')[0]
120+
repair_command_prepared = prepare_command(repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
121+
call(repair_command_prepared, env=env, shell=True)
122+
repaired_wheel = glob(os.path.join(repaired_wheel_dir, '*.whl'))[0]
123123

124124
if test_command:
125125
# set up a virtual environment to install and test from, to make sure
@@ -141,7 +141,7 @@ def call(args, env=None, cwd=None, shell=False):
141141
call(['which', 'python'], env=virtualenv_env)
142142

143143
# install the wheel
144-
call(['pip', 'install', delocated_wheel + test_extras], env=virtualenv_env)
144+
call(['pip', 'install', repaired_wheel + test_extras], env=virtualenv_env)
145145

146146
# test the wheel
147147
if test_requires:
@@ -157,5 +157,5 @@ def call(args, env=None, cwd=None, shell=False):
157157
shutil.rmtree(venv_dir)
158158

159159
# we're all done here; move it to output (overwrite existing)
160-
dst = os.path.join(output_dir, os.path.basename(delocated_wheel))
161-
shutil.move(delocated_wheel, dst)
160+
dst = os.path.join(output_dir, os.path.basename(repaired_wheel))
161+
shutil.move(repaired_wheel, dst)

cibuildwheel/util.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
import warnings
33

44

5-
def prepare_command(command, project):
5+
def prepare_command(command, **kwargs):
66
'''
7-
Preprocesses a command by expanding variables like {project}.
7+
Preprocesses a command by expanding variables like {python}.
88
9-
For example, used in the test_command option, to specify the path to the
10-
tests directory.
9+
For example, used in the test_command option to specify the path to the
10+
project's root.
1111
'''
12-
return command.format(python='python', pip='pip', project=project)
12+
return command.format(python='python', pip='pip', **kwargs)
1313

1414

1515
def get_build_verbosity_extra_flags(level):

cibuildwheel/windows.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ def get_python_configurations(build_selector):
5757
return python_configurations
5858

5959

60-
61-
def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, environment):
60+
def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment):
6261
def simple_shell(args, env=None, cwd=None):
6362
print('+ ' + ' '.join(args))
6463
args = ['cmd', '/E:ON', '/V:ON', '/C'] + args
@@ -90,6 +89,7 @@ def shell(args, env=None, cwd=None):
9089
abs_project_dir = os.path.abspath(project_dir)
9190
temp_dir = tempfile.mkdtemp(prefix='cibuildwheel')
9291
built_wheel_dir = os.path.join(temp_dir, 'built_wheel')
92+
repaired_wheel_dir = os.path.join(temp_dir, 'repaired_wheel')
9393

9494
# install nuget as best way to provide python
9595
nuget = 'C:\\cibw\\nuget.exe'
@@ -109,11 +109,6 @@ def shell(args, env=None, cwd=None):
109109
assert os.path.exists(os.path.join(config_python_path, 'python.exe'))
110110
assert os.path.exists(os.path.join(config_python_path, 'Scripts', 'pip.exe'))
111111

112-
# setup dirs
113-
if os.path.exists(built_wheel_dir):
114-
shutil.rmtree(built_wheel_dir)
115-
os.makedirs(built_wheel_dir)
116-
117112
env = os.environ.copy()
118113
# set up environment variables for run_with_env
119114
env['PYTHON_VERSION'] = config.version
@@ -140,8 +135,23 @@ def shell(args, env=None, cwd=None):
140135
shell([before_build_prepared], env=env)
141136

142137
# build the wheel
138+
if os.path.exists(built_wheel_dir):
139+
shutil.rmtree(built_wheel_dir)
140+
os.makedirs(built_wheel_dir)
143141
shell(['pip', 'wheel', abs_project_dir, '-w', built_wheel_dir, '--no-deps'] + get_build_verbosity_extra_flags(build_verbosity), env=env)
144-
built_wheel = glob(built_wheel_dir+'/*.whl')[0]
142+
built_wheel = glob(os.path.join(built_wheel_dir, '*.whl'))[0]
143+
144+
# repair the wheel
145+
if os.path.exists(repaired_wheel_dir):
146+
shutil.rmtree(repaired_wheel_dir)
147+
os.makedirs(repaired_wheel_dir)
148+
if built_wheel.endswith('none-any.whl') or not repair_command:
149+
# pure Python wheel or empty repair command
150+
shutil.move(built_wheel, repaired_wheel_dir)
151+
else:
152+
repair_command_prepared = prepare_command(repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
153+
shell([repair_command_prepared], env=env)
154+
repaired_wheel = glob(os.path.join(repaired_wheel_dir, '*.whl'))[0]
145155

146156
if test_command:
147157
# set up a virtual environment to install and test from, to make sure
@@ -160,7 +170,7 @@ def shell(args, env=None, cwd=None):
160170
shell(['which', 'python'], env=virtualenv_env)
161171

162172
# install the wheel
163-
shell(['pip', 'install', built_wheel + test_extras], env=virtualenv_env)
173+
shell(['pip', 'install', repaired_wheel + test_extras], env=virtualenv_env)
164174

165175
# test the wheel
166176
if test_requires:
@@ -176,7 +186,7 @@ def shell(args, env=None, cwd=None):
176186
shutil.rmtree(venv_dir)
177187

178188
# we're all done here; move it to output (remove if already exists)
179-
dst = os.path.join(output_dir, os.path.basename(built_wheel))
189+
dst = os.path.join(output_dir, os.path.basename(repaired_wheel))
180190
if os.path.isfile(dst):
181191
os.remove(dst)
182-
shutil.move(built_wheel, dst)
192+
shutil.move(repaired_wheel, dst)

0 commit comments

Comments
 (0)