Merge pull request #482 from asfaltboy/support-quemu-on-github
Support qemu in GitHub Actions
This commit is contained in:
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-18.04, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
python_version: ['3.7']
|
python_version: ['3.7']
|
||||||
timeout-minutes: 180
|
timeout-minutes: 180
|
||||||
steps:
|
steps:
|
||||||
@@ -55,3 +55,24 @@ jobs:
|
|||||||
- name: Test cibuildwheel
|
- name: Test cibuildwheel
|
||||||
run: |
|
run: |
|
||||||
python ./bin/run_tests.py
|
python ./bin/run_tests.py
|
||||||
|
|
||||||
|
test-emulated:
|
||||||
|
name: Test emulated cibuildwheel using qemu
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 180
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install -r requirements-dev.txt
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
id: qemu
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Run the emulation tests
|
||||||
|
run: |
|
||||||
|
pytest --run-emulation test/test_emulation.py
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import sys
|
|||||||
import textwrap
|
import textwrap
|
||||||
import time
|
import time
|
||||||
import click
|
import click
|
||||||
|
from glob import glob
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
@@ -30,49 +31,70 @@ def generate_basic_project(path):
|
|||||||
project.generate(path)
|
project.generate(path)
|
||||||
|
|
||||||
|
|
||||||
CIService = namedtuple('CIService', 'name src_config_path dst_config_path badge_md')
|
CIService = namedtuple('CIService', 'name dst_config_path badge_md')
|
||||||
services = [
|
services = [
|
||||||
CIService(
|
CIService(
|
||||||
name='appveyor',
|
name='appveyor',
|
||||||
src_config_path='examples/appveyor-minimal.yml',
|
|
||||||
dst_config_path='appveyor.yml',
|
dst_config_path='appveyor.yml',
|
||||||
badge_md='[](https://ci.appveyor.com/project/joerick/cibuildwheel/branch/{branch})',
|
badge_md='[](https://ci.appveyor.com/project/joerick/cibuildwheel/branch/{branch})',
|
||||||
),
|
),
|
||||||
CIService(
|
CIService(
|
||||||
name='azure-pipelines',
|
name='azure-pipelines',
|
||||||
src_config_path='examples/azure-pipelines-minimal.yml',
|
|
||||||
dst_config_path='azure-pipelines.yml',
|
dst_config_path='azure-pipelines.yml',
|
||||||
badge_md='[](https://dev.azure.com/joerick0429/cibuildwheel/_build/latest?definitionId=2&branchName={branch})',
|
badge_md='[](https://dev.azure.com/joerick0429/cibuildwheel/_build/latest?definitionId=2&branchName={branch})',
|
||||||
),
|
),
|
||||||
CIService(
|
CIService(
|
||||||
name='circle-ci',
|
name='circleci',
|
||||||
src_config_path='examples/circleci-minimal.yml',
|
|
||||||
dst_config_path='.circleci/config.yml',
|
dst_config_path='.circleci/config.yml',
|
||||||
badge_md='[](https://circleci.com/gh/joerick/cibuildwheel/tree/{branch})',
|
badge_md='[](https://circleci.com/gh/joerick/cibuildwheel/tree/{branch})',
|
||||||
),
|
),
|
||||||
CIService(
|
CIService(
|
||||||
name='github',
|
name='github',
|
||||||
src_config_path='examples/github-minimal.yml',
|
|
||||||
dst_config_path='.github/workflows/example.yml',
|
dst_config_path='.github/workflows/example.yml',
|
||||||
badge_md='[](https://github.com/joerick/cibuildwheel/actions)',
|
badge_md='[](https://github.com/joerick/cibuildwheel/actions)',
|
||||||
),
|
),
|
||||||
CIService(
|
CIService(
|
||||||
name='travis-ci',
|
name='travis-ci',
|
||||||
src_config_path='examples/travis-ci-minimal.yml',
|
|
||||||
dst_config_path='.travis.yml',
|
dst_config_path='.travis.yml',
|
||||||
badge_md='[](https://travis-ci.org/joerick/cibuildwheel)',
|
badge_md='[](https://travis-ci.org/joerick/cibuildwheel)',
|
||||||
),
|
),
|
||||||
CIService(
|
CIService(
|
||||||
name='gitlab',
|
name='gitlab',
|
||||||
src_config_path='examples/gitlab-minimal.yml',
|
|
||||||
dst_config_path='.gitlab-ci.yml',
|
dst_config_path='.gitlab-ci.yml',
|
||||||
badge_md='[](https://gitlab.com/joerick/cibuildwheel/-/commits/{branch})'
|
badge_md='[](https://gitlab.com/joerick/cibuildwheel/-/commits/{branch})'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def ci_service_for_config_file(config_file):
|
||||||
|
service_name = Path(config_file).name.rsplit('-', 1)[0]
|
||||||
|
|
||||||
|
for service in services:
|
||||||
|
if service.name == service_name:
|
||||||
|
return service
|
||||||
|
|
||||||
|
raise ValueError(f'unknown ci service for config file {config_file}')
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
def run_example_ci_configs():
|
@click.argument('config_files', nargs=-1, type=click.Path())
|
||||||
|
def run_example_ci_configs(config_files=None):
|
||||||
|
'''
|
||||||
|
Test the example configs. If no files are specified, will test
|
||||||
|
examples/*-minimal.yml
|
||||||
|
'''
|
||||||
|
|
||||||
|
if len(config_files) == 0:
|
||||||
|
config_files = glob('examples/*-minimal.yml')
|
||||||
|
|
||||||
|
# check each CI service has at most 1 config file
|
||||||
|
configs_by_service = {}
|
||||||
|
for config_file in config_files:
|
||||||
|
service = ci_service_for_config_file(config_file)
|
||||||
|
if service.name in configs_by_service:
|
||||||
|
raise Exception('You cannot specify more than one config per CI service')
|
||||||
|
configs_by_service[service.name] = config_file
|
||||||
|
|
||||||
if git_repo_has_changes():
|
if git_repo_has_changes():
|
||||||
print('Your git repo has uncommitted changes. Commit or stash before continuing.')
|
print('Your git repo has uncommitted changes. Commit or stash before continuing.')
|
||||||
exit(1)
|
exit(1)
|
||||||
@@ -91,18 +113,19 @@ def run_example_ci_configs():
|
|||||||
example_project = Path('example_root')
|
example_project = Path('example_root')
|
||||||
generate_basic_project(example_project)
|
generate_basic_project(example_project)
|
||||||
|
|
||||||
for service in services:
|
for config_file in config_files:
|
||||||
src_config_file = Path(service.src_config_path)
|
service = ci_service_for_config_file(config_file)
|
||||||
|
src_config_file = Path(config_file)
|
||||||
dst_config_file = example_project / service.dst_config_path
|
dst_config_file = example_project / service.dst_config_path
|
||||||
|
|
||||||
dst_config_file.parent.mkdir(parents=True, exist_ok=True)
|
dst_config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.copyfile(src_config_file, dst_config_file)
|
shutil.copyfile(src_config_file, dst_config_file)
|
||||||
|
|
||||||
run(['git', 'add', example_project], check=True)
|
run(['git', 'add', example_project], check=True)
|
||||||
run(['git', 'commit', '-m', textwrap.dedent(f'''
|
run(['git', 'commit', '--no-verify', '-m', textwrap.dedent(f'''
|
||||||
Test example minimal configs
|
Test example minimal configs
|
||||||
|
|
||||||
Testing files: {[s.src_config_path for s in services]}
|
Testing files: {config_files}
|
||||||
Generated from branch: {previous_branch}
|
Generated from branch: {previous_branch}
|
||||||
Time: {timestamp}
|
Time: {timestamp}
|
||||||
''')], check=True)
|
''')], check=True)
|
||||||
@@ -116,9 +139,10 @@ def run_example_ci_configs():
|
|||||||
print('> ')
|
print('> ')
|
||||||
print('> | Service | Config | Status |')
|
print('> | Service | Config | Status |')
|
||||||
print('> |---|---|---|')
|
print('> |---|---|---|')
|
||||||
for service in services:
|
for config_file in config_files:
|
||||||
|
service = ci_service_for_config_file(config_file)
|
||||||
badge = service.badge_md.format(branch=branch_name, branch_escaped=quote(branch_name, safe=''))
|
badge = service.badge_md.format(branch=branch_name, branch_escaped=quote(branch_name, safe=''))
|
||||||
print(f'> | {service.name} | `{service.src_config_path}` | {badge} |')
|
print(f'> | {service.name} | `{config_file}` | {badge} |')
|
||||||
print('> ')
|
print('> ')
|
||||||
print('> Generated by `bin/run_example_ci_config.py`')
|
print('> Generated by `bin/run_example_ci_config.py`')
|
||||||
print()
|
print()
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from cibuildwheel.environment import (
|
|||||||
parse_environment,
|
parse_environment,
|
||||||
)
|
)
|
||||||
from cibuildwheel.util import (
|
from cibuildwheel.util import (
|
||||||
|
Architecture,
|
||||||
BuildOptions,
|
BuildOptions,
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
DependencyConstraints,
|
DependencyConstraints,
|
||||||
@@ -67,6 +68,17 @@ def main() -> None:
|
|||||||
run in Windows, and it will build and test for all versions of
|
run in Windows, and it will build and test for all versions of
|
||||||
Python. Default: auto.
|
Python. Default: auto.
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
parser.add_argument('--archs',
|
||||||
|
default=None,
|
||||||
|
help='''
|
||||||
|
Comma-separated list of CPU architectures to build for.
|
||||||
|
When set to 'auto', builds the architectures natively supported
|
||||||
|
on this machine. Set this option to build an architecture
|
||||||
|
via emulation, for example, using binfmt_misc and QEMU.
|
||||||
|
Default: auto.
|
||||||
|
Choices: auto, {}
|
||||||
|
'''.format(", ".join(a.name for a in Architecture)))
|
||||||
parser.add_argument('--output-dir',
|
parser.add_argument('--output-dir',
|
||||||
default=os.environ.get('CIBW_OUTPUT_DIR', 'wheelhouse'),
|
default=os.environ.get('CIBW_OUTPUT_DIR', 'wheelhouse'),
|
||||||
help='Destination folder for the wheels.')
|
help='Destination folder for the wheels.')
|
||||||
@@ -168,8 +180,14 @@ def main() -> None:
|
|||||||
print('cibuildwheel: Could not find setup.py, setup.cfg or pyproject.toml at root of package', file=sys.stderr)
|
print('cibuildwheel: Could not find setup.py, setup.cfg or pyproject.toml at root of package', file=sys.stderr)
|
||||||
exit(2)
|
exit(2)
|
||||||
|
|
||||||
|
if args.archs is not None:
|
||||||
|
archs_config_str = args.archs
|
||||||
|
else:
|
||||||
|
archs_config_str = get_option_from_environment('CIBW_ARCHS', platform=platform, default='auto')
|
||||||
|
archs = Architecture.parse_config(archs_config_str, platform=platform)
|
||||||
|
|
||||||
if args.print_build_identifiers:
|
if args.print_build_identifiers:
|
||||||
print_build_identifiers(platform, build_selector)
|
print_build_identifiers(platform, build_selector, archs)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
manylinux_images: Optional[Dict[str, str]] = None
|
manylinux_images: Optional[Dict[str, str]] = None
|
||||||
@@ -202,6 +220,7 @@ def main() -> None:
|
|||||||
manylinux_images[build_platform] = image
|
manylinux_images[build_platform] = image
|
||||||
|
|
||||||
build_options = BuildOptions(
|
build_options = BuildOptions(
|
||||||
|
architectures=archs,
|
||||||
package_dir=package_dir,
|
package_dir=package_dir,
|
||||||
output_dir=output_dir,
|
output_dir=output_dir,
|
||||||
test_command=test_command,
|
test_command=test_command,
|
||||||
@@ -284,10 +303,12 @@ def print_preamble(platform: str, build_options: BuildOptions) -> None:
|
|||||||
print('\nHere we go!\n')
|
print('\nHere we go!\n')
|
||||||
|
|
||||||
|
|
||||||
def print_build_identifiers(platform: str, build_selector: BuildSelector) -> None:
|
def print_build_identifiers(
|
||||||
|
platform: str, build_selector: BuildSelector, architectures: List[Architecture]
|
||||||
|
) -> None:
|
||||||
python_configurations: List[Any] = []
|
python_configurations: List[Any] = []
|
||||||
if platform == 'linux':
|
if platform == 'linux':
|
||||||
python_configurations = cibuildwheel.linux.get_python_configurations(build_selector)
|
python_configurations = cibuildwheel.linux.get_python_configurations(build_selector, architectures)
|
||||||
elif platform == 'windows':
|
elif platform == 'windows':
|
||||||
python_configurations = cibuildwheel.windows.get_python_configurations(build_selector)
|
python_configurations = cibuildwheel.windows.get_python_configurations(build_selector)
|
||||||
elif platform == 'macos':
|
elif platform == 'macos':
|
||||||
|
|||||||
+16
-28
@@ -1,4 +1,3 @@
|
|||||||
import platform
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
@@ -8,29 +7,10 @@ from typing import List, NamedTuple, Union
|
|||||||
|
|
||||||
from .docker_container import DockerContainer
|
from .docker_container import DockerContainer
|
||||||
from .logger import log
|
from .logger import log
|
||||||
from .util import (BuildOptions, BuildSelector, NonPlatformWheelError,
|
from .util import (
|
||||||
get_build_verbosity_extra_flags, prepare_command)
|
Architecture, BuildOptions, BuildSelector, NonPlatformWheelError,
|
||||||
|
get_build_verbosity_extra_flags, prepare_command,
|
||||||
|
)
|
||||||
def matches_platform(identifier: str) -> bool:
|
|
||||||
pm = platform.machine()
|
|
||||||
if pm == "x86_64":
|
|
||||||
# x86_64 machines can run i686 docker containers
|
|
||||||
if identifier.endswith('x86_64') or identifier.endswith('i686'):
|
|
||||||
return True
|
|
||||||
elif pm == "i686":
|
|
||||||
if identifier.endswith('i686'):
|
|
||||||
return True
|
|
||||||
elif pm == "aarch64":
|
|
||||||
if identifier.endswith('aarch64'):
|
|
||||||
return True
|
|
||||||
elif pm == "ppc64le":
|
|
||||||
if identifier.endswith('ppc64le'):
|
|
||||||
return True
|
|
||||||
elif pm == "s390x":
|
|
||||||
if identifier.endswith('s390x'):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class PythonConfiguration(NamedTuple):
|
class PythonConfiguration(NamedTuple):
|
||||||
@@ -43,7 +23,9 @@ class PythonConfiguration(NamedTuple):
|
|||||||
return PurePath(self.path_str)
|
return PurePath(self.path_str)
|
||||||
|
|
||||||
|
|
||||||
def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfiguration]:
|
def get_python_configurations(
|
||||||
|
build_selector: BuildSelector, architectures: List[Architecture]
|
||||||
|
) -> List[PythonConfiguration]:
|
||||||
python_configurations = [
|
python_configurations = [
|
||||||
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27m'),
|
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27m'),
|
||||||
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27mu'),
|
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27mu'),
|
||||||
@@ -78,8 +60,14 @@ def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfi
|
|||||||
PythonConfiguration(version='3.8', identifier='cp38-manylinux_s390x', path_str='/opt/python/cp38-cp38'),
|
PythonConfiguration(version='3.8', identifier='cp38-manylinux_s390x', path_str='/opt/python/cp38-cp38'),
|
||||||
PythonConfiguration(version='3.9', identifier='cp39-manylinux_s390x', path_str='/opt/python/cp39-cp39'),
|
PythonConfiguration(version='3.9', identifier='cp39-manylinux_s390x', path_str='/opt/python/cp39-cp39'),
|
||||||
]
|
]
|
||||||
# skip builds as required
|
|
||||||
return [c for c in python_configurations if matches_platform(c.identifier) and build_selector(c.identifier)]
|
# return all configurations whose arch is in our `architectures` list,
|
||||||
|
# and match the build/skip rules
|
||||||
|
return [
|
||||||
|
c for c in python_configurations
|
||||||
|
if any(c.identifier.endswith(arch.value) for arch in architectures)
|
||||||
|
and build_selector(c.identifier)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(options: BuildOptions) -> None:
|
||||||
@@ -93,7 +81,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
exit(2)
|
exit(2)
|
||||||
|
|
||||||
assert options.manylinux_images is not None
|
assert options.manylinux_images is not None
|
||||||
python_configurations = get_python_configurations(options.build_selector)
|
python_configurations = get_python_configurations(options.build_selector, options.architectures)
|
||||||
platforms = [
|
platforms = [
|
||||||
('cp', 'manylinux_x86_64', options.manylinux_images['x86_64']),
|
('cp', 'manylinux_x86_64', options.manylinux_images['x86_64']),
|
||||||
('cp', 'manylinux_i686', options.manylinux_images['i686']),
|
('cp', 'manylinux_i686', options.manylinux_images['i686']),
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import textwrap
|
||||||
from os import PathLike
|
from os import PathLike
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, NamedTuple, Optional, Sequence, Union
|
from typing import Dict, List, NamedTuple, Optional, Sequence, Union
|
||||||
|
|
||||||
from .environment import ParsedEnvironment
|
from .environment import ParsedEnvironment
|
||||||
from .logger import log
|
from .logger import log
|
||||||
from .util import (BuildOptions, BuildSelector, NonPlatformWheelError,
|
from .util import (Architecture, BuildOptions, BuildSelector, NonPlatformWheelError,
|
||||||
download, get_build_verbosity_extra_flags, get_pip_script,
|
download, get_build_verbosity_extra_flags, get_pip_script,
|
||||||
install_certifi_script, prepare_command)
|
install_certifi_script, prepare_command)
|
||||||
|
|
||||||
@@ -186,6 +187,12 @@ def setup_python(python_configuration: PythonConfiguration,
|
|||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(options: BuildOptions) -> None:
|
||||||
|
if options.architectures != [Architecture.x86_64]:
|
||||||
|
raise ValueError(textwrap.dedent(f'''
|
||||||
|
Invalid archs option {options.architectures}. macOS only supports x86_64 for the moment.
|
||||||
|
If you want to set emulation architectures on Linux, use CIBW_ARCHS_LINUX instead.
|
||||||
|
'''))
|
||||||
|
|
||||||
temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel'))
|
temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel'))
|
||||||
built_wheel_dir = temp_dir / 'built_wheel'
|
built_wheel_dir = temp_dir / 'built_wheel'
|
||||||
repaired_wheel_dir = temp_dir / 'repaired_wheel'
|
repaired_wheel_dir = temp_dir / 'repaired_wheel'
|
||||||
|
|||||||
+38
-1
@@ -1,4 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
|
import platform as platform_module
|
||||||
|
import re
|
||||||
import ssl
|
import ssl
|
||||||
import textwrap
|
import textwrap
|
||||||
import urllib.request
|
import urllib.request
|
||||||
@@ -121,10 +123,45 @@ class DependencyConstraints:
|
|||||||
return f'{self.__class__.__name__}{self.base_file_path!r})'
|
return f'{self.__class__.__name__}{self.base_file_path!r})'
|
||||||
|
|
||||||
|
|
||||||
|
class Architecture(Enum):
|
||||||
|
# mac/linux archs
|
||||||
|
x86_64 = 'x86_64'
|
||||||
|
i686 = 'i686'
|
||||||
|
aarch64 = 'aarch64'
|
||||||
|
ppc64le = 'ppc64le'
|
||||||
|
s390x = 's390x'
|
||||||
|
|
||||||
|
# windows archs
|
||||||
|
x86 = 'x86'
|
||||||
|
AMD64 = 'AMD64'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_config(config: str, platform: str) -> 'List[Architecture]':
|
||||||
|
result = []
|
||||||
|
for arch_str in re.split(r'[\s,]+', config):
|
||||||
|
if arch_str == 'auto':
|
||||||
|
result += Architecture.auto_archs(platform=platform)
|
||||||
|
else:
|
||||||
|
result.append(Architecture(arch_str))
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def auto_archs(platform: str) -> 'List[Architecture]':
|
||||||
|
native_architecture = Architecture(platform_module.machine())
|
||||||
|
result = [native_architecture]
|
||||||
|
if platform == 'linux' and native_architecture == Architecture.x86_64:
|
||||||
|
# x86_64 machines can run i686 docker containers
|
||||||
|
result.append(Architecture.i686)
|
||||||
|
if platform == 'windows' and native_architecture == Architecture.AMD64:
|
||||||
|
result.append(Architecture.x86)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class BuildOptions(NamedTuple):
|
class BuildOptions(NamedTuple):
|
||||||
package_dir: Path
|
package_dir: Path
|
||||||
output_dir: Path
|
output_dir: Path
|
||||||
build_selector: BuildSelector
|
build_selector: BuildSelector
|
||||||
|
architectures: List[Architecture]
|
||||||
environment: ParsedEnvironment
|
environment: ParsedEnvironment
|
||||||
before_all: str
|
before_all: str
|
||||||
before_build: Optional[str]
|
before_build: Optional[str]
|
||||||
@@ -164,7 +201,7 @@ def strtobool(val: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class CIProvider(str, Enum):
|
class CIProvider(Enum):
|
||||||
travis_ci = 'travis'
|
travis_ci = 'travis'
|
||||||
appveyor = 'appveyor'
|
appveyor = 'appveyor'
|
||||||
circle_ci = 'circle_ci'
|
circle_ci = 'circle_ci'
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import textwrap
|
||||||
from os import PathLike
|
from os import PathLike
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, NamedTuple, Optional, Sequence, Union
|
from typing import Dict, List, NamedTuple, Optional, Sequence, Union
|
||||||
@@ -12,7 +13,7 @@ import toml
|
|||||||
|
|
||||||
from .environment import ParsedEnvironment
|
from .environment import ParsedEnvironment
|
||||||
from .logger import log
|
from .logger import log
|
||||||
from .util import (BuildOptions, BuildSelector, NonPlatformWheelError,
|
from .util import (Architecture, BuildOptions, BuildSelector, NonPlatformWheelError,
|
||||||
download, get_build_verbosity_extra_flags, get_pip_script,
|
download, get_build_verbosity_extra_flags, get_pip_script,
|
||||||
prepare_command)
|
prepare_command)
|
||||||
|
|
||||||
@@ -201,6 +202,13 @@ def pep_518_cp35_workaround(package_dir: Path, env: Dict[str, str]) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(options: BuildOptions) -> None:
|
||||||
|
if options.architectures != [Architecture.AMD64, Architecture.x86]:
|
||||||
|
raise ValueError(textwrap.dedent(f'''
|
||||||
|
Invalid archs option {options.architectures}. Windows only supports 'amd64,x86' for the
|
||||||
|
moment. If you want to set emulation architectures on Linux, use CIBW_ARCHS_LINUX
|
||||||
|
instead.
|
||||||
|
'''))
|
||||||
|
|
||||||
temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel'))
|
temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel'))
|
||||||
built_wheel_dir = temp_dir / 'built_wheel'
|
built_wheel_dir = temp_dir / 'built_wheel'
|
||||||
repaired_wheel_dir = temp_dir / 'repaired_wheel'
|
repaired_wheel_dir = temp_dir / 'repaired_wheel'
|
||||||
|
|||||||
+20
@@ -24,6 +24,26 @@ Linux wheels are built in the [`manylinux` docker images](https://github.com/pyp
|
|||||||
|
|
||||||
- Alternative dockers images can be specified with the `CIBW_MANYLINUX_X86_64_IMAGE`, `CIBW_MANYLINUX_I686_IMAGE`, and `CIBW_MANYLINUX_PYPY_X86_64_IMAGE` options to allow for a custom, preconfigured build environment for the Linux builds. See [options](options.md#manylinux-image) for more details.
|
- Alternative dockers images can be specified with the `CIBW_MANYLINUX_X86_64_IMAGE`, `CIBW_MANYLINUX_I686_IMAGE`, and `CIBW_MANYLINUX_PYPY_X86_64_IMAGE` options to allow for a custom, preconfigured build environment for the Linux builds. See [options](options.md#manylinux-image) for more details.
|
||||||
|
|
||||||
|
### Building non-native architectures using emulation {: #emulation}
|
||||||
|
|
||||||
|
cibuildwheel supports building non-native architectures on Linux, via
|
||||||
|
emulation through the binfmt_misc kernel feature. The easiest way to use this
|
||||||
|
is via the [docker/setup-qemu-action][setup-qemu-action] on Github Actions or
|
||||||
|
[tonistiigi/binfmt][binfmt].
|
||||||
|
|
||||||
|
[setup-qemu-action]: https://github.com/docker/setup-qemu-action
|
||||||
|
[binfmt]: https://hub.docker.com/r/tonistiigi/binfmt
|
||||||
|
|
||||||
|
Check out the following config for an example of how to set it up on Github
|
||||||
|
Actions. Once QEMU is set up and registered, you just need to set the
|
||||||
|
`CIBW_ARCHS_LINUX` environment variable (or use the `--archs` option on
|
||||||
|
Linux), and the other architectures are emulated automatically.
|
||||||
|
|
||||||
|
> .github/workflows/build.yml
|
||||||
|
```yaml
|
||||||
|
{% include "../examples/github-with-qemu.yml" %}
|
||||||
|
```
|
||||||
|
|
||||||
### Building packages with optional C extensions
|
### Building packages with optional C extensions
|
||||||
|
|
||||||
`cibuildwheel` defines the environment variable `CIBUILDWHEEL` to the value `1` allowing projects for which the C extension is optional to make it mandatory when building wheels.
|
`cibuildwheel` defines the environment variable `CIBUILDWHEEL` to the value `1` allowing projects for which the C extension is optional to make it mandatory when building wheels.
|
||||||
|
|||||||
+30
-1
@@ -155,6 +155,28 @@ CIBW_SKIP: pp*
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
### `CIBW_ARCHS_LINUX` {: #archs}
|
||||||
|
> Build non-native architectures
|
||||||
|
|
||||||
|
A space-separated list of architectures to build. Use this in conjunction with
|
||||||
|
emulation, such as that provided by [docker/setup-qemu-action][setup-qemu-action]
|
||||||
|
or [tonistiigi/binfmt][binfmt], to build architectures other than those your
|
||||||
|
machine natively supports.
|
||||||
|
|
||||||
|
Options: `auto` `x86_64` `i686` `aarch64` `ppc64le` `s390x`
|
||||||
|
|
||||||
|
Default: `auto`, meaning the native archs supported on the build machine. For
|
||||||
|
example, on an `x86_64` machine, `auto` expands to `x86_64` and `i686`.
|
||||||
|
|
||||||
|
[setup-qemu-action]: https://github.com/docker/setup-qemu-action
|
||||||
|
[binfmt]: https://hub.docker.com/r/tonistiigi/binfmt
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# On an intel runner with qemu installed, build Intel and ARM wheels
|
||||||
|
CIBW_ARCHS_LINUX: "auto aarch64"
|
||||||
|
```
|
||||||
|
|
||||||
## Build customization
|
## Build customization
|
||||||
|
|
||||||
@@ -488,7 +510,8 @@ CIBW_BUILD_VERBOSITY: 1
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
usage: cibuildwheel [-h] [--platform {auto,linux,macos,windows}]
|
usage: cibuildwheel [-h] [--platform {auto,linux,macos,windows}]
|
||||||
[--output-dir OUTPUT_DIR] [--print-build-identifiers]
|
[--archs ARCHS] [--output-dir OUTPUT_DIR]
|
||||||
|
[--print-build-identifiers]
|
||||||
[package_dir]
|
[package_dir]
|
||||||
|
|
||||||
Build wheels for all the platforms.
|
Build wheels for all the platforms.
|
||||||
@@ -510,6 +533,12 @@ optional arguments:
|
|||||||
don't run on your development machine. For "windows",
|
don't run on your development machine. For "windows",
|
||||||
you need to run in Windows, and it will build and test
|
you need to run in Windows, and it will build and test
|
||||||
for all versions of Python. Default: auto.
|
for all versions of Python. Default: auto.
|
||||||
|
--archs ARCHS Comma-separated list of CPU architectures to build
|
||||||
|
for. If unspecified, builds the architectures natively
|
||||||
|
supported on this machine. Set this option to build an
|
||||||
|
architecture via emulation, for example, using
|
||||||
|
binfmt_misc and qemu. Default: auto Choices: auto,
|
||||||
|
x86_64, i686, aarch64, ppc64le, s390x, x86, AMD64
|
||||||
--output-dir OUTPUT_DIR
|
--output-dir OUTPUT_DIR
|
||||||
Destination folder for the wheels.
|
Destination folder for the wheels.
|
||||||
--print-build-identifiers
|
--print-build-identifiers
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-18.04, windows-latest, macos-latest]
|
os: [ubuntu-18.04, windows-2019, macos-10.15]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-18.04, windows-latest, macos-latest]
|
os: [ubuntu-18.04, windows-2019, macos-10.15]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_wheels:
|
||||||
|
name: Build wheels on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-18.04, windows-2019, macos-10.15]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
name: Install Python
|
||||||
|
with:
|
||||||
|
python-version: '3.7'
|
||||||
|
|
||||||
|
- name: Install cibuildwheel
|
||||||
|
run: |
|
||||||
|
python -m pip install cibuildwheel==1.7.1
|
||||||
|
|
||||||
|
- name: Install Visual C++ for Python 2.7
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
run: |
|
||||||
|
choco install vcpython27 -f -y
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
run: |
|
||||||
|
python -m cibuildwheel --output-dir wheelhouse
|
||||||
|
env:
|
||||||
|
# configure cibuildwheel to build native archs ('auto'), and some
|
||||||
|
# emulated ones
|
||||||
|
CIBW_ARCHS_LINUX: auto aarch64 ppc64le s390x
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption(
|
||||||
|
"--run-emulation", action="store_true", default=False, help="run emulation tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
config.addinivalue_line("markers", "emulation: mark test requiring qemu binfmt_misc to run")
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_collection_modifyitems(config, items):
|
||||||
|
if config.getoption("--run-emulation"):
|
||||||
|
# --run-emulation given in cli: do not skip emulation tests
|
||||||
|
return
|
||||||
|
skip_emulation = pytest.mark.skip(reason="need --run-emulation option to run")
|
||||||
|
for item in items:
|
||||||
|
if "emulation" in item.keywords:
|
||||||
|
item.add_marker(skip_emulation)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import subprocess
|
||||||
|
import pytest
|
||||||
|
from . import utils
|
||||||
|
from . import test_projects
|
||||||
|
|
||||||
|
|
||||||
|
project_with_a_test = test_projects.new_c_project()
|
||||||
|
|
||||||
|
project_with_a_test.files['test/spam_test.py'] = r'''
|
||||||
|
import spam
|
||||||
|
|
||||||
|
def test_spam():
|
||||||
|
assert spam.system('python -c "exit(0)"') == 0
|
||||||
|
assert spam.system('python -c "exit(1)"') != 0
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.emulation
|
||||||
|
def test(tmp_path):
|
||||||
|
project_dir = tmp_path / 'project'
|
||||||
|
project_with_a_test.generate(project_dir)
|
||||||
|
|
||||||
|
# build and test the wheels
|
||||||
|
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
|
||||||
|
'CIBW_TEST_REQUIRES': 'pytest',
|
||||||
|
'CIBW_TEST_COMMAND': 'pytest {project}/test',
|
||||||
|
'CIBW_ARCHS': 'aarch64 ppc64le s390x',
|
||||||
|
})
|
||||||
|
|
||||||
|
# also check that we got the right wheels
|
||||||
|
expected_wheels = (
|
||||||
|
utils.expected_wheels('spam', '0.1.0', machine_arch='aarch64')
|
||||||
|
+ utils.expected_wheels('spam', '0.1.0', machine_arch='ppc64le')
|
||||||
|
+ utils.expected_wheels('spam', '0.1.0', machine_arch='s390x')
|
||||||
|
)
|
||||||
|
assert set(actual_wheels) == set(expected_wheels)
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_arch_on_other_platforms(tmp_path, capfd):
|
||||||
|
if utils.platform == 'linux':
|
||||||
|
pytest.skip('this test checks the behaviour on platforms other than linux')
|
||||||
|
|
||||||
|
project_dir = tmp_path / 'project'
|
||||||
|
project_with_a_test.generate(project_dir)
|
||||||
|
|
||||||
|
# build and test the wheels
|
||||||
|
with pytest.raises(subprocess.CalledProcessError):
|
||||||
|
utils.cibuildwheel_run(project_dir, add_env={
|
||||||
|
'CIBW_ARCHS': 'aarch64',
|
||||||
|
})
|
||||||
|
|
||||||
|
captured = capfd.readouterr()
|
||||||
|
assert "Invalid archs option" in captured.err
|
||||||
+8
-5
@@ -76,7 +76,7 @@ def cibuildwheel_run(project_path, package_dir='.', env=None, add_env=None, outp
|
|||||||
|
|
||||||
|
|
||||||
def expected_wheels(package_name, package_version, manylinux_versions=None,
|
def expected_wheels(package_name, package_version, manylinux_versions=None,
|
||||||
macosx_deployment_target='10.9'):
|
macosx_deployment_target='10.9', machine_arch=None):
|
||||||
'''
|
'''
|
||||||
Returns a list of expected wheels from a run of cibuildwheel.
|
Returns a list of expected wheels from a run of cibuildwheel.
|
||||||
'''
|
'''
|
||||||
@@ -85,15 +85,18 @@ def expected_wheels(package_name, package_version, manylinux_versions=None,
|
|||||||
# {python tag} and {abi tag} are closely related to the python interpreter used to build the wheel
|
# {python tag} and {abi tag} are closely related to the python interpreter used to build the wheel
|
||||||
# so we'll merge them below as python_abi_tag
|
# so we'll merge them below as python_abi_tag
|
||||||
|
|
||||||
|
if machine_arch is None:
|
||||||
|
machine_arch = pm.machine()
|
||||||
|
|
||||||
if manylinux_versions is None:
|
if manylinux_versions is None:
|
||||||
if pm.machine() == 'x86_64':
|
if machine_arch == 'x86_64':
|
||||||
manylinux_versions = ['manylinux1', 'manylinux2010']
|
manylinux_versions = ['manylinux1', 'manylinux2010']
|
||||||
else:
|
else:
|
||||||
manylinux_versions = ['manylinux2014']
|
manylinux_versions = ['manylinux2014']
|
||||||
|
|
||||||
python_abi_tags = ['cp35-cp35m', 'cp36-cp36m', 'cp37-cp37m', 'cp38-cp38', 'cp39-cp39']
|
python_abi_tags = ['cp35-cp35m', 'cp36-cp36m', 'cp37-cp37m', 'cp38-cp38', 'cp39-cp39']
|
||||||
|
|
||||||
if pm.machine() in ['x86_64', 'AMD64', 'x86']:
|
if machine_arch in ['x86_64', 'AMD64', 'x86']:
|
||||||
python_abi_tags += ['cp27-cp27m', 'pp27-pypy_73', 'pp36-pypy36_pp73', 'pp37-pypy37_pp73']
|
python_abi_tags += ['cp27-cp27m', 'pp27-pypy_73', 'pp36-pypy36_pp73', 'pp37-pypy37_pp73']
|
||||||
|
|
||||||
if platform == 'linux':
|
if platform == 'linux':
|
||||||
@@ -105,9 +108,9 @@ def expected_wheels(package_name, package_version, manylinux_versions=None,
|
|||||||
platform_tags = []
|
platform_tags = []
|
||||||
|
|
||||||
if platform == 'linux':
|
if platform == 'linux':
|
||||||
architectures = [pm.machine()]
|
architectures = [machine_arch]
|
||||||
|
|
||||||
if pm.machine() == 'x86_64' and python_abi_tag.startswith('cp'):
|
if machine_arch == 'x86_64' and python_abi_tag.startswith('cp'):
|
||||||
architectures.append('i686')
|
architectures.append('i686')
|
||||||
|
|
||||||
platform_tags = [
|
platform_tags = [
|
||||||
|
|||||||
@@ -70,5 +70,14 @@ def platform(request, monkeypatch):
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def intercepted_build_args(platform, monkeypatch):
|
def intercepted_build_args(platform, monkeypatch):
|
||||||
intercepted = ArgsInterceptor()
|
intercepted = ArgsInterceptor()
|
||||||
monkeypatch.setattr(globals()[platform], 'build', intercepted)
|
|
||||||
|
if platform == 'linux':
|
||||||
|
monkeypatch.setattr(linux, 'build', intercepted)
|
||||||
|
elif platform == 'macos':
|
||||||
|
monkeypatch.setattr(macos, 'build', intercepted)
|
||||||
|
elif platform == 'windows':
|
||||||
|
monkeypatch.setattr(windows, 'build', intercepted)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'unknown platform value: {platform}')
|
||||||
|
|
||||||
return intercepted
|
return intercepted
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import platform as platform_module
|
||||||
|
from cibuildwheel.util import Architecture
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -64,3 +66,48 @@ def test_platform_environment(platform, intercepted_build_args, monkeypatch):
|
|||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR
|
assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR
|
||||||
|
|
||||||
|
|
||||||
|
def test_archs_default(platform, intercepted_build_args, monkeypatch):
|
||||||
|
monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64')
|
||||||
|
|
||||||
|
main()
|
||||||
|
build_options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
|
if platform == 'linux':
|
||||||
|
assert build_options.architectures == [Architecture.x86_64, Architecture.i686]
|
||||||
|
else:
|
||||||
|
assert build_options.architectures == [Architecture.x86_64]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('use_env_var', [False, True])
|
||||||
|
def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_var):
|
||||||
|
monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64')
|
||||||
|
if use_env_var:
|
||||||
|
monkeypatch.setenv('CIBW_ARCHS', 'ppc64le')
|
||||||
|
else:
|
||||||
|
monkeypatch.setenv('CIBW_ARCHS', 'unused')
|
||||||
|
monkeypatch.setattr(sys, 'argv', sys.argv + ['--archs', 'ppc64le'])
|
||||||
|
|
||||||
|
main()
|
||||||
|
build_options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
|
assert build_options.architectures == [Architecture.ppc64le]
|
||||||
|
|
||||||
|
|
||||||
|
def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch):
|
||||||
|
monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64')
|
||||||
|
monkeypatch.setenv('CIBW_ARCHS', 'unused')
|
||||||
|
monkeypatch.setenv('CIBW_ARCHS_LINUX', 'ppc64le')
|
||||||
|
monkeypatch.setenv('CIBW_ARCHS_WINDOWS', 'x86')
|
||||||
|
monkeypatch.setenv('CIBW_ARCHS_MACOS', 'x86_64')
|
||||||
|
|
||||||
|
main()
|
||||||
|
build_options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
|
if platform == 'linux':
|
||||||
|
assert build_options.architectures == [Architecture.ppc64le]
|
||||||
|
elif platform == 'windows':
|
||||||
|
assert build_options.architectures == [Architecture.x86]
|
||||||
|
elif platform == 'macos':
|
||||||
|
assert build_options.architectures == [Architecture.x86_64]
|
||||||
|
|||||||
Reference in New Issue
Block a user