diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py
index 137bc19e..bce3f754 100644
--- a/cibuildwheel/__main__.py
+++ b/cibuildwheel/__main__.py
@@ -128,6 +128,7 @@ def main() -> None:
build_config, skip_config = os.environ.get('CIBW_BUILD', '*'), os.environ.get('CIBW_SKIP', '')
environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='')
+ before_all = get_option_from_environment('CIBW_BEFORE_ALL', platform=platform)
before_build = get_option_from_environment('CIBW_BEFORE_BUILD', platform=platform)
repair_command = get_option_from_environment('CIBW_REPAIR_WHEEL_COMMAND', platform=platform, default=repair_command_default)
dependency_versions = get_option_from_environment('CIBW_DEPENDENCY_VERSIONS', platform=platform, default='pinned')
@@ -212,6 +213,7 @@ def main() -> None:
test_extras=test_extras,
before_test=before_test,
before_build=before_build,
+ before_all=before_all,
build_verbosity=build_verbosity,
build_selector=build_selector,
repair_command=repair_command,
diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py
index 99e779c4..8bbda24d 100644
--- a/cibuildwheel/linux.py
+++ b/cibuildwheel/linux.py
@@ -133,6 +133,10 @@ def build(options: BuildOptions) -> None:
call(['docker', 'start', container_name])
+ if options.before_all:
+ before_all_prepared = prepare_command(options.before_all, project='/project', package=options.package_dir)
+ call(['docker', 'exec', '-i', container_name] + shell_cmd, universal_newlines=True, input=before_all_prepared)
+
for config in platform_configs:
if options.dependency_constraints:
constraints_file = options.dependency_constraints.get_for_python_version(config.version)
diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py
index 2fd28d20..3e67b534 100644
--- a/cibuildwheel/macos.py
+++ b/cibuildwheel/macos.py
@@ -176,6 +176,10 @@ def build(options: BuildOptions) -> None:
built_wheel_dir = temp_dir / 'built_wheel'
repaired_wheel_dir = temp_dir / 'repaired_wheel'
+ if options.before_all:
+ before_all_prepared = prepare_command(options.before_all, project='.', package=options.package_dir)
+ call([before_all_prepared], shell=True)
+
python_configurations = get_python_configurations(options.build_selector)
for config in python_configurations:
diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py
index feef6185..f7201462 100644
--- a/cibuildwheel/util.py
+++ b/cibuildwheel/util.py
@@ -112,6 +112,7 @@ class BuildOptions(NamedTuple):
output_dir: Path
build_selector: BuildSelector
environment: ParsedEnvironment
+ before_all: str
before_build: Optional[str]
repair_command: str
manylinux_images: Optional[Dict[str, str]]
@@ -120,7 +121,7 @@ class BuildOptions(NamedTuple):
before_test: Optional[str]
test_requires: List[str]
test_extras: str
- build_verbosity: int
+x build_verbosity: int
resources_dir = Path(__file__).resolve().parent / 'resources'
diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py
index e81fe301..73628c7f 100644
--- a/cibuildwheel/windows.py
+++ b/cibuildwheel/windows.py
@@ -162,6 +162,10 @@ def build(options: BuildOptions) -> None:
nuget = Path('C:\\cibw\\nuget.exe')
download('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', nuget)
+ if options.before_all:
+ before_all_prepared = prepare_command(options.before_all, project='.', package=options.package_dir)
+ shell([before_all_prepared])
+
python_configurations = get_python_configurations(options.build_selector)
for config in python_configurations:
dependency_constraint_flags = []
diff --git a/docs/options.md b/docs/options.md
index 18d5417b..0d61060d 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -190,6 +190,25 @@ CIBW_ENVIRONMENT: "BUILD_TIME=$(date) SAMPLE_TEXT=\"sample text\""
!!! note
`cibuildwheel` always defines the environment variable `CIBUILDWHEEL=1`. This can be useful for [building wheels with optional extensions](faq.md#building-packages-with-optional-c-extensions).
+### `CIBW_BEFORE_ALL` {: #before-all}
+> Execute a shell command preparing common part for each wheel.
+
+Shell command to prepare common part of project (ex. build libraries which does not depend on python).
+This option is added mainly for linux build, because linux wheels are build in isolated from host docker containers.
+
+The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by `cibuildwheel`.
+
+Platform-specific variants also available:
+ `CIBW_BEFORE_ALL_MACOS` | `CIBW_BEFORE_ALL_WINDOWS` | `CIBW_BEFORE_ALL_LINUX`
+
+#### Examples
+```yaml
+# build third party library
+CIBW_BEFORE_ALL: make -C third_party_lib
+
+# install system library
+CIBW_BEFORE_ALL: yum install -y libffi-dev
+```
### `CIBW_BEFORE_BUILD` {: #before-build}
> Execute a shell command preparing each wheel's build
diff --git a/test/14_before_all/cibuildwheel_test.py b/test/14_before_all/cibuildwheel_test.py
new file mode 100644
index 00000000..e5340e35
--- /dev/null
+++ b/test/14_before_all/cibuildwheel_test.py
@@ -0,0 +1,23 @@
+import os
+
+import utils
+
+
+def test():
+ project_dir = os.path.dirname(__file__)
+
+ with open(os.path.join(project_dir, "text_info.txt"), mode='w') as ff:
+ print("dummy text", file=ff)
+
+ # build the wheels
+ actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
+ # write python version information to a temporary file, this is
+ # checked in setup.py
+ 'CIBW_BEFORE_ALL': '''python -c "open('{project}/text_info.txt', 'w').write('sample text')"''',
+ })
+
+
+ # also check that we got the right wheels
+ os.remove(os.path.join(project_dir, "text_info.txt"))
+ expected_wheels = utils.expected_wheels('spam', '0.1.0')
+ assert set(actual_wheels) == set(expected_wheels)
diff --git a/test/14_before_all/setup.py b/test/14_before_all/setup.py
new file mode 100644
index 00000000..7b68808b
--- /dev/null
+++ b/test/14_before_all/setup.py
@@ -0,0 +1,23 @@
+import os
+import sys
+
+from setuptools import (
+ Extension,
+ setup,
+)
+
+# assert that the Python version as written to text_info.txt in the CIBW_BEFORE_ALL step
+# is the same one as is currently running.
+with open("text_info.txt") as f:
+ stored_text = f.read()
+
+print("## stored text: "+stored_text)
+assert stored_text == "sample text"
+
+
+
+setup(
+ name="spam",
+ ext_modules=[Extension('spam', sources=['spam.c'])],
+ version="0.1.0",
+)
diff --git a/test/14_before_all/spam.c b/test/14_before_all/spam.c
new file mode 100644
index 00000000..d1ab0f22
--- /dev/null
+++ b/test/14_before_all/spam.c
@@ -0,0 +1,48 @@
+#include
+
+static PyObject *
+spam_system(PyObject *self, PyObject *args)
+{
+ const char *command;
+ int sts;
+
+ if (!PyArg_ParseTuple(args, "s", &command))
+ return NULL;
+ sts = system(command);
+ return PyLong_FromLong(sts);
+}
+
+/* Module initialization */
+
+#if PY_MAJOR_VERSION >= 3
+ #define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
+ #define MOD_DEF(m, name, doc, methods, module_state_size) \
+ static struct PyModuleDef moduledef = { \
+ PyModuleDef_HEAD_INIT, name, doc, module_state_size, methods, }; \
+ m = PyModule_Create(&moduledef);
+ #define MOD_RETURN(m) return m;
+#else
+ #define MOD_INIT(name) PyMODINIT_FUNC init##name(void)
+ #define MOD_DEF(m, name, doc, methods, module_state_size) \
+ m = Py_InitModule3(name, methods, doc);
+ #define MOD_RETURN(m) return;
+#endif
+
+static PyMethodDef module_methods[] = {
+ {"system", (PyCFunction)spam_system, METH_VARARGS,
+ "Execute a shell command."},
+ {NULL} /* Sentinel */
+};
+
+MOD_INIT(spam)
+{
+ PyObject* m;
+
+ MOD_DEF(m,
+ "spam",
+ "Example module",
+ module_methods,
+ -1)
+
+ MOD_RETURN(m)
+}