style: apply black via pre-commit run -a

This commit is contained in:
Henry Schreiner
2021-05-03 13:12:36 -04:00
committed by Henry Schreiner
parent 9cbed6a9be
commit 178aaea6c7
53 changed files with 1479 additions and 714 deletions
+36 -17
View File
@@ -37,11 +37,18 @@ def bump_version() -> None:
current_version = cibuildwheel.__version__
try:
commit_date_str = subprocess.run([
'git',
'show', '--no-patch', '--pretty=format:%ci',
f'v{current_version}^{{commit}}',
], check=True, capture_output=True, encoding='utf8').stdout
commit_date_str = subprocess.run(
[
'git',
'show',
'--no-patch',
'--pretty=format:%ci',
f'v{current_version}^{{commit}}',
],
check=True,
capture_output=True,
encoding='utf8',
).stdout
cd_date, cd_time, cd_tz = commit_date_str.split(' ')
url_opts = urllib.parse.urlencode({'q': f'is:pr merged:>{cd_date}T{cd_time}{cd_tz}'})
@@ -90,7 +97,11 @@ def bump_version() -> None:
if find_pattern in contents:
found_at_least_one_file_needing_update = True
actions.append(
(path, find_pattern, replace_pattern,)
(
path,
find_pattern,
replace_pattern,
)
)
if not found_at_least_one_file_needing_update:
@@ -125,18 +136,26 @@ def bump_version() -> None:
while input('Type "done" to continue: ').strip().lower() != 'done':
pass
subprocess.run([
'git', 'commit',
'--all',
f"--message=Bump version: v{new_version}",
], check=True)
subprocess.run(
[
'git',
'commit',
'--all',
f"--message=Bump version: v{new_version}",
],
check=True,
)
subprocess.run([
'git', 'tag',
'--annotate',
f"--message=v{new_version}",
f'v{new_version}',
], check=True)
subprocess.run(
[
'git',
'tag',
'--annotate',
f"--message=v{new_version}",
f'v{new_version}',
],
check=True,
)
print('Done.')
+11 -8
View File
@@ -31,10 +31,9 @@ def main():
print('Your git repo has uncommitted changes. Commit or stash before continuing.')
sys.exit(1)
previous_branch = shell('git rev-parse --abbrev-ref HEAD',
check=True,
capture_output=True,
encoding='utf8').stdout.strip()
previous_branch = shell(
'git rev-parse --abbrev-ref HEAD', check=True, capture_output=True, encoding='utf8'
).stdout.strip()
shell('git fetch origin', check=True)
@@ -51,20 +50,24 @@ def main():
return
shell('git commit -a -m "Update dependencies"', check=True)
body = textwrap.dedent(f'''
body = textwrap.dedent(
f'''
Update the versions of our dependencies.
PR generated by `{os.path.basename(__file__)}`.
''')
'''
)
run(
[
'gh', 'pr', 'create',
'gh',
'pr',
'create',
'--repo=joerick/cibuildwheel',
'--base=master',
"--title=Update dependencies",
f"--body='{body}'",
],
check=True
check=True,
)
print('Done.')
+12 -4
View File
@@ -83,9 +83,11 @@ class Project:
@classmethod
def header(cls) -> str:
return textwrap.dedent(f"""\
return textwrap.dedent(
f"""\
| {'Name':{cls.NAME}} | CI | OS | Notes |
|{'':-^{cls.NAME+2 }}|----|----|:------|""")
|{'':-^{cls.NAME+2 }}|----|----|:------|"""
)
@property
def namelink(self) -> str:
@@ -142,7 +144,10 @@ def path_for_icon(icon_name: str) -> Path:
def str_projects(
config: list[dict[str, Any]], *, online: bool = True, auth: str | None = None,
config: list[dict[str, Any]],
*,
online: bool = True,
auth: str | None = None,
) -> str:
io = StringIO()
print = functools.partial(builtins.print, file=io)
@@ -180,7 +185,10 @@ def str_projects(
@click.option("--auth", help="GitHub authentication token")
@click.option("--readme", type=click.File("r+"), help="Modify a readme file if given")
def projects(
input: TextIO, online: bool, auth: str | None, readme: TextIO | None,
input: TextIO,
online: bool,
auth: str | None,
readme: TextIO | None,
) -> None:
config = yaml.safe_load(input)
output = str_projects(config, online=online, auth=auth)
+16 -11
View File
@@ -64,7 +64,7 @@ services = [
CIService(
name='gitlab',
dst_config_path='.gitlab-ci.yml',
badge_md='[![Gitlab](https://gitlab.com/joerick/cibuildwheel/badges/{branch}/pipeline.svg)](https://gitlab.com/joerick/cibuildwheel/-/commits/{branch})'
badge_md='[![Gitlab](https://gitlab.com/joerick/cibuildwheel/badges/{branch}/pipeline.svg)](https://gitlab.com/joerick/cibuildwheel/-/commits/{branch})',
),
]
@@ -83,8 +83,8 @@ def ci_service_for_config_file(config_file):
@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
Test the example configs. If no files are specified, will test
examples/*-minimal.yml
'''
if len(config_files) == 0:
@@ -102,10 +102,9 @@ def run_example_ci_configs(config_files=None):
print('Your git repo has uncommitted changes. Commit or stash before continuing.')
sys.exit(1)
previous_branch = shell('git rev-parse --abbrev-ref HEAD',
check=True,
capture_output=True,
encoding='utf8').stdout.strip()
previous_branch = shell(
'git rev-parse --abbrev-ref HEAD', check=True, capture_output=True, encoding='utf8'
).stdout.strip()
timestamp = time.strftime('%Y-%m-%dT%H-%M-%S', time.gmtime())
branch_name = f'example-config-test---{previous_branch}-{timestamp}'
@@ -125,13 +124,15 @@ def run_example_ci_configs(config_files=None):
shutil.copyfile(src_config_file, dst_config_file)
run(['git', 'add', example_project], check=True)
message = textwrap.dedent(f'''\
message = textwrap.dedent(
f'''\
Test example minimal configs
Testing files: {config_files}
Generated from branch: {previous_branch}
Time: {timestamp}
''')
'''
)
run(['git', 'commit', '--no-verify', '--message', message], check=True)
shell(f'git subtree --prefix={example_project} push origin {branch_name}', check=True)
@@ -139,13 +140,17 @@ def run_example_ci_configs(config_files=None):
print()
print('> **Examples test run**')
print('> ')
print(f'> Branch: [{branch_name}](https://github.com/joerick/cibuildwheel/tree/{branch_name})')
print(
f'> Branch: [{branch_name}](https://github.com/joerick/cibuildwheel/tree/{branch_name})'
)
print('> ')
print('> | Service | Config | Status |')
print('> |---|---|---|')
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} | `{config_file}` | {badge} |')
print('> ')
print('> Generated by `bin/run_example_ci_config.py`')
+4 -1
View File
@@ -17,4 +17,7 @@ if __name__ == '__main__':
subprocess.run(unit_test_args, check=True)
# run the integration tests
subprocess.run([sys.executable, '-m', 'pytest', '-x', '--durations', '0', '--timeout=2400', 'test'], check=True)
subprocess.run(
[sys.executable, '-m', 'pytest', '-x', '--durations', '0', '--timeout=2400', 'test'],
check=True,
)
+4 -4
View File
@@ -19,9 +19,9 @@ if __name__ == '__main__':
options = parser.parse_args()
project_dir = tempfile.mkdtemp()
subprocess.run([
sys.executable, '-m', 'test.test_projects',
options.project_python_path, project_dir
], check=True,)
subprocess.run(
[sys.executable, '-m', 'test.test_projects', options.project_python_path, project_dir],
check=True,
)
sys.exit(subprocess.run([sys.executable, '-m', 'cibuildwheel'], cwd=project_dir).returncode)
+32 -31
View File
@@ -21,13 +21,16 @@ PYTHON_VERSIONS = ['27', '35', '36', '37', '38', '39']
if '--no-docker' in sys.argv:
for python_version in PYTHON_VERSIONS:
subprocess.run([
f'./env{python_version}/bin/pip-compile',
'--allow-unsafe',
'--upgrade',
'cibuildwheel/resources/constraints.in',
'--output-file=cibuildwheel/resources/constraints-python{python_version}.txt',
], check=True)
subprocess.run(
[
f'./env{python_version}/bin/pip-compile',
'--allow-unsafe',
'--upgrade',
'cibuildwheel/resources/constraints.in',
'--output-file=cibuildwheel/resources/constraints-python{python_version}.txt',
],
check=True,
)
else:
# latest manylinux2010 image with cpython 2.7 support
image_runner = 'quay.io/pypa/manylinux2010_x86_64:2021-02-06-3d322a5'
@@ -41,19 +44,27 @@ else:
'cibuildwheel/resources/constraints.in '
f'--output-file cibuildwheel/resources/constraints-python{python_version}.txt'
)
subprocess.run([
'docker', 'run',
'--rm',
'--env=CUSTOM_COMPILE_COMMAND',
"--volume={os.getcwd()}:/volume",
'--workdir=/volume',
image_runner,
'bash', '-c',
command,
], check=True)
subprocess.run(
[
'docker',
'run',
'--rm',
'--env=CUSTOM_COMPILE_COMMAND',
"--volume={os.getcwd()}:/volume",
'--workdir=/volume',
image_runner,
'bash',
'-c',
command,
],
check=True,
)
# default constraints.txt
shutil.copyfile(f'cibuildwheel/resources/constraints-python{PYTHON_VERSIONS[-1]}.txt', 'cibuildwheel/resources/constraints.txt',)
shutil.copyfile(
f'cibuildwheel/resources/constraints-python{PYTHON_VERSIONS[-1]}.txt',
'cibuildwheel/resources/constraints.txt',
)
class Image(NamedTuple):
@@ -66,20 +77,16 @@ class Image(NamedTuple):
images = [
Image('manylinux1', 'x86_64', 'quay.io/pypa/manylinux1_x86_64', None),
Image('manylinux1', 'i686', 'quay.io/pypa/manylinux1_i686', None),
# Images for manylinux2010 are pinned to the latest tag supporting cp27
Image('manylinux2010', 'x86_64', 'quay.io/pypa/manylinux2010_x86_64', '2021-02-06-3d322a5'),
Image('manylinux2010', 'i686', 'quay.io/pypa/manylinux2010_i686', '2021-02-06-3d322a5'),
Image('manylinux2010', 'pypy_x86_64', 'pypywheels/manylinux2010-pypy_x86_64', None),
# 2014 images
Image('manylinux2014', 'x86_64', 'quay.io/pypa/manylinux2014_x86_64', None),
Image('manylinux2014', 'i686', 'quay.io/pypa/manylinux2014_i686', None),
Image('manylinux2014', 'aarch64', 'quay.io/pypa/manylinux2014_aarch64', None),
Image('manylinux2014', 'ppc64le', 'quay.io/pypa/manylinux2014_ppc64le', None),
Image('manylinux2014', 's390x', 'quay.io/pypa/manylinux2014_s390x', None),
# 2_24 images
Image('manylinux_2_24', 'x86_64', 'quay.io/pypa/manylinux_2_24_x86_64', None),
Image('manylinux_2_24', 'i686', 'quay.io/pypa/manylinux_2_24_i686', None),
@@ -112,23 +119,17 @@ for image in images:
if info['manifest_digest'] == latest_tag['manifest_digest']
)
else:
response = requests.get(
f'https://hub.docker.com/v2/repositories/{image.image_name}/tags'
)
response = requests.get(f'https://hub.docker.com/v2/repositories/{image.image_name}/tags')
response.raise_for_status()
tags = response.json()['results']
latest_tag = next(
tag for tag in tags if tag['name'] == 'latest'
)
latest_tag = next(tag for tag in tags if tag['name'] == 'latest')
# i don't know what it would mean to have multiple images per tag
assert len(latest_tag['images']) == 1
digest = latest_tag['images'][0]['digest']
pinned_tag = next(
tag
for tag in tags
if tag != latest_tag and tag['images'][0]['digest'] == digest
tag for tag in tags if tag != latest_tag and tag['images'][0]['digest'] == digest
)
tag_name = pinned_tag['name']
+15 -5
View File
@@ -112,7 +112,9 @@ class PyPyVersions:
release["python_version"] = Version(release["python_version"])
self.releases = [
r for r in releases if not r["pypy_version"].is_prerelease and not r["pypy_version"].is_devrelease
r
for r in releases
if not r["pypy_version"].is_prerelease and not r["pypy_version"].is_devrelease
]
self.arch = arch_str
@@ -154,7 +156,9 @@ class PyPyVersions:
identifier = f"pp{version.major}{version.minor}-macosx_x86_64"
(url,) = [
rf["download_url"] for rf in release["files"] if "" in rf["platform"] == "darwin" and rf["arch"] == "x64"
rf["download_url"]
for rf in release["files"]
if "" in rf["platform"] == "darwin" and rf["arch"] == "x64"
]
return ConfigMacOS(
@@ -167,7 +171,9 @@ class PyPyVersions:
class CPythonVersions:
def __init__(self) -> None:
response = requests.get("https://www.python.org/api/v2/downloads/release/?is_published=true")
response = requests.get(
"https://www.python.org/api/v2/downloads/release/?is_published=true"
)
response.raise_for_status()
releases_info = response.json()
@@ -188,7 +194,9 @@ class CPythonVersions:
for version in reversed(sorted_versions):
# Find the first patch version that contains the requested file
uri = self.versions_dict[version]
response = requests.get(f"https://www.python.org/api/v2/downloads/release_file/?release={uri}")
response = requests.get(
f"https://www.python.org/api/v2/downloads/release_file/?release={uri}"
)
response.raise_for_status()
file_info = response.json()
@@ -252,7 +260,9 @@ class AllVersions:
@click.command()
@click.option("--force", is_flag=True)
@click.option("--level", default="INFO", type=click.Choice(["INFO", "DEBUG", "TRACE"], case_sensitive=False))
@click.option(
"--level", default="INFO", type=click.Choice(["INFO", "DEBUG", "TRACE"], case_sensitive=False)
)
def update_pythons(force: bool, level: str) -> None:
logging.basicConfig(
+132 -58
View File
@@ -30,10 +30,22 @@ from cibuildwheel.util import (
@overload
def get_option_from_environment(option_name: str, *, platform: Optional[str] = None, default: str) -> str: ... # noqa: E704
def get_option_from_environment(
option_name: str, *, platform: Optional[str] = None, default: str
) -> str:
... # noqa: E704
@overload
def get_option_from_environment(option_name: str, *, platform: Optional[str] = None, default: None = None) -> Optional[str]: ... # noqa: E704 E302
def get_option_from_environment(option_name: str, *, platform: Optional[str] = None, default: Optional[str] = None) -> Optional[str]: # noqa: E302
def get_option_from_environment(
option_name: str, *, platform: Optional[str] = None, default: None = None
) -> Optional[str]:
... # noqa: E704 E302
def get_option_from_environment(
option_name: str, *, platform: Optional[str] = None, default: Optional[str] = None
) -> Optional[str]: # noqa: E302
'''
Returns an option from the environment, optionally scoped by the platform.
@@ -59,53 +71,66 @@ def main() -> None:
epilog='''
Most options are supplied via environment variables.
See https://github.com/joerick/cibuildwheel#options for info.
''')
''',
)
parser.add_argument('--platform',
choices=['auto', 'linux', 'macos', 'windows'],
default=os.environ.get('CIBW_PLATFORM', 'auto'),
help='''
parser.add_argument(
'--platform',
choices=['auto', 'linux', 'macos', 'windows'],
default=os.environ.get('CIBW_PLATFORM', 'auto'),
help='''
Platform to build for. For "linux" you need docker running, on Mac
or Linux. For "macos", you need a Mac machine, and note that this
script is going to automatically install MacPython on your system,
so don't run on your development machine. For "windows", you need to
run in Windows, and it will build and test for all versions of
Python. Default: auto.
''')
''',
)
arch_list_str = ", ".join(a.name for a in Architecture)
parser.add_argument('--archs',
default=None,
help=f'''
parser.add_argument(
'--archs',
default=None,
help=f'''
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, auto64, auto32, native, all, {arch_list_str}
''')
''',
)
parser.add_argument('--output-dir',
default=os.environ.get('CIBW_OUTPUT_DIR', 'wheelhouse'),
help='Destination folder for the wheels.')
parser.add_argument(
'--output-dir',
default=os.environ.get('CIBW_OUTPUT_DIR', 'wheelhouse'),
help='Destination folder for the wheels.',
)
parser.add_argument('package_dir',
default='.',
nargs='?',
help='''
parser.add_argument(
'package_dir',
default='.',
nargs='?',
help='''
Path to the package that you want wheels for. Must be a subdirectory of
the working directory. When set, the working directory is still
considered the 'project' and is copied into the Docker container on
Linux. Default: the working directory.
''')
''',
)
parser.add_argument('--print-build-identifiers',
action='store_true',
help='Print the build identifiers matched by the current invocation and exit.')
parser.add_argument(
'--print-build-identifiers',
action='store_true',
help='Print the build identifiers matched by the current invocation and exit.',
)
parser.add_argument('--allow-empty',
action='store_true',
help='Do not report an error code if the build does not match any wheels.',)
parser.add_argument(
'--allow-empty',
action='store_true',
help='Do not report an error code if the build does not match any wheels.',
)
args = parser.parse_args()
@@ -116,12 +141,17 @@ def main() -> None:
else:
ci_provider = detect_ci_provider()
if ci_provider is None:
print(textwrap.dedent('''
print(
textwrap.dedent(
'''
cibuildwheel: Unable to detect platform. cibuildwheel should run on your CI server;
Travis CI, AppVeyor, Azure Pipelines, GitHub Actions, CircleCI, and Gitlab are
supported. You can run on your development machine or other CI providers using the
--platform argument. Check --help output for more information.
'''), file=sys.stderr)
'''
),
file=sys.stderr,
)
sys.exit(2)
if sys.platform.startswith('linux'):
platform = 'linux'
@@ -130,9 +160,11 @@ def main() -> None:
elif sys.platform == 'win32':
platform = 'windows'
else:
print('cibuildwheel: Unable to detect platform from "sys.platform" in a CI environment. You can run '
'cibuildwheel using the --platform argument. Check --help output for more information.',
file=sys.stderr)
print(
'cibuildwheel: Unable to detect platform from "sys.platform" in a CI environment. You can run '
'cibuildwheel using the --platform argument. Check --help output for more information.',
file=sys.stderr,
)
sys.exit(2)
if platform not in PLATFORMS:
@@ -154,29 +186,45 @@ def main() -> None:
build_config = os.environ.get('CIBW_BUILD') or '*'
skip_config = os.environ.get('CIBW_SKIP', '')
test_skip = os.environ.get('CIBW_TEST_SKIP', '')
environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='')
environment_config = get_option_from_environment(
'CIBW_ENVIRONMENT', platform=platform, default=''
)
before_all = get_option_from_environment('CIBW_BEFORE_ALL', platform=platform, default='')
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')
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'
)
test_command = get_option_from_environment('CIBW_TEST_COMMAND', platform=platform)
before_test = get_option_from_environment('CIBW_BEFORE_TEST', platform=platform)
test_requires = get_option_from_environment('CIBW_TEST_REQUIRES', platform=platform, default='').split()
test_requires = get_option_from_environment(
'CIBW_TEST_REQUIRES', platform=platform, default=''
).split()
test_extras = get_option_from_environment('CIBW_TEST_EXTRAS', platform=platform, default='')
build_verbosity_str = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='')
build_verbosity_str = get_option_from_environment(
'CIBW_BUILD_VERBOSITY', platform=platform, default=''
)
package_files = {'setup.py', 'setup.cfg', 'pyproject.toml'}
if not any(package_dir.joinpath(name).exists() for name in package_files):
names = ', '.join(sorted(package_files, reverse=True))
print(f'cibuildwheel: Could not find any of {{{names}}} at root of package', file=sys.stderr)
print(
f'cibuildwheel: Could not find any of {{{names}}} at root of package', file=sys.stderr
)
sys.exit(2)
# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
requires_python_str: Optional[str] = os.environ.get('CIBW_PROJECT_REQUIRES_PYTHON') or get_requires_python_str(package_dir)
requires_python_str: Optional[str] = os.environ.get(
'CIBW_PROJECT_REQUIRES_PYTHON'
) or get_requires_python_str(package_dir)
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
build_selector = BuildSelector(build_config=build_config, skip_config=skip_config, requires_python=requires_python)
build_selector = BuildSelector(
build_config=build_config, skip_config=skip_config, requires_python=requires_python
)
test_selector = TestSelector(skip_config=test_skip)
try:
@@ -187,7 +235,9 @@ def main() -> None:
sys.exit(2)
if dependency_versions == 'pinned':
dependency_constraints: Optional[DependencyConstraints] = DependencyConstraints.with_defaults()
dependency_constraints: Optional[
DependencyConstraints
] = DependencyConstraints.with_defaults()
elif dependency_versions == 'latest':
dependency_constraints = None
else:
@@ -209,7 +259,9 @@ def main() -> None:
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_config_str = get_option_from_environment(
'CIBW_ARCHS', platform=platform, default='auto'
)
archs = Architecture.parse_config(archs_config_str, platform=platform)
@@ -287,7 +339,9 @@ def main() -> None:
if not output_dir.exists():
output_dir.mkdir(parents=True)
with cibuildwheel.util.print_new_wheels("\n{n} wheels produced in {m:.0f} minutes:", output_dir):
with cibuildwheel.util.print_new_wheels(
"\n{n} wheels produced in {m:.0f} minutes:", output_dir
):
if platform == 'linux':
cibuildwheel.linux.build(build_options)
elif platform == 'windows':
@@ -300,10 +354,14 @@ def main() -> None:
def detect_obsolete_options() -> None:
# Check the old 'MANYLINUX1_*_IMAGE' options
for (deprecated, alternative) in [('CIBW_MANYLINUX1_X86_64_IMAGE', 'CIBW_MANYLINUX_X86_64_IMAGE'),
('CIBW_MANYLINUX1_I686_IMAGE', 'CIBW_MANYLINUX_I686_IMAGE')]:
for (deprecated, alternative) in [
('CIBW_MANYLINUX1_X86_64_IMAGE', 'CIBW_MANYLINUX_X86_64_IMAGE'),
('CIBW_MANYLINUX1_I686_IMAGE', 'CIBW_MANYLINUX_I686_IMAGE'),
]:
if deprecated in os.environ:
print(f"'{deprecated}' has been deprecated, and will be removed in a future release. Use the option '{alternative}' instead.")
print(
f"'{deprecated}' has been deprecated, and will be removed in a future release. Use the option '{alternative}' instead."
)
if alternative not in os.environ:
print(f"Using value of option '{deprecated}' as replacement for '{alternative}'")
os.environ[alternative] = os.environ[deprecated]
@@ -313,21 +371,29 @@ def detect_obsolete_options() -> None:
# Check for deprecated identifiers in 'CIBW_BUILD' and 'CIBW_SKIP' options
for option in ['CIBW_BUILD', 'CIBW_SKIP']:
for deprecated, alternative in [('manylinux1', 'manylinux'),
('macosx_10_6_intel', 'macosx_x86_64'),
('macosx_10_9_x86_64', 'macosx_x86_64')]:
for deprecated, alternative in [
('manylinux1', 'manylinux'),
('macosx_10_6_intel', 'macosx_x86_64'),
('macosx_10_9_x86_64', 'macosx_x86_64'),
]:
if option in os.environ and deprecated in os.environ[option]:
print(f"Build identifiers with '{deprecated}' have been deprecated. Replacing all occurences of '{deprecated}' with '{alternative}' in the option '{option}'")
print(
f"Build identifiers with '{deprecated}' have been deprecated. Replacing all occurences of '{deprecated}' with '{alternative}' in the option '{option}'"
)
os.environ[option] = os.environ[option].replace(deprecated, alternative)
def print_preamble(platform: str, build_options: BuildOptions) -> None:
print(textwrap.dedent('''
print(
textwrap.dedent(
'''
_ _ _ _ _ _ _
___|_| |_ _ _|_| |_| |_ _ _| |_ ___ ___| |
| _| | . | | | | | . | | | | | -_| -_| |
|___|_|___|___|_|_|___|_____|_|_|___|___|_|
'''))
'''
)
)
print(f'cibuildwheel version {cibuildwheel.__version__}\n')
@@ -348,16 +414,24 @@ def print_preamble(platform: str, build_options: BuildOptions) -> None:
def get_build_identifiers(
platform: PlatformName, build_selector: BuildSelector, architectures: Set[Architecture]
) -> List[str]:
python_configurations: Union[List[cibuildwheel.linux.PythonConfiguration],
List[cibuildwheel.windows.PythonConfiguration],
List[cibuildwheel.macos.PythonConfiguration]]
python_configurations: Union[
List[cibuildwheel.linux.PythonConfiguration],
List[cibuildwheel.windows.PythonConfiguration],
List[cibuildwheel.macos.PythonConfiguration],
]
if platform == 'linux':
python_configurations = cibuildwheel.linux.get_python_configurations(build_selector, architectures)
python_configurations = cibuildwheel.linux.get_python_configurations(
build_selector, architectures
)
elif platform == 'windows':
python_configurations = cibuildwheel.windows.get_python_configurations(build_selector, architectures)
python_configurations = cibuildwheel.windows.get_python_configurations(
build_selector, architectures
)
elif platform == 'macos':
python_configurations = cibuildwheel.macos.get_python_configurations(build_selector, architectures)
python_configurations = cibuildwheel.macos.get_python_configurations(
build_selector, architectures
)
else:
assert_never(platform)
+7 -1
View File
@@ -73,7 +73,13 @@ class Architecture(Enum):
@staticmethod
def all_archs(platform: PlatformName) -> 'Set[Architecture]':
if platform == 'linux':
return {Architecture.x86_64, Architecture.i686, Architecture.aarch64, Architecture.ppc64le, Architecture.s390x}
return {
Architecture.x86_64,
Architecture.i686,
Architecture.aarch64,
Architecture.ppc64le,
Architecture.s390x,
}
elif platform == 'macos':
return {Architecture.x86_64, Architecture.arm64, Architecture.universal2}
elif platform == 'windows':
+16 -6
View File
@@ -8,7 +8,9 @@ EnvironmentExecutor = Callable[[List[str], Dict[str, str]], str]
def local_environment_executor(command: List[str], env: Dict[str, str]) -> str:
return subprocess.run(command, env=env, universal_newlines=True, stdout=subprocess.PIPE, check=True).stdout
return subprocess.run(
command, env=env, universal_newlines=True, stdout=subprocess.PIPE, check=True
).stdout
class NodeExecutionContext(NamedTuple):
@@ -17,7 +19,9 @@ class NodeExecutionContext(NamedTuple):
executor: EnvironmentExecutor
def evaluate(value: str, environment: Dict[str, str], executor: Optional[EnvironmentExecutor] = None) -> str:
def evaluate(
value: str, environment: Dict[str, str], executor: Optional[EnvironmentExecutor] = None
) -> str:
if not value:
# empty string evaluates to empty string
# (but trips up bashlex)
@@ -32,7 +36,9 @@ def evaluate(value: str, environment: Dict[str, str], executor: Optional[Environ
return evaluate_node(
value_word_node,
context=NodeExecutionContext(environment=environment, input=value, executor=executor or local_environment_executor)
context=NodeExecutionContext(
environment=environment, input=value, executor=executor or local_environment_executor
),
)
@@ -53,7 +59,7 @@ def evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) ->
value: str = node.word
for part in node.parts:
part_string = context.input[part.pos[0]:part.pos[1]]
part_string = context.input[part.pos[0] : part.pos[1]]
part_value = evaluate_node(part, context=context)
if part_string not in value:
@@ -74,7 +80,9 @@ def evaluate_command_node(node: bashlex.ast.node, context: NodeExecutionContext)
return evaluate_nodes_as_simple_command(node.parts, context=context)
def evaluate_nodes_as_compound_command(nodes: Sequence[bashlex.ast.node], context: NodeExecutionContext) -> str:
def evaluate_nodes_as_compound_command(
nodes: Sequence[bashlex.ast.node], context: NodeExecutionContext
) -> str:
# bashlex doesn't support any operators besides ';' inside command
# substitutions, so we only need to handle that case. We do so assuming
# that `set -o errexit` is on, because it's easier to code!
@@ -92,7 +100,9 @@ def evaluate_nodes_as_compound_command(nodes: Sequence[bashlex.ast.node], contex
return result
def evaluate_nodes_as_simple_command(nodes: List[bashlex.ast.node], context: NodeExecutionContext) -> str:
def evaluate_nodes_as_simple_command(
nodes: List[bashlex.ast.node], context: NodeExecutionContext
) -> str:
command = [evaluate_node(part, context=context) for part in nodes]
return context.executor(command, context.environment)
+61 -32
View File
@@ -23,13 +23,16 @@ class DockerContainer:
the command is relayed to the remote shell, and the results are streamed
back to cibuildwheel.
'''
UTILITY_PYTHON = '/opt/python/cp38-cp38/bin/python'
process: PopenBytes
bash_stdin: IO[bytes]
bash_stdout: IO[bytes]
def __init__(self, docker_image: str, simulate_32_bit: bool = False, cwd: Optional[PathOrStr] = None):
def __init__(
self, docker_image: str, simulate_32_bit: bool = False, cwd: Optional[PathOrStr] = None
):
if not docker_image:
raise ValueError("Must have a non-empty docker image to run.")
@@ -44,21 +47,24 @@ class DockerContainer:
shell_args = ['linux32', '/bin/bash'] if self.simulate_32_bit else ['/bin/bash']
subprocess.run(
[
'docker', 'create',
'docker',
'create',
'--env=CIBUILDWHEEL',
f'--name={self.name}',
'--interactive',
'--volume=/:/host', # ignored on CircleCI
*cwd_args,
self.docker_image,
*shell_args
*shell_args,
],
check=True,
)
self.process = subprocess.Popen(
[
'docker', 'start',
'--attach', '--interactive',
'docker',
'start',
'--attach',
'--interactive',
self.name,
],
stdin=subprocess.PIPE,
@@ -75,10 +81,11 @@ class DockerContainer:
return self
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> None:
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
self.bash_stdin.close()
self.process.terminate()
@@ -101,12 +108,14 @@ class DockerContainer:
f'tar cf - . | docker exec -i {self.name} tar -xC {shell_quote(to_path)} -f -',
shell=True,
check=True,
cwd=from_path)
cwd=from_path,
)
else:
subprocess.run(
f'cat {shell_quote(from_path)} | docker exec -i {self.name} sh -c "cat > {shell_quote(to_path)}"',
shell=True,
check=True)
check=True,
)
def copy_out(self, from_path: PurePath, to_path: Path) -> None:
# note: we assume from_path is a dir
@@ -116,30 +125,39 @@ class DockerContainer:
f'docker exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -',
shell=True,
check=True,
cwd=to_path
cwd=to_path,
)
def glob(self, path: PurePath, pattern: str) -> List[PurePath]:
glob_pattern = os.path.join(str(path), pattern)
path_strs = json.loads(self.call([
self.UTILITY_PYTHON,
'-c',
f'import sys, json, glob; json.dump(glob.glob({glob_pattern!r}), sys.stdout)'
], capture_output=True))
path_strs = json.loads(
self.call(
[
self.UTILITY_PYTHON,
'-c',
f'import sys, json, glob; json.dump(glob.glob({glob_pattern!r}), sys.stdout)',
],
capture_output=True,
)
)
return [PurePath(p) for p in path_strs]
def call(
self,
args: Sequence[PathOrStr],
env: Optional[Dict[str, str]] = None,
capture_output: bool = False,
cwd: Optional[PathOrStr] = None) -> str:
self,
args: Sequence[PathOrStr],
env: Optional[Dict[str, str]] = None,
capture_output: bool = False,
cwd: Optional[PathOrStr] = None,
) -> str:
chdir = f'cd {cwd}' if cwd else ''
env_assignments = ' '.join(f'{shlex.quote(k)}={shlex.quote(v)}'
for k, v in env.items()) if env is not None else ''
env_assignments = (
' '.join(f'{shlex.quote(k)}={shlex.quote(v)}' for k, v in env.items())
if env is not None
else ''
)
command = ' '.join(shlex.quote(str(a)) for a in args)
end_of_message = str(uuid.uuid4())
@@ -153,12 +171,18 @@ class DockerContainer:
# Finally, the remote shell is told to write a footer - this will show
# up in the output so we know when to stop reading, and will include
# the returncode of `command`.
self.bash_stdin.write(bytes(f'''(
self.bash_stdin.write(
bytes(
f'''(
{chdir}
env {env_assignments} {command}
printf "%04d%s\n" $? {end_of_message}
)
''', encoding='utf8', errors='surrogateescape'))
''',
encoding='utf8',
errors='surrogateescape',
)
)
self.bash_stdin.flush()
if capture_output:
@@ -178,7 +202,7 @@ class DockerContainer:
- 4 # 4 returncode decimals
)
# fmt: on
returncode_str = line[footer_offset:footer_offset+4]
returncode_str = line[footer_offset : footer_offset + 4]
returncode = int(returncode_str)
# add the last line to output, without the footer
output_io.write(line[0:footer_offset])
@@ -197,11 +221,16 @@ class DockerContainer:
return output
def get_environment(self) -> Dict[str, str]:
env = json.loads(self.call([
self.UTILITY_PYTHON,
'-c',
'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'
], capture_output=True))
env = json.loads(
self.call(
[
self.UTILITY_PYTHON,
'-c',
'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)',
],
capture_output=True,
)
)
return cast(Dict[str, str], env)
def environment_executor(self, command: List[str], environment: Dict[str, str]) -> str:
+11 -5
View File
@@ -32,7 +32,7 @@ def split_env_items(env_string: str) -> List[str]:
result = []
for word_node in command_node.parts:
part_string = env_string[word_node.pos[0]:word_node.pos[1]]
part_string = env_string[word_node.pos[0] : word_node.pos[1]]
result.append(part_string)
return result
@@ -46,7 +46,11 @@ class EnvironmentAssignment:
self.name = name
self.value = value
def evaluated_value(self, environment: Dict[str, str], executor: Optional[bashlex_eval.EnvironmentExecutor] = None) -> str:
def evaluated_value(
self,
environment: Dict[str, str],
executor: Optional[bashlex_eval.EnvironmentExecutor] = None,
) -> str:
'''Returns the value of this assignment, as evaluated in the environment'''
return bashlex_eval.evaluate(self.value, environment=environment, executor=executor)
@@ -61,9 +65,11 @@ class ParsedEnvironment:
def __init__(self, assignments: List[EnvironmentAssignment]):
self.assignments = assignments
def as_dictionary(self,
prev_environment: Mapping[str, str],
executor: Optional[bashlex_eval.EnvironmentExecutor] = None) -> Dict[str, str]:
def as_dictionary(
self,
prev_environment: Mapping[str, str],
executor: Optional[bashlex_eval.EnvironmentExecutor] = None,
) -> Dict[str, str]:
environment = dict(**prev_environment)
for assignment in self.assignments:
+115 -36
View File
@@ -41,7 +41,8 @@ def get_python_configurations(
# return all configurations whose arch is in our `architectures` set,
# and match the build/skip rules
return [
c for c in python_configurations
c
for c in python_configurations
if any(c.identifier.endswith(arch.value) for arch in architectures)
and build_selector(c.identifier)
]
@@ -52,10 +53,12 @@ def build(options: BuildOptions) -> None:
# check docker is installed
subprocess.run(['docker', '--version'], check=True, stdout=subprocess.DEVNULL)
except Exception:
print('cibuildwheel: Docker not found. Docker is required to run Linux builds. '
'If you\'re building on Travis CI, add `services: [docker]` to your .travis.yml.'
'If you\'re building on Circle CI in Linux, add a `setup_remote_docker` step to your .circleci/config.yml',
file=sys.stderr)
print(
'cibuildwheel: Docker not found. Docker is required to run Linux builds. '
'If you\'re building on Travis CI, add `services: [docker]` to your .travis.yml.'
'If you\'re building on Circle CI in Linux, add a `setup_remote_docker` step to your .circleci/config.yml',
file=sys.stderr,
)
sys.exit(2)
assert options.manylinux_images is not None
@@ -79,13 +82,21 @@ def build(options: BuildOptions) -> None:
container_output_dir = PurePath('/output')
for implementation, platform_tag, docker_image in platforms:
platform_configs = [c for c in python_configurations if c.identifier.startswith(implementation) and c.identifier.endswith(platform_tag)]
platform_configs = [
c
for c in python_configurations
if c.identifier.startswith(implementation) and c.identifier.endswith(platform_tag)
]
if not platform_configs:
continue
try:
log.step(f'Starting Docker image {docker_image}...')
with DockerContainer(docker_image, simulate_32_bit=platform_tag.endswith('i686'), cwd=container_project_path) as docker:
with DockerContainer(
docker_image,
simulate_32_bit=platform_tag.endswith('i686'),
cwd=container_project_path,
) as docker:
log.step('Copying project into Docker...')
docker.copy_into(Path.cwd(), container_project_path)
@@ -96,9 +107,15 @@ def build(options: BuildOptions) -> None:
env = docker.get_environment()
env['PATH'] = f'/opt/python/cp38-cp38/bin:{env["PATH"]}'
env['PIP_DISABLE_PIP_VERSION_CHECK'] = '1'
env = options.environment.as_dictionary(env, executor=docker.environment_executor)
env = options.environment.as_dictionary(
env, executor=docker.environment_executor
)
before_all_prepared = prepare_command(options.before_all, project=container_project_path, package=container_package_dir)
before_all_prepared = prepare_command(
options.before_all,
project=container_project_path,
package=container_package_dir,
)
docker.call(['sh', '-c', before_all_prepared], env=env)
for config in platform_configs:
@@ -112,12 +129,24 @@ def build(options: BuildOptions) -> None:
patch_docker_path = PurePath('/pypy_venv.patch')
docker.copy_into(patch_path, patch_docker_path)
try:
docker.call(['patch', '--force', '-p1', '-d', config.path, '-i', patch_docker_path])
docker.call(
[
'patch',
'--force',
'-p1',
'-d',
config.path,
'-i',
patch_docker_path,
]
)
except subprocess.CalledProcessError:
print("PyPy patch not applied", file=sys.stderr)
if options.dependency_constraints:
constraints_file = options.dependency_constraints.get_for_python_version(config.version)
constraints_file = options.dependency_constraints.get_for_python_version(
config.version
)
container_constraints_file = PurePath('/constraints.txt')
docker.copy_into(constraints_file, container_constraints_file)
@@ -131,22 +160,36 @@ def build(options: BuildOptions) -> None:
python_bin = config.path / 'bin'
env['PATH'] = f'{python_bin}:{env["PATH"]}'
env = options.environment.as_dictionary(env, executor=docker.environment_executor)
env = options.environment.as_dictionary(
env, executor=docker.environment_executor
)
# check config python and pip are still on PATH
which_python = docker.call(['which', 'python'], env=env, capture_output=True).strip()
which_python = docker.call(
['which', 'python'], env=env, capture_output=True
).strip()
if PurePath(which_python) != python_bin / 'python':
print("cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", file=sys.stderr)
print(
"cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.",
file=sys.stderr,
)
sys.exit(1)
which_pip = docker.call(['which', 'pip'], env=env, capture_output=True).strip()
if PurePath(which_pip) != python_bin / 'pip':
print("cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.", file=sys.stderr)
print(
"cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.",
file=sys.stderr,
)
sys.exit(1)
if options.before_build:
log.step('Running before_build...')
before_build_prepared = prepare_command(options.before_build, project=container_project_path, package=container_package_dir)
before_build_prepared = prepare_command(
options.before_build,
project=container_project_path,
package=container_package_dir,
)
docker.call(['sh', '-c', before_build_prepared], env=env)
log.step('Building wheel...')
@@ -156,13 +199,18 @@ def build(options: BuildOptions) -> None:
docker.call(['rm', '-rf', built_wheel_dir])
docker.call(['mkdir', '-p', built_wheel_dir])
docker.call([
'pip', 'wheel',
container_package_dir,
'--wheel-dir', built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity)
], env=env)
docker.call(
[
'pip',
'wheel',
container_package_dir,
'--wheel-dir',
built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity),
],
env=env,
)
built_wheel = docker.glob(built_wheel_dir, '*.whl')[0]
@@ -175,7 +223,9 @@ def build(options: BuildOptions) -> None:
if options.repair_command:
log.step('Repairing wheel...')
repair_command_prepared = prepare_command(options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
repair_command_prepared = prepare_command(
options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
)
docker.call(['sh', '-c', repair_command_prepared], env=env)
else:
docker.call(['mv', built_wheel, repaired_wheel_dir])
@@ -187,16 +237,27 @@ def build(options: BuildOptions) -> None:
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
docker.call(['pip', 'install', 'virtualenv', *dependency_constraint_flags], env=env)
venv_dir = PurePath(docker.call(['mktemp', '-d'], capture_output=True).strip()) / 'venv'
docker.call(
['pip', 'install', 'virtualenv', *dependency_constraint_flags], env=env
)
venv_dir = (
PurePath(docker.call(['mktemp', '-d'], capture_output=True).strip())
/ 'venv'
)
docker.call(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
docker.call(
['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env
)
virtualenv_env = env.copy()
virtualenv_env['PATH'] = f"{venv_dir / 'bin'}:{virtualenv_env['PATH']}"
if options.before_test:
before_test_prepared = prepare_command(options.before_test, project=container_project_path, package=container_package_dir)
before_test_prepared = prepare_command(
options.before_test,
project=container_project_path,
package=container_package_dir,
)
docker.call(['sh', '-c', before_test_prepared], env=virtualenv_env)
# Install the wheel we just built
@@ -206,15 +267,26 @@ def build(options: BuildOptions) -> None:
# different external shared libraries. so it doesn't matter which one we run the tests on.
# Let's just pick the first one.
wheel_to_test = repaired_wheels[0]
docker.call(['pip', 'install', str(wheel_to_test) + options.test_extras], env=virtualenv_env)
docker.call(
['pip', 'install', str(wheel_to_test) + options.test_extras],
env=virtualenv_env,
)
# Install any requirements to run the tests
if options.test_requires:
docker.call(['pip', 'install', *options.test_requires], env=virtualenv_env)
docker.call(
['pip', 'install', *options.test_requires], env=virtualenv_env
)
# Run the tests from a different directory
test_command_prepared = prepare_command(options.test_command, project=container_project_path, package=container_package_dir)
docker.call(['sh', '-c', test_command_prepared], cwd='/root', env=virtualenv_env)
test_command_prepared = prepare_command(
options.test_command,
project=container_project_path,
package=container_package_dir,
)
docker.call(
['sh', '-c', test_command_prepared], cwd='/root', env=virtualenv_env
)
# clean up test environment
docker.call(['rm', '-rf', venv_dir])
@@ -230,19 +302,23 @@ def build(options: BuildOptions) -> None:
docker.copy_out(container_output_dir, options.output_dir)
log.step_end()
except subprocess.CalledProcessError as error:
log.step_end_with_error(f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}')
log.step_end_with_error(
f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}'
)
troubleshoot(options.package_dir, error)
sys.exit(1)
def troubleshoot(package_dir: Path, error: Exception) -> None:
if (isinstance(error, subprocess.CalledProcessError) and error.cmd[0:2] == ['pip', 'wheel']):
if isinstance(error, subprocess.CalledProcessError) and error.cmd[0:2] == ['pip', 'wheel']:
# the 'pip wheel' step failed.
print('Checking for common errors...')
so_files = list(package_dir.glob('**/*.so'))
if so_files:
print(textwrap.dedent('''
print(
textwrap.dedent(
'''
NOTE: Shared object (.so) files found in this project.
These files might be built against the wrong OS, causing problems with
@@ -250,7 +326,10 @@ def troubleshoot(package_dir: Path, error: Exception) -> None:
If you're using Cython and have previously done an in-place build,
remove those build files (*.so and *.c) before starting cibuildwheel.
'''), file=sys.stderr)
'''
),
file=sys.stderr,
)
print(' Files detected:')
print('\n'.join(f' {f}' for f in so_files))
+9 -5
View File
@@ -89,7 +89,9 @@ class Logger:
duration = time.time() - self.build_start_time
print()
print(f'{c.green}{s.done} {c.end}{self.active_build_identifier} finished in {duration:.2f}s')
print(
f'{c.green}{s.done} {c.end}{self.active_build_identifier} finished in {duration:.2f}s'
)
self.build_start_time = None
self.active_build_identifier = None
@@ -143,7 +145,9 @@ class Logger:
if self.active_fold_group_name:
fold_start_pattern = FOLD_PATTERNS.get(self.fold_mode, DEFAULT_FOLD_PATTERN)[1]
identifier = self._fold_group_identifier(self.active_fold_group_name)
print(fold_start_pattern.format(name=self.active_fold_group_name, identifier=identifier))
print(
fold_start_pattern.format(name=self.active_fold_group_name, identifier=identifier)
)
sys.stdout.flush()
self.active_fold_group_name = None
@@ -231,11 +235,11 @@ def file_supports_color(file_obj: IO[AnyStr]) -> bool:
Returns True if the running system's terminal supports color.
"""
plat = sys.platform
supported_platform = (plat != 'win32' or 'ANSICON' in os.environ)
supported_platform = plat != 'win32' or 'ANSICON' in os.environ
is_a_tty = file_is_a_tty(file_obj)
return (supported_platform and is_a_tty)
return supported_platform and is_a_tty
def file_is_a_tty(file_obj: IO[AnyStr]) -> bool:
@@ -249,7 +253,7 @@ def file_supports_unicode(file_obj: IO[AnyStr]) -> bool:
codec_info = codecs.lookup(encoding)
return ('utf' in codec_info.name)
return 'utf' in codec_info.name
'''
+169 -60
View File
@@ -28,7 +28,12 @@ from .util import (
)
def call(args: Sequence[PathOrStr], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None, shell: bool = False) -> None:
def call(
args: Sequence[PathOrStr],
env: Optional[Dict[str, str]] = None,
cwd: Optional[str] = None,
shell: bool = False,
) -> None:
# print the command executing for the logs
if shell:
print(f'+ {args}')
@@ -69,16 +74,20 @@ class PythonConfiguration(NamedTuple):
url: str
def get_python_configurations(build_selector: BuildSelector,
architectures: Set[Architecture]) -> List[PythonConfiguration]:
def get_python_configurations(
build_selector: BuildSelector, architectures: Set[Architecture]
) -> List[PythonConfiguration]:
full_python_configs = read_python_configs('macos')
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
# filter out configs that don't match any of the selected architectures
python_configurations = [c for c in python_configurations
if any(c.identifier.endswith(a.value) for a in architectures)]
python_configurations = [
c
for c in python_configurations
if any(c.identifier.endswith(a.value) for a in architectures)
]
# skip builds as required by BUILD/SKIP
python_configurations = [c for c in python_configurations if build_selector(c.identifier)]
@@ -89,21 +98,33 @@ def get_python_configurations(build_selector: BuildSelector,
if any(c.identifier.startswith('pp') for c in python_configurations):
# pypy doesn't work on macOS 11 yet
# See https://foss.heptapod.net/pypy/pypy/-/issues/3314
log.warning(unwrap('''
log.warning(
unwrap(
'''
PyPy is currently unsupported when building on macOS 11. To build macOS PyPy wheels,
build on an older OS, such as macOS 10.15. To silence this warning, deselect PyPy by
adding "pp*-macosx*" to your CIBW_SKIP option.
'''))
python_configurations = [c for c in python_configurations if not c.identifier.startswith('pp')]
'''
)
)
python_configurations = [
c for c in python_configurations if not c.identifier.startswith('pp')
]
if any(c.identifier.startswith('cp35') for c in python_configurations):
# CPython 3.5 doesn't work on macOS 11
log.warning(unwrap('''
log.warning(
unwrap(
'''
CPython 3.5 is unsupported when building on macOS 11. To build CPython 3.5 wheels,
build on an older OS, such as macOS 10.15. To silence this warning, deselect CPython
3.5 by adding "cp35-macosx_x86_64" to your CIBW_SKIP option.
'''))
python_configurations = [c for c in python_configurations if not c.identifier.startswith('cp35')]
'''
)
)
python_configurations = [
c for c in python_configurations if not c.identifier.startswith('cp35')
]
return python_configurations
@@ -122,12 +143,16 @@ def make_symlinks(installation_bin_path: Path, python_executable: str, pip_execu
SYMLINKS_DIR.mkdir(parents=True)
(SYMLINKS_DIR / 'python').symlink_to(installation_bin_path / python_executable)
(SYMLINKS_DIR / 'python-config').symlink_to(installation_bin_path / (python_executable + '-config'))
(SYMLINKS_DIR / 'python-config').symlink_to(
installation_bin_path / (python_executable + '-config')
)
(SYMLINKS_DIR / 'pip').symlink_to(installation_bin_path / pip_executable)
def install_cpython(version: str, url: str) -> Path:
installed_system_packages = subprocess.run(['pkgutil', '--pkgs'], universal_newlines=True, check=True, stdout=subprocess.PIPE).stdout.splitlines()
installed_system_packages = subprocess.run(
['pkgutil', '--pkgs'], universal_newlines=True, check=True, stdout=subprocess.PIPE
).stdout.splitlines()
# if this version of python isn't installed, get it from python.org and install
python_package_identifier = f'org.python.Python.PythonFramework-{version}'
@@ -143,9 +168,18 @@ def install_cpython(version: str, url: str) -> Path:
if version == '3.5':
open_ssl_patch_url = f'https://github.com/mayeut/patch-macos-python-openssl/releases/download/v1.1.1h/patch-macos-python-{version}-openssl-v1.1.1h.tar.gz'
download(open_ssl_patch_url, Path('/tmp/python-patch.tar.gz'))
call(['sudo', 'tar', '-C', f'/Library/Frameworks/Python.framework/Versions/{version}/', '-xmf', '/tmp/python-patch.tar.gz'])
call(
[
'sudo',
'tar',
'-C',
f'/Library/Frameworks/Python.framework/Versions/{version}/',
'-xmf',
'/tmp/python-patch.tar.gz',
]
)
call(["sudo", str(installation_bin_path/python_executable), str(install_certifi_script)])
call(["sudo", str(installation_bin_path / python_executable), str(install_certifi_script)])
pip_executable = 'pip3' if version[0] == '3' else 'pip'
make_symlinks(installation_bin_path, python_executable, pip_executable)
@@ -157,7 +191,7 @@ def install_pypy(version: str, url: str) -> Path:
pypy_tar_bz2 = url.rsplit('/', 1)[-1]
extension = ".tar.bz2"
assert pypy_tar_bz2.endswith(extension)
pypy_base_filename = pypy_tar_bz2[:-len(extension)]
pypy_base_filename = pypy_tar_bz2[: -len(extension)]
installation_path = Path('/tmp') / pypy_base_filename
if not installation_path.exists():
downloaded_tar_bz2 = Path("/tmp") / pypy_tar_bz2
@@ -176,14 +210,18 @@ def install_pypy(version: str, url: str) -> Path:
return installation_bin_path
def setup_python(python_configuration: PythonConfiguration,
dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment) -> Dict[str, str]:
def setup_python(
python_configuration: PythonConfiguration,
dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment,
) -> Dict[str, str]:
implementation_id = python_configuration.identifier.split("-")[0]
log.step(f'Installing Python {implementation_id}...')
if implementation_id.startswith('cp'):
installation_bin_path = install_cpython(python_configuration.version, python_configuration.url)
installation_bin_path = install_cpython(
python_configuration.version, python_configuration.url
)
elif implementation_id.startswith('pp'):
installation_bin_path = install_pypy(python_configuration.version, python_configuration.url)
else:
@@ -192,11 +230,13 @@ def setup_python(python_configuration: PythonConfiguration,
log.step('Setting up build environment...')
env = os.environ.copy()
env['PATH'] = os.pathsep.join([
str(SYMLINKS_DIR),
str(installation_bin_path),
env['PATH'],
])
env['PATH'] = os.pathsep.join(
[
str(SYMLINKS_DIR),
str(installation_bin_path),
env['PATH'],
]
)
# Fix issue with site.py setting the wrong `sys.prefix`, `sys.exec_prefix`,
# `sys.path`, ... for PyPy: https://foss.heptapod.net/pypy/pypy/issues/3175
@@ -213,9 +253,14 @@ def setup_python(python_configuration: PythonConfiguration,
# check what version we're on
call(['which', 'python'], env=env)
call(['python', '--version'], env=env)
which_python = subprocess.run(['which', 'python'], env=env, universal_newlines=True, check=True, stdout=subprocess.PIPE).stdout.strip()
which_python = subprocess.run(
['which', 'python'], env=env, universal_newlines=True, check=True, stdout=subprocess.PIPE
).stdout.strip()
if which_python != '/tmp/cibw_bin/python':
print("cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", file=sys.stderr)
print(
"cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.",
file=sys.stderr,
)
sys.exit(1)
# install pip & wheel
@@ -223,9 +268,14 @@ def setup_python(python_configuration: PythonConfiguration,
assert (installation_bin_path / 'pip').exists()
call(['which', 'pip'], env=env)
call(['pip', '--version'], env=env)
which_pip = subprocess.run(['which', 'pip'], env=env, universal_newlines=True, check=True, stdout=subprocess.PIPE).stdout.strip()
which_pip = subprocess.run(
['which', 'pip'], env=env, universal_newlines=True, check=True, stdout=subprocess.PIPE
).stdout.strip()
if which_pip != '/tmp/cibw_bin/pip':
print("cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.", file=sys.stderr)
print(
"cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.",
file=sys.stderr,
)
sys.exit(1)
# Set MACOSX_DEPLOYMENT_TARGET to 10.9, if the user didn't set it.
@@ -271,15 +321,30 @@ def setup_python(python_configuration: PythonConfiguration,
arm64_compatible_sdks = [s for s in sdks if not s.startswith('macosx10.')]
if not arm64_compatible_sdks:
log.warning(unwrap('''
log.warning(
unwrap(
'''
SDK for building arm64-compatible wheels not found. You need Xcode 12.2 or later
to build universal2 or arm64 wheels.
'''))
'''
)
)
else:
env.setdefault('SDKROOT', arm64_compatible_sdks[0])
log.step('Installing build tools...')
call(['pip', 'install', '--upgrade', 'setuptools', 'wheel', 'delocate', *dependency_constraint_flags], env=env)
call(
[
'pip',
'install',
'--upgrade',
'setuptools',
'wheel',
'delocate',
*dependency_constraint_flags,
],
env=env,
)
return env
@@ -294,10 +359,14 @@ def build(options: BuildOptions) -> None:
log.step('Running before_all...')
env = options.environment.as_dictionary(prev_environment=os.environ)
env.setdefault('MACOSX_DEPLOYMENT_TARGET', '10.9')
before_all_prepared = prepare_command(options.before_all, project='.', package=options.package_dir)
before_all_prepared = prepare_command(
options.before_all, project='.', package=options.package_dir
)
call([before_all_prepared], shell=True, env=env)
python_configurations = get_python_configurations(options.build_selector, options.architectures)
python_configurations = get_python_configurations(
options.build_selector, options.architectures
)
for config in python_configurations:
log.build_start(config.identifier)
@@ -308,14 +377,17 @@ def build(options: BuildOptions) -> None:
dependency_constraint_flags: Sequence[PathOrStr] = []
if options.dependency_constraints:
dependency_constraint_flags = [
'-c', options.dependency_constraints.get_for_python_version(config.version)
'-c',
options.dependency_constraints.get_for_python_version(config.version),
]
env = setup_python(config, dependency_constraint_flags, options.environment)
if options.before_build:
log.step('Running before_build...')
before_build_prepared = prepare_command(options.before_build, project='.', package=options.package_dir)
before_build_prepared = prepare_command(
options.before_build, project='.', package=options.package_dir
)
call(before_build_prepared, env=env, shell=True)
log.step('Building wheel...')
@@ -325,13 +397,18 @@ def build(options: BuildOptions) -> None:
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/joerick/cibuildwheel/pull/369
call([
'pip', 'wheel',
options.package_dir.resolve(),
'--wheel-dir', built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity)
], env=env)
call(
[
'pip',
'wheel',
options.package_dir.resolve(),
'--wheel-dir',
built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity),
],
env=env,
)
built_wheel = next(built_wheel_dir.glob('*.whl'))
@@ -385,27 +462,39 @@ def build(options: BuildOptions) -> None:
if machine_arch == 'x86_64' and testing_arch == 'arm64':
if config_is_arm64:
log.warning(unwrap('''
log.warning(
unwrap(
'''
While arm64 wheels can be built on x86_64, they cannot be tested. The
ability to test the arm64 wheels will be added in a future release of
cibuildwheel, once Apple Silicon CI runners are widely available. To
silence this warning, set `CIBW_TEST_SKIP: *-macosx_arm64`.
'''))
'''
)
)
elif config_is_universal2:
log.warning(unwrap('''
log.warning(
unwrap(
'''
While universal2 wheels can be built on x86_64, the arm64 part of them
cannot currently be tested. The ability to test the arm64 part of a
universal2 wheel will be added in a future release of cibuildwheel, once
Apple Silicon CI runners are widely available. To silence this warning,
set `CIBW_TEST_SKIP: *-macosx_universal2:arm64`.
'''))
'''
)
)
else:
raise RuntimeError('unreachable')
# skip this test
continue
log.step('Testing wheel...' if testing_arch == machine_arch else f'Testing wheel on {testing_arch}...')
log.step(
'Testing wheel...'
if testing_arch == machine_arch
else f'Testing wheel on {testing_arch}...'
)
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
@@ -418,7 +507,9 @@ def build(options: BuildOptions) -> None:
# rosetta2 will provide the emulation with just the arch prefix.
arch_prefix = ['arch', '-x86_64']
else:
raise RuntimeError("don't know how to emulate {testing_arch} on {machine_arch}")
raise RuntimeError(
"don't know how to emulate {testing_arch} on {machine_arch}"
)
# define a custom 'call' function that adds the arch prefix each time
def call_with_arch(args: Sequence[PathOrStr], **kwargs: Any) -> None:
@@ -430,27 +521,38 @@ def build(options: BuildOptions) -> None:
# Use --no-download to ensure determinism by using seed libraries
# built into virtualenv
call_with_arch(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
call_with_arch(
['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env
)
virtualenv_env = env.copy()
virtualenv_env['PATH'] = os.pathsep.join([
str(venv_dir / 'bin'),
virtualenv_env['PATH'],
])
virtualenv_env['PATH'] = os.pathsep.join(
[
str(venv_dir / 'bin'),
virtualenv_env['PATH'],
]
)
# check that we are using the Python from the virtual environment
call_with_arch(['which', 'python'], env=virtualenv_env)
if options.before_test:
before_test_prepared = prepare_command(options.before_test, project='.', package=options.package_dir)
before_test_prepared = prepare_command(
options.before_test, project='.', package=options.package_dir
)
call_with_arch(before_test_prepared, env=virtualenv_env, shell=True)
# install the wheel
call_with_arch(['pip', 'install', f"{repaired_wheel}{options.test_extras}"], env=virtualenv_env)
call_with_arch(
['pip', 'install', f"{repaired_wheel}{options.test_extras}"],
env=virtualenv_env,
)
# test the wheel
if options.test_requires:
call_with_arch(['pip', 'install'] + options.test_requires, env=virtualenv_env)
call_with_arch(
['pip', 'install'] + options.test_requires, env=virtualenv_env
)
# run the tests from $HOME, with an absolute path in the command
# (this ensures that Python runs the tests against the installed wheel
@@ -458,9 +560,14 @@ def build(options: BuildOptions) -> None:
test_command_prepared = prepare_command(
options.test_command,
project=Path('.').resolve(),
package=options.package_dir.resolve()
package=options.package_dir.resolve(),
)
call_with_arch(
test_command_prepared,
cwd=os.environ['HOME'],
env=virtualenv_env,
shell=True,
)
call_with_arch(test_command_prepared, cwd=os.environ['HOME'], env=virtualenv_env, shell=True)
# clean up
shutil.rmtree(venv_dir)
@@ -469,5 +576,7 @@ def build(options: BuildOptions) -> None:
shutil.move(str(repaired_wheel), options.output_dir)
log.build_end()
except subprocess.CalledProcessError as error:
log.step_end_with_error(f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}')
log.step_end_with_error(
f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}'
)
sys.exit(1)
+2 -3
View File
@@ -35,9 +35,8 @@ class Analyzer(ast.NodeVisitor):
if node.arg == "python_requires":
# Must not be nested in an if or other structure
# This will be Module -> Expr -> Call -> keyword
if (
not hasattr(node.parent.parent.parent, "parent") # type: ignore
and isinstance(node.value, Constant)
if not hasattr(node.parent.parent.parent, "parent") and isinstance( # type: ignore
node.value, Constant
):
self.requires_python = get_constant(node.value)
+12 -8
View File
@@ -34,14 +34,17 @@ PY3 = sys.version_info[0] == 3
if PY3:
iterbytes = iter
else:
def iterbytes(buf):
return (ord(byte) for byte in buf)
try:
from base64 import b85decode
except ImportError:
_b85alphabet = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~")
_b85alphabet = (
b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"
)
def b85decode(b):
_b85dec = [None] * 256
@@ -53,7 +56,7 @@ except ImportError:
out = []
packI = struct.Struct('!I').pack
for i in range(0, len(b), 5):
chunk = b[i:i + 5]
chunk = b[i : i + 5]
acc = 0
try:
for c in iterbytes(chunk):
@@ -61,15 +64,12 @@ except ImportError:
except TypeError:
for j, c in enumerate(iterbytes(chunk)):
if _b85dec[c] is None:
raise ValueError(
'bad base85 character at position %d' % (i + j)
)
raise ValueError('bad base85 character at position %d' % (i + j))
raise
try:
out.append(packI(acc))
except struct.error:
raise ValueError('base85 overflow in hunk starting at byte %d'
% i)
raise ValueError('base85 overflow in hunk starting at byte %d' % i)
result = b''.join(out)
if padding:
@@ -87,6 +87,7 @@ def bootstrap(tmpdir=None):
# Due to pip._internal.commands.commands_dict structure, a monkeypatch
# seems the simplest workaround.
install_parse_args = InstallCommand.parse_args
def cert_parse_args(self, args):
# If cert isn't specified in config or environment, we provide our
# own certificate through defaults.
@@ -95,6 +96,7 @@ def bootstrap(tmpdir=None):
if not self.parser.get_default_values().cert:
self.parser.defaults["cert"] = cert_path # calculated below
return install_parse_args(self, args)
InstallCommand.parse_args = cert_parse_args
implicit_pip = True
@@ -118,12 +120,14 @@ def bootstrap(tmpdir=None):
if implicit_setuptools:
try:
import setuptools # noqa
implicit_setuptools = False
except ImportError:
pass
if implicit_wheel:
try:
import wheel # noqa
implicit_wheel = False
except ImportError:
pass
+14 -7
View File
@@ -13,20 +13,27 @@ import stat
import subprocess
import sys
STAT_0o775 = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
| stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
| stat.S_IROTH | stat.S_IXOTH)
STAT_0o775 = (
stat.S_IRUSR
| stat.S_IWUSR
| stat.S_IXUSR
| stat.S_IRGRP
| stat.S_IWGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH
)
if sys.version_info[0] == 2:
FileNotFoundError = OSError
def main():
openssl_dir, openssl_cafile = os.path.split(
ssl.get_default_verify_paths().openssl_cafile)
openssl_dir, openssl_cafile = os.path.split(ssl.get_default_verify_paths().openssl_cafile)
print(" -- pip install --upgrade certifi")
subprocess.check_call([sys.executable,
"-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"])
subprocess.check_call(
[sys.executable, "-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"]
)
import certifi
+12 -1
View File
@@ -9,7 +9,18 @@ else:
from typing import Final, Literal, TypedDict
__all__ = ("Final", "Literal", "TypedDict", "Set", "Union", "PopenBytes", "PathOrStr", "PlatformName", "PLATFORMS", "assert_never")
__all__ = (
"Final",
"Literal",
"TypedDict",
"Set",
"Union",
"PopenBytes",
"PathOrStr",
"PlatformName",
"PLATFORMS",
"assert_never",
)
if TYPE_CHECKING:
+11 -7
View File
@@ -62,7 +62,9 @@ class IdentifierSelector:
numeric digits before the first dash.
"""
def __init__(self, *, build_config: str, skip_config: str, requires_python: Optional[SpecifierSet] = None):
def __init__(
self, *, build_config: str, skip_config: str, requires_python: Optional[SpecifierSet] = None
):
self.build_patterns = build_config.split()
self.skip_patterns = skip_config.split()
self.requires_python = requires_python
@@ -77,7 +79,9 @@ class IdentifierSelector:
if not self.requires_python.contains(version):
return False
build_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in self.build_patterns)
build_patterns = itertools.chain.from_iterable(
bracex.expand(p) for p in self.build_patterns
)
skip_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in self.skip_patterns)
build: bool = any(fnmatch.fnmatch(build_id, pat) for pat in build_patterns)
@@ -153,9 +157,7 @@ class DependencyConstraints:
@staticmethod
def with_defaults() -> 'DependencyConstraints':
return DependencyConstraints(
base_file_path=resources_dir / 'constraints.txt'
)
return DependencyConstraints(base_file_path=resources_dir / 'constraints.txt')
def get_for_python_version(self, version: str) -> Path:
version_parts = version.split('.')
@@ -195,7 +197,8 @@ class BuildOptions(NamedTuple):
class NonPlatformWheelError(Exception):
def __init__(self) -> None:
message = textwrap.dedent('''
message = textwrap.dedent(
'''
cibuildwheel: Build failed because a pure Python wheel was generated.
If you intend to build a pure-Python wheel, you don't need cibuildwheel - use
@@ -203,7 +206,8 @@ class NonPlatformWheelError(Exception):
If you expected a platform wheel, check your project configuration, or run
cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.
''')
'''
)
super().__init__(message)
+105 -47
View File
@@ -27,8 +27,9 @@ from .util import (
IS_RUNNING_ON_AZURE = Path('C:\\hostedtoolcache').exists()
def call(args: Sequence[PathOrStr], env: Optional[Dict[str, str]] = None,
cwd: Optional[str] = None) -> None:
def call(
args: Sequence[PathOrStr], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None
) -> None:
print('+ ' + ' '.join(str(a) for a in args))
# we use shell=True here, even though we don't need a shell due to a bug
# https://bugs.python.org/issue8557
@@ -46,9 +47,12 @@ def get_nuget_args(version: str, arch: str) -> List[str]:
python_name += 'x86'
return [
python_name,
'-Version', version,
'-FallbackSource', 'https://api.nuget.org/v3/index.json',
'-OutputDirectory', 'C:\\cibw\\python',
'-Version',
version,
'-FallbackSource',
'https://api.nuget.org/v3/index.json',
'-OutputDirectory',
'C:\\cibw\\python',
]
@@ -60,8 +64,8 @@ class PythonConfiguration(NamedTuple):
def get_python_configurations(
build_selector: BuildSelector,
architectures: Set[Architecture],
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> List[PythonConfiguration]:
full_python_configs = read_python_configs('windows')
@@ -76,11 +80,14 @@ def get_python_configurations(
# Only supported with custom compiler, since MS removed the 2008 compiler download
custom_compiler = os.environ.get('DISTUTILS_USE_SDK') and os.environ.get('MSSdk')
if not custom_compiler:
python_configurations = [c for c in python_configurations if not c.version.startswith('2.7')]
python_configurations = [
c for c in python_configurations if not c.version.startswith('2.7')
]
# skip builds as required
python_configurations = [
c for c in python_configurations
c
for c in python_configurations
if build_selector(c.identifier) and map_arch[c.arch] in architectures
]
@@ -105,7 +112,7 @@ def install_pypy(version: str, arch: str, url: str) -> Path:
zip_filename = url.rsplit('/', 1)[-1]
extension = ".zip"
assert zip_filename.endswith(extension)
installation_path = Path('C:\\cibw') / zip_filename[:-len(extension)]
installation_path = Path('C:\\cibw') / zip_filename[: -len(extension)]
if not installation_path.exists():
pypy_zip = Path('C:\\cibw') / zip_filename
download(url, pypy_zip)
@@ -116,7 +123,11 @@ def install_pypy(version: str, arch: str, url: str) -> Path:
return installation_path
def setup_python(python_configuration: PythonConfiguration, dependency_constraint_flags: Sequence[PathOrStr], environment: ParsedEnvironment) -> Dict[str, str]:
def setup_python(
python_configuration: PythonConfiguration,
dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment,
) -> Dict[str, str]:
nuget = Path('C:\\cibw\\nuget.exe')
if not nuget.exists():
log.step('Downloading nuget...')
@@ -126,10 +137,14 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain
log.step(f'Installing Python {implementation_id}...')
if implementation_id.startswith('cp'):
installation_path = install_cpython(python_configuration.version, python_configuration.arch, nuget)
installation_path = install_cpython(
python_configuration.version, python_configuration.arch, nuget
)
elif implementation_id.startswith('pp'):
assert python_configuration.url is not None
installation_path = install_pypy(python_configuration.version, python_configuration.arch, python_configuration.url)
installation_path = install_pypy(
python_configuration.version, python_configuration.arch, python_configuration.url
)
else:
raise ValueError("Unknown Python implementation")
@@ -141,11 +156,9 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain
env = os.environ.copy()
env['PYTHON_VERSION'] = python_configuration.version
env['PYTHON_ARCH'] = python_configuration.arch
env['PATH'] = os.pathsep.join([
str(installation_path),
str(installation_path / 'Scripts'),
env['PATH']
])
env['PATH'] = os.pathsep.join(
[str(installation_path), str(installation_path / 'Scripts'), env['PATH']]
)
env['PIP_DISABLE_PIP_VERSION_CHECK'] = '1'
# update env with results from CIBW_ENVIRONMENT
@@ -155,25 +168,53 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain
call(['where', 'python'], env=env)
call(['python', '--version'], env=env)
call(['python', '-c', '"import struct; print(struct.calcsize(\'P\') * 8)"'], env=env)
where_python = subprocess.run(['where', 'python'], env=env, universal_newlines=True, check=True, stdout=subprocess.PIPE).stdout.splitlines()[0].strip()
where_python = (
subprocess.run(
['where', 'python'],
env=env,
universal_newlines=True,
check=True,
stdout=subprocess.PIPE,
)
.stdout.splitlines()[0]
.strip()
)
if where_python != str(installation_path / 'python.exe'):
print("cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", file=sys.stderr)
print(
"cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.",
file=sys.stderr,
)
sys.exit(1)
# make sure pip is installed
if not (installation_path / 'Scripts' / 'pip.exe').exists():
call(['python', get_pip_script, *dependency_constraint_flags], env=env, cwd="C:\\cibw")
assert (installation_path / 'Scripts' / 'pip.exe').exists()
where_pip = subprocess.run(['where', 'pip'], env=env, universal_newlines=True, check=True, stdout=subprocess.PIPE).stdout.splitlines()[0].strip()
where_pip = (
subprocess.run(
['where', 'pip'], env=env, universal_newlines=True, check=True, stdout=subprocess.PIPE
)
.stdout.splitlines()[0]
.strip()
)
if where_pip.strip() != str(installation_path / 'Scripts' / 'pip.exe'):
print("cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.", file=sys.stderr)
print(
"cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.",
file=sys.stderr,
)
sys.exit(1)
log.step('Installing build tools...')
call(['python', '-m', 'pip', 'install', '--upgrade', 'pip', *dependency_constraint_flags], env=env)
call(
['python', '-m', 'pip', 'install', '--upgrade', 'pip', *dependency_constraint_flags],
env=env,
)
call(['pip', '--version'], env=env)
call(['pip', 'install', '--upgrade', 'setuptools', 'wheel', *dependency_constraint_flags], env=env)
call(
['pip', 'install', '--upgrade', 'setuptools', 'wheel', *dependency_constraint_flags],
env=env,
)
return env
@@ -194,11 +235,7 @@ def pep_518_cp35_workaround(package_dir: Path, env: Dict[str, str]) -> None:
if pyproject_path.exists():
data = toml.load(pyproject_path)
requirements = (
data['build-system'].get('requires', [])
if 'build-system' in data
else []
)
requirements = data['build-system'].get('requires', []) if 'build-system' in data else []
if requirements:
log.step('Performing PEP518 workaround...')
@@ -219,10 +256,14 @@ def build(options: BuildOptions) -> None:
if options.before_all:
log.step('Running before_all...')
env = options.environment.as_dictionary(prev_environment=os.environ)
before_all_prepared = prepare_command(options.before_all, project='.', package=options.package_dir)
before_all_prepared = prepare_command(
options.before_all, project='.', package=options.package_dir
)
shell(before_all_prepared, env=env)
python_configurations = get_python_configurations(options.build_selector, options.architectures)
python_configurations = get_python_configurations(
options.build_selector, options.architectures
)
for config in python_configurations:
log.build_start(config.identifier)
@@ -230,7 +271,8 @@ def build(options: BuildOptions) -> None:
dependency_constraint_flags: Sequence[PathOrStr] = []
if options.dependency_constraints:
dependency_constraint_flags = [
'-c', options.dependency_constraints.get_for_python_version(config.version)
'-c',
options.dependency_constraints.get_for_python_version(config.version),
]
# install Python
@@ -239,7 +281,9 @@ def build(options: BuildOptions) -> None:
# run the before_build command
if options.before_build:
log.step('Running before_build...')
before_build_prepared = prepare_command(options.before_build, project='.', package=options.package_dir)
before_build_prepared = prepare_command(
options.before_build, project='.', package=options.package_dir
)
shell(before_build_prepared, env=env)
# activate the PEP 518 patch if on Windows Python 3.5
@@ -253,13 +297,18 @@ def build(options: BuildOptions) -> None:
built_wheel_dir.mkdir(parents=True)
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/joerick/cibuildwheel/pull/369
call([
'pip', 'wheel',
options.package_dir.resolve(),
'-w', built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity)
], env=env)
call(
[
'pip',
'wheel',
options.package_dir.resolve(),
'-w',
built_wheel_dir,
'--no-deps',
*get_build_verbosity_extra_flags(options.build_verbosity),
],
env=env,
)
built_wheel = next(built_wheel_dir.glob('*.whl'))
@@ -273,7 +322,9 @@ def build(options: BuildOptions) -> None:
if options.repair_command:
log.step('Repairing wheel...')
repair_command_prepared = prepare_command(options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir)
repair_command_prepared = prepare_command(
options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
)
shell(repair_command_prepared, env=env)
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
@@ -292,10 +343,12 @@ def build(options: BuildOptions) -> None:
call(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
virtualenv_env = env.copy()
virtualenv_env['PATH'] = os.pathsep.join([
str(venv_dir / 'Scripts'),
virtualenv_env['PATH'],
])
virtualenv_env['PATH'] = os.pathsep.join(
[
str(venv_dir / 'Scripts'),
virtualenv_env['PATH'],
]
)
# check that we are using the Python from the virtual environment
call(['where', 'python'], env=virtualenv_env)
@@ -309,7 +362,10 @@ def build(options: BuildOptions) -> None:
shell(before_test_prepared, env=virtualenv_env)
# install the wheel
call(['pip', 'install', str(repaired_wheel) + options.test_extras], env=virtualenv_env)
call(
['pip', 'install', str(repaired_wheel) + options.test_extras],
env=virtualenv_env,
)
# test the wheel
if options.test_requires:
@@ -332,5 +388,7 @@ def build(options: BuildOptions) -> None:
shutil.move(str(repaired_wheel), options.output_dir)
log.build_end()
except subprocess.CalledProcessError as error:
log.step_end_with_error(f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}')
log.step_end_with_error(
f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}'
)
sys.exit(1)
+9 -4
View File
@@ -8,12 +8,14 @@ from cibuildwheel.logger import Logger
from . import test_projects, utils
basic_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent('''
setup_py_add=textwrap.dedent(
'''
import os
if os.environ.get("CIBUILDWHEEL", "0") != "1":
raise Exception("CIBUILDWHEEL environment variable is not set to 1")
''')
'''
)
)
@@ -55,8 +57,11 @@ def test_build_identifiers(tmp_path):
# can be multiple wheels for each wheel, though, so we need to limit
# the expected wheels
if platform.machine() in ['x86_64', 'i686']:
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-manylinux' not in w or '-manylinux1' in w]
expected_wheels = [
w
for w in utils.expected_wheels('spam', '0.1.0')
if '-manylinux' not in w or '-manylinux1' in w
]
else:
expected_wheels = utils.expected_wheels('spam', '0.1.0')
build_identifiers = utils.cibuildwheel_get_build_identifiers(project_dir)
+28 -17
View File
@@ -6,7 +6,8 @@ import pytest
from . import test_projects, utils
project_with_before_build_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
setup_py_add=textwrap.dedent(
r'''
# 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:
@@ -14,7 +15,8 @@ project_with_before_build_asserts = test_projects.new_c_project(
print("## stored text: " + stored_text)
assert stored_text == "sample text 123"
''')
'''
)
)
@@ -27,13 +29,16 @@ def test(tmp_path):
# build the wheels
before_all_command = '''python -c "import os;open('{project}/text_info.txt', 'w').write('sample text '+os.environ.get('TEST_VAL', ''))"'''
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': before_all_command,
'CIBW_BEFORE_ALL_LINUX': f'{before_all_command} && python -c "import sys; assert sys.version_info >= (3, 6)"',
'CIBW_ENVIRONMENT': "TEST_VAL='123'"
})
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': before_all_command,
'CIBW_BEFORE_ALL_LINUX': f'{before_all_command} && python -c "import sys; assert sys.version_info >= (3, 6)"',
'CIBW_ENVIRONMENT': "TEST_VAL='123'",
},
)
# also check that we got the right wheels
(project_dir / 'text_info.txt').unlink()
@@ -46,20 +51,26 @@ def test_failing_command(tmp_path):
test_projects.new_c_project().generate(project_dir)
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BEFORE_ALL': 'false',
'CIBW_BEFORE_ALL_WINDOWS': 'exit /b 1',
})
utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BEFORE_ALL': 'false',
'CIBW_BEFORE_ALL_WINDOWS': 'exit /b 1',
},
)
def test_cwd(tmp_path):
project_dir = tmp_path / 'project'
test_projects.new_c_project().generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BEFORE_ALL': f'''python -c "import os; assert os.getcwd() == {str(project_dir)!r}"''',
'CIBW_BEFORE_ALL_LINUX': '''python -c "import os; assert os.getcwd() == '/project'"''',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BEFORE_ALL': f'''python -c "import os; assert os.getcwd() == {str(project_dir)!r}"''',
'CIBW_BEFORE_ALL_LINUX': '''python -c "import os; assert os.getcwd() == '/project'"''',
},
)
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
+31 -18
View File
@@ -6,7 +6,8 @@ import pytest
from . import test_projects, utils
project_with_before_build_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
setup_py_add=textwrap.dedent(
r'''
import os
# assert that the Python version as written to pythonversion.txt in the CIBW_BEFORE_BUILD step
@@ -26,7 +27,8 @@ project_with_before_build_asserts = test_projects.new_c_project(
print('sys.executable', sys.executable)
# windows/mac are case insensitive
assert os.path.realpath(stored_executable).lower() == os.path.realpath(sys.executable).lower()
''')
'''
)
)
@@ -34,16 +36,21 @@ def test(tmp_path):
project_dir = tmp_path / 'project'
project_with_before_build_asserts.generate(project_dir)
before_build = ('''python -c "import sys; open('{output_dir}pythonversion.txt', 'w').write(sys.version)" && '''
'''python -c "import sys; open('{output_dir}pythonexecutable.txt', 'w').write(sys.executable)"''')
before_build = (
'''python -c "import sys; open('{output_dir}pythonversion.txt', 'w').write(sys.version)" && '''
'''python -c "import sys; open('{output_dir}pythonexecutable.txt', 'w').write(sys.executable)"'''
)
# 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_BUILD': before_build.format(output_dir='/tmp/'),
'CIBW_BEFORE_BUILD_WINDOWS': before_build.format(output_dir=r'c:\\'),
})
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_BUILD': before_build.format(output_dir='/tmp/'),
'CIBW_BEFORE_BUILD_WINDOWS': before_build.format(output_dir=r'c:\\'),
},
)
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
@@ -55,20 +62,26 @@ def test_failing_command(tmp_path):
test_projects.new_c_project().generate(project_dir)
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BEFORE_BUILD': 'false',
'CIBW_BEFORE_BUILD_WINDOWS': 'exit /b 1',
})
utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BEFORE_BUILD': 'false',
'CIBW_BEFORE_BUILD_WINDOWS': 'exit /b 1',
},
)
def test_cwd(tmp_path):
project_dir = tmp_path / 'project'
test_projects.new_c_project().generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BEFORE_BUILD': f'''python -c "import os; assert os.getcwd() == {str(project_dir)!r}"''',
'CIBW_BEFORE_BUILD_LINUX': '''python -c "import os; assert os.getcwd() == '/project'"''',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BEFORE_BUILD': f'''python -c "import os; assert os.getcwd() == {str(project_dir)!r}"''',
'CIBW_BEFORE_BUILD_LINUX': '''python -c "import os; assert os.getcwd() == '/project'"''',
},
)
expected_wheels = utils.expected_wheels('spam', '0.1.0')
assert set(actual_wheels) == set(expected_wheels)
+17 -12
View File
@@ -1,7 +1,9 @@
from . import test_projects, utils
before_test_project = test_projects.new_c_project()
before_test_project.files['test/spam_test.py'] = r'''
before_test_project.files[
'test/spam_test.py'
] = r'''
import sys
import os
from unittest import TestCase
@@ -40,17 +42,20 @@ def test(tmp_path):
test_projects.new_c_project().generate(test_project_dir)
# 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_TEST': '''python -c "import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('/tmp/pythonprefix.txt', 'w').write(sys.prefix)" && python -m pip install {project}/dependency''',
'CIBW_BEFORE_TEST_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonprefix.txt', 'w').write(sys.prefix)" && python -m pip install {project}/dependency''',
'CIBW_TEST_REQUIRES': 'nose',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'nosetests {project}/test',
})
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_TEST': '''python -c "import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('/tmp/pythonprefix.txt', 'w').write(sys.prefix)" && python -m pip install {project}/dependency''',
'CIBW_BEFORE_TEST_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonprefix.txt', 'w').write(sys.prefix)" && python -m pip install {project}/dependency''',
'CIBW_TEST_REQUIRES': 'nose',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'nosetests {project}/test',
},
)
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
+14 -8
View File
@@ -3,13 +3,15 @@ import textwrap
from . import test_projects, utils
project_with_skip_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
setup_py_add=textwrap.dedent(
r'''
# explode if run on Python 2.7 or Python 3.7 (these should be skipped)
if sys.version_info[0:2] == (2, 7):
raise Exception("Python 2.7 should not be built")
if sys.version_info[0:2] == (3, 7):
raise Exception("Python 3.7 should be skipped")
''')
'''
)
)
@@ -18,12 +20,16 @@ def test(tmp_path):
project_with_skip_asserts.generate(project_dir)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BUILD': 'cp3?-*',
'CIBW_SKIP': 'cp37-*',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BUILD': 'cp3?-*',
'CIBW_SKIP': 'cp37-*',
},
)
# check that we got the right wheels. There should be no 2.7 or 3.7.
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if ('-cp3' in w) and ('-cp37' not in w)]
expected_wheels = [
w for w in utils.expected_wheels('spam', '0.1.0') if ('-cp3' in w) and ('-cp37' not in w)
]
assert set(actual_wheels) == set(expected_wheels)
+23 -12
View File
@@ -91,8 +91,11 @@ def test_cpp11(tmp_path):
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32'}
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if 'cp27-cp27m-win' not in w and 'pp27-pypy_73-win32' not in w]
expected_wheels = [
w
for w in utils.expected_wheels('spam', '0.1.0')
if 'cp27-cp27m-win' not in w and 'pp27-pypy_73-win32' not in w
]
assert set(actual_wheels) == set(expected_wheels)
@@ -115,9 +118,11 @@ def test_cpp14(tmp_path):
add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32'}
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if 'cp27-cp27m-win' not in w
and 'pp27-pypy_73-win32' not in w]
expected_wheels = [
w
for w in utils.expected_wheels('spam', '0.1.0')
if 'cp27-cp27m-win' not in w and 'pp27-pypy_73-win32' not in w
]
assert set(actual_wheels) == set(expected_wheels)
@@ -129,7 +134,9 @@ cpp17_project = cpp_test_project.copy()
cpp17_project.template_context['extra_compile_args'] = (
['/std:c++17', '/wd5033'] if utils.platform == 'windows' else ['-std=c++17', '-Wno-register']
)
cpp17_project.template_context['spam_cpp_top_level_add'] = r'''
cpp17_project.template_context[
'spam_cpp_top_level_add'
] = r'''
#include <utility>
auto a = std::pair(5.0, false);
'''
@@ -152,9 +159,11 @@ def test_cpp17(tmp_path):
add_env['MACOSX_DEPLOYMENT_TARGET'] = '10.13'
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', macosx_deployment_target='10.13')
if 'cp27-cp27m-win' not in w
and '-pp' not in w]
expected_wheels = [
w
for w in utils.expected_wheels('spam', '0.1.0', macosx_deployment_target='10.13')
if 'cp27-cp27m-win' not in w and '-pp' not in w
]
assert set(actual_wheels) == set(expected_wheels)
@@ -204,8 +213,10 @@ def test_cpp17_py27_modern_msvc_workaround(tmp_path):
add_env_x64['CIBW_BUILD'] = 'cp27-win_amd64'
actual_wheels += utils.cibuildwheel_run(project_dir, add_env=add_env_x64)
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', exclude_27=False)
if 'cp27-cp27m-win' in w
or 'pp27-pypy_73-win32' in w]
expected_wheels = [
w
for w in utils.expected_wheels('spam', '0.1.0', exclude_27=False)
if 'cp27-cp27m-win' in w or 'pp27-pypy_73-win32' in w
]
assert set(actual_wheels) == set(expected_wheels)
+51 -38
View File
@@ -8,7 +8,8 @@ import cibuildwheel.util
from . import test_projects, utils
project_with_expected_version_checks = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
setup_py_add=textwrap.dedent(
r'''
import subprocess
import os
@@ -30,7 +31,8 @@ project_with_expected_version_checks = test_projects.new_c_project(
assert '{}=={}'.format(package_name, expected_version) in versions, (
'error: {} version should equal {}'.format(package_name, expected_version)
)
''')
'''
)
)
@@ -41,8 +43,7 @@ def get_versions_from_constraint_file(constraint_file):
constraint_file_text = constraint_file.read_text(encoding='utf8')
return {
package: version
for package, version in re.findall(VERSION_REGEX, constraint_file_text)
package: version for package, version in re.findall(VERSION_REGEX, constraint_file_text)
}
@@ -54,9 +55,7 @@ def test_pinned_versions(tmp_path, python_version):
if utils.platform == 'windows' and python_version == '2.7':
pytest.skip('Windows requires a workaround')
is_macos_11_or_later = (
utils.platform == 'macos' and utils.get_macos_version() >= (10, 16)
)
is_macos_11_or_later = utils.platform == 'macos' and utils.get_macos_version() >= (10, 16)
if is_macos_11_or_later and python_version == '3.5':
pytest.skip('CPython 3.5 doesn\'t work on macOS Big Sur+')
@@ -89,29 +88,34 @@ def test_pinned_versions(tmp_path, python_version):
env_name = f'EXPECTED_{package.upper()}_VERSION'
build_environment[env_name] = constraint_versions[package]
cibw_environment_option = ' '.join(
f'{k}={v}' for k, v in build_environment.items()
)
cibw_environment_option = ' '.join(f'{k}={v}' for k, v in build_environment.items())
# build and test the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BUILD': build_pattern,
'CIBW_ENVIRONMENT': cibw_environment_option,
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BUILD': build_pattern,
'CIBW_ENVIRONMENT': cibw_environment_option,
},
)
# also check that we got the right wheels
if python_version == '2.7':
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-cp27' in w or '-pp27' in w]
expected_wheels = [
w for w in utils.expected_wheels('spam', '0.1.0') if '-cp27' in w or '-pp27' in w
]
elif python_version == '3.5':
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-cp35' in w or '-pp35' in w]
expected_wheels = [
w for w in utils.expected_wheels('spam', '0.1.0') if '-cp35' in w or '-pp35' in w
]
elif python_version == '3.6':
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-cp36' in w or '-pp36' in w]
expected_wheels = [
w for w in utils.expected_wheels('spam', '0.1.0') if '-cp36' in w or '-pp36' in w
]
elif python_version == '3.8':
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-cp38' in w or '-pp38' in w]
expected_wheels = [
w for w in utils.expected_wheels('spam', '0.1.0') if '-cp38' in w or '-pp38' in w
]
else:
raise ValueError('unhandled python version')
@@ -137,14 +141,18 @@ def test_dependency_constraints_file(tmp_path, python_version):
}
constraints_file = tmp_path / 'constraints.txt'
constraints_file.write_text(textwrap.dedent(
'''
constraints_file.write_text(
textwrap.dedent(
'''
pip=={pip}
setuptools=={setuptools}
wheel=={wheel}
virtualenv=={virtualenv}
'''.format(**tool_versions)
))
'''.format(
**tool_versions
)
)
)
build_environment = {}
@@ -152,23 +160,28 @@ def test_dependency_constraints_file(tmp_path, python_version):
env_name = f'EXPECTED_{package_name.upper()}_VERSION'
build_environment[env_name] = version
cibw_environment_option = ' '.join(
f'{k}={v}' for k, v in build_environment.items()
)
cibw_environment_option = ' '.join(f'{k}={v}' for k, v in build_environment.items())
# build and test the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BUILD': '[cp]p27-*' if python_version == '2.7' else '[cp]p3?-*',
'CIBW_ENVIRONMENT': cibw_environment_option,
'CIBW_DEPENDENCY_VERSIONS': str(constraints_file),
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BUILD': '[cp]p27-*' if python_version == '2.7' else '[cp]p3?-*',
'CIBW_ENVIRONMENT': cibw_environment_option,
'CIBW_DEPENDENCY_VERSIONS': str(constraints_file),
},
)
# also check that we got the right wheels
if python_version == '2.7':
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-cp27' in w or '-pp27' in w]
expected_wheels = [
w for w in utils.expected_wheels('spam', '0.1.0') if '-cp27' in w or '-pp27' in w
]
else:
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-cp27' not in w and '-pp27' not in w]
expected_wheels = [
w
for w in utils.expected_wheels('spam', '0.1.0')
if '-cp27' not in w and '-pp27' not in w
]
assert set(actual_wheels) == set(expected_wheels)
+20 -10
View File
@@ -6,7 +6,8 @@ import pytest
from . import test_projects, utils
dockcross_only_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
setup_py_add=textwrap.dedent(
r'''
import os
# check that we're running in the correct docker image as specified in the
@@ -15,7 +16,8 @@ dockcross_only_project = test_projects.new_c_project(
raise Exception(
"/dockcross directory not found. Is this test running in the correct docker image?"
)
''')
'''
)
)
@@ -23,18 +25,26 @@ def test(tmp_path):
if utils.platform != 'linux':
pytest.skip('the test is only relevant to the linux build')
if platform.machine() not in ['x86_64', 'i686']:
pytest.skip('this test is currently only possible on x86_64/i686 due to availability of alternative images')
pytest.skip(
'this test is currently only possible on x86_64/i686 due to availability of alternative images'
)
project_dir = tmp_path / 'project'
dockcross_only_project.generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_MANYLINUX_X86_64_IMAGE': 'dockcross/manylinux2010-x64',
'CIBW_MANYLINUX_I686_IMAGE': 'dockcross/manylinux2010-x86',
'CIBW_SKIP': 'pp* cp27-* cp39-*',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_MANYLINUX_X86_64_IMAGE': 'dockcross/manylinux2010-x64',
'CIBW_MANYLINUX_I686_IMAGE': 'dockcross/manylinux2010-x86',
'CIBW_SKIP': 'pp* cp27-* cp39-*',
},
)
# also check that we got the right wheels built
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if '-pp' not in w and '-cp39-' not in w and '-cp27-' not in w]
expected_wheels = [
w
for w in utils.expected_wheels('spam', '0.1.0')
if '-pp' not in w and '-cp39-' not in w and '-cp27-' not in w
]
assert set(actual_wheels) == set(expected_wheels)
+17 -9
View File
@@ -6,7 +6,9 @@ from . import test_projects, utils
project_with_a_test = test_projects.new_c_project()
project_with_a_test.files['test/spam_test.py'] = r'''
project_with_a_test.files[
'test/spam_test.py'
] = r'''
import spam
def test_spam():
@@ -21,11 +23,14 @@ def test(tmp_path):
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',
})
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 = (
@@ -45,9 +50,12 @@ def test_setting_arch_on_other_platforms(tmp_path, capfd):
# build and test the wheels
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(project_dir, add_env={
'CIBW_ARCHS': 'aarch64',
})
utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_ARCHS': 'aarch64',
},
)
captured = capfd.readouterr()
assert "Invalid archs option" in captured.err
+27 -14
View File
@@ -7,7 +7,8 @@ import pytest
from . import test_projects, utils
project_with_environment_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
setup_py_add=textwrap.dedent(
r'''
import os
# explode if environment isn't correct, as set in CIBW_ENVIRONMENT
@@ -26,7 +27,8 @@ project_with_environment_asserts = test_projects.new_c_project(
raise Exception('PATH should contain "/opt/cibw_test_path". It was "%s"' % PATH)
if "$PATH" in PATH:
raise Exception('$PATH should be expanded in PATH. It was "%s"' % PATH)
''')
'''
)
)
@@ -37,10 +39,13 @@ def test(tmp_path):
# write some information into the CIBW_ENVIRONMENT, for expansion and
# insertion into the environment by cibuildwheel. This is checked
# in setup.py
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_ENVIRONMENT': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH=$PATH:/opt/cibw_test_path''',
'CIBW_ENVIRONMENT_WINDOWS': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH="$PATH;/opt/cibw_test_path"''',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_ENVIRONMENT': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH=$PATH:/opt/cibw_test_path''',
'CIBW_ENVIRONMENT_WINDOWS': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH="$PATH;/opt/cibw_test_path"''',
},
)
# also check that we got the right wheels built
expected_wheels = utils.expected_wheels('spam', '0.1.0')
@@ -58,19 +63,27 @@ def test_overridden_path(tmp_path, capfd):
# mess up PATH, somehow
with pytest.raises(subprocess.CalledProcessError):
if utils.platform == 'linux':
utils.cibuildwheel_run(project_dir, output_dir=output_dir, add_env={
'CIBW_BEFORE_ALL': 'mkdir new_path && touch new_path/python && chmod +x new_path/python',
'CIBW_ENVIRONMENT': '''PATH="$(pwd)/new_path:$PATH"''',
})
utils.cibuildwheel_run(
project_dir,
output_dir=output_dir,
add_env={
'CIBW_BEFORE_ALL': 'mkdir new_path && touch new_path/python && chmod +x new_path/python',
'CIBW_ENVIRONMENT': '''PATH="$(pwd)/new_path:$PATH"''',
},
)
else:
new_path = tmp_path / 'another_bin'
new_path.mkdir()
(new_path / 'python').touch(mode=0o777)
utils.cibuildwheel_run(project_dir, output_dir=output_dir, add_env={
'NEW_PATH': str(new_path),
'CIBW_ENVIRONMENT': f'''PATH="$NEW_PATH{os.pathsep}$PATH"''',
})
utils.cibuildwheel_run(
project_dir,
output_dir=output_dir,
add_env={
'NEW_PATH': str(new_path),
'CIBW_ENVIRONMENT': f'''PATH="$NEW_PATH{os.pathsep}$PATH"''',
},
)
assert len(os.listdir(output_dir)) == 0
captured = capfd.readouterr()
+32 -18
View File
@@ -10,7 +10,7 @@ basic_project = test_projects.new_c_project()
ALL_MACOS_WHEELS = {
*utils.expected_wheels('spam', '0.1.0', machine_arch='x86_64'),
*utils.expected_wheels('spam', '0.1.0', machine_arch='arm64')
*utils.expected_wheels('spam', '0.1.0', machine_arch='arm64'),
}
@@ -37,10 +37,13 @@ def test_cross_compiled_build(tmp_path):
project_dir = tmp_path / 'project'
basic_project.generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BUILD': 'cp39-*',
'CIBW_ARCHS': 'x86_64, universal2, arm64',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BUILD': 'cp39-*',
'CIBW_ARCHS': 'x86_64, universal2, arm64',
},
)
expected_wheels = [w for w in ALL_MACOS_WHEELS if 'cp39' in w]
assert set(actual_wheels) == set(expected_wheels)
@@ -56,11 +59,14 @@ def test_cross_compiled_test(tmp_path, capfd, build_universal2):
project_dir = tmp_path / 'project'
basic_project.generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BUILD': 'cp39-*',
'CIBW_TEST_COMMAND': '''python -c "import platform; print('running tests on ' + platform.machine())"''',
'CIBW_ARCHS': 'universal2' if build_universal2 else 'x86_64 arm64',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BUILD': 'cp39-*',
'CIBW_TEST_COMMAND': '''python -c "import platform; print('running tests on ' + platform.machine())"''',
'CIBW_ARCHS': 'universal2' if build_universal2 else 'x86_64 arm64',
},
)
captured = capfd.readouterr()
@@ -69,9 +75,14 @@ def test_cross_compiled_test(tmp_path, capfd, build_universal2):
assert 'running tests on x86_64' in captured.out
assert 'running tests on arm64' not in captured.out
if build_universal2:
assert 'While universal2 wheels can be built on x86_64, the arm64 part of them cannot currently be tested' in captured.err
assert (
'While universal2 wheels can be built on x86_64, the arm64 part of them cannot currently be tested'
in captured.err
)
else:
assert 'While arm64 wheels can be built on x86_64, they cannot be tested' in captured.err
assert (
'While arm64 wheels can be built on x86_64, they cannot be tested' in captured.err
)
elif platform.machine() == 'arm64':
# ensure that tests were run on both x86_64 and arm64
assert 'running tests on x86_64' in captured.out
@@ -97,12 +108,15 @@ def test_universal2_testing(tmp_path, capfd, skip_arm64_test):
project_dir = tmp_path / 'project'
basic_project.generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_BUILD': 'cp39-*',
'CIBW_TEST_COMMAND': '''python -c "import platform; print('running tests on ' + platform.machine())"''',
'CIBW_ARCHS': 'universal2',
'CIBW_TEST_SKIP': '*_universal2:arm64' if skip_arm64_test else '',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_BUILD': 'cp39-*',
'CIBW_TEST_COMMAND': '''python -c "import platform; print('running tests on ' + platform.machine())"''',
'CIBW_ARCHS': 'universal2',
'CIBW_TEST_SKIP': '*_universal2:arm64' if skip_arm64_test else '',
},
)
captured = capfd.readouterr()
+14 -6
View File
@@ -7,7 +7,8 @@ from . import test_projects, utils
# TODO: specify these at runtime according to manylinux_image
project_with_manylinux_symbols = test_projects.new_c_project(
spam_c_top_level_add=textwrap.dedent(r'''
spam_c_top_level_add=textwrap.dedent(
r'''
#include <malloc.h>
#include <stdlib.h>
#include <stdint.h>
@@ -20,8 +21,10 @@ project_with_manylinux_symbols = test_projects.new_c_project(
#if !__GLIBC_PREREQ(2, 5) /* manylinux1 is glibc 2.5 */
#error "Must run on a glibc >= 2.5 linux environment"
#endif
'''),
spam_c_function_add=textwrap.dedent(r'''
'''
),
spam_c_function_add=textwrap.dedent(
r'''
#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 24)
// nextupf is only available in manylinux_2_24+
sts = (int)nextupf(0.0F);
@@ -32,11 +35,14 @@ project_with_manylinux_symbols = test_projects.new_c_project(
// malloc_info is only available on manylinux2010+
sts = malloc_info(0, stdout);
#endif
'''),
'''
),
)
@pytest.mark.parametrize('manylinux_image', ['manylinux1', 'manylinux2010', 'manylinux2014', 'manylinux_2_24'])
@pytest.mark.parametrize(
'manylinux_image', ['manylinux1', 'manylinux2010', 'manylinux2014', 'manylinux_2_24']
)
def test(manylinux_image, tmp_path):
if utils.platform != 'linux':
pytest.skip('the docker test is only relevant to the linux build')
@@ -68,7 +74,9 @@ def test(manylinux_image, tmp_path):
add_env['CIBW_SKIP'] = 'cp27* pp*'
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', manylinux_versions=[manylinux_image])]
expected_wheels = [
w for w in utils.expected_wheels('spam', '0.1.0', manylinux_versions=[manylinux_image])
]
if manylinux_image in {'manylinux2014', 'manylinux_2_24'}:
expected_wheels = [w for w in expected_wheels if '-cp27' not in w]
if manylinux_image in {'manylinux1', 'manylinux2014', 'manylinux_2_24'}:
+18 -8
View File
@@ -8,18 +8,28 @@ from pathlib import Path
def main():
parser = ArgumentParser(
prog="python -m test.test_projects",
description='Generate a test project to check it out'
prog="python -m test.test_projects", description='Generate a test project to check it out'
)
parser.add_argument('--open', action='store_true', help='''
parser.add_argument(
'--open',
action='store_true',
help='''
Open the generated project in a file explorer
''')
parser.add_argument('PROJECT', help='''
''',
)
parser.add_argument(
'PROJECT',
help='''
Python path to a project object. E.g. test.test_0_basic.basic_project
''')
parser.add_argument('OUTPUT', nargs='?', help='''
''',
)
parser.add_argument(
'OUTPUT',
nargs='?',
help='''
Path to output dir. If no dir is passed, a tempdir will be generated.
''')
''',
)
options = parser.parse_args()
module, _, name = options.PROJECT.rpartition('.')
+1
View File
@@ -14,6 +14,7 @@ class TestProject:
Write out to the filesystem using `generate`.
'''
__test__ = False # Have pytest ignore this class on `from .test_projects import TestProject`
files: FilesDict
+24 -14
View File
@@ -88,22 +88,32 @@ version = 0.1.0
'''
def new_c_project(*, spam_c_top_level_add='', spam_c_function_add='', setup_py_add='',
setup_py_setup_args_add='', setup_cfg_add=''):
def new_c_project(
*,
spam_c_top_level_add='',
spam_c_function_add='',
setup_py_add='',
setup_py_setup_args_add='',
setup_cfg_add='',
):
project = TestProject()
project.files.update({
'spam.c': jinja2.Template(SPAM_C_TEMPLATE),
'setup.py': jinja2.Template(SETUP_PY_TEMPLATE),
'setup.cfg': jinja2.Template(SETUP_CFG_TEMPLATE),
})
project.files.update(
{
'spam.c': jinja2.Template(SPAM_C_TEMPLATE),
'setup.py': jinja2.Template(SETUP_PY_TEMPLATE),
'setup.cfg': jinja2.Template(SETUP_CFG_TEMPLATE),
}
)
project.template_context.update({
'spam_c_top_level_add': spam_c_top_level_add,
'spam_c_function_add': spam_c_function_add,
'setup_py_add': setup_py_add,
'setup_py_setup_args_add': setup_py_setup_args_add,
'setup_cfg_add': setup_cfg_add,
})
project.template_context.update(
{
'spam_c_top_level_add': spam_c_top_level_add,
'spam_c_function_add': spam_c_function_add,
'setup_py_add': setup_py_add,
'setup_py_setup_args_add': setup_py_setup_args_add,
'setup_cfg_add': setup_cfg_add,
}
)
return project
+6 -2
View File
@@ -6,7 +6,9 @@ import pytest
from . import utils
pure_python_project = test_projects.TestProject()
pure_python_project.files['setup.py'] = '''
pure_python_project.files[
'setup.py'
] = '''
from setuptools import Extension, setup
setup(
@@ -16,7 +18,9 @@ setup(
)
'''
pure_python_project.files['spam.py'] = '''
pure_python_project.files[
'spam.py'
] = '''
def a_function():
pass
'''
+4 -2
View File
@@ -3,7 +3,8 @@ import textwrap
from . import test_projects, utils
project_with_ssl_tests = test_projects.new_c_project(
setup_py_add=textwrap.dedent(r'''
setup_py_add=textwrap.dedent(
r'''
import ssl
if sys.version_info[0] == 2:
@@ -15,7 +16,8 @@ project_with_ssl_tests = test_projects.new_c_project(
data = urlopen("https://www.nist.gov", context=context)
data = urlopen("https://raw.githubusercontent.com/joerick/cibuildwheel/master/CI.md", context=context)
data = urlopen("https://raw.githubusercontent.com/joerick/cibuildwheel/master/CI.md")
''')
'''
)
)
+20 -11
View File
@@ -12,7 +12,9 @@ subdir_package_project.files['src/spam/spam.c'] = jinja2.Template(SPAM_C_TEMPLAT
subdir_package_project.template_context['spam_c_top_level_add'] = ''
subdir_package_project.template_context['spam_c_function_add'] = ''
subdir_package_project.files['src/spam/setup.py'] = r'''
subdir_package_project.files[
'src/spam/setup.py'
] = r'''
from setuptools import Extension, setup
setup(
@@ -22,11 +24,15 @@ setup(
)
'''
subdir_package_project.files['src/spam/test/run_tests.py'] = r'''
subdir_package_project.files[
'src/spam/test/run_tests.py'
] = r'''
print('run_tests.py executed!')
'''
subdir_package_project.files['bin/before_build.py'] = r'''
subdir_package_project.files[
'bin/before_build.py'
] = r'''
print('before_build.py executed!')
'''
@@ -37,16 +43,19 @@ def test(capfd, tmp_path):
package_dir = Path('src', 'spam')
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, package_dir=package_dir, add_env={
'CIBW_BEFORE_BUILD': 'python {project}/bin/before_build.py',
'CIBW_TEST_COMMAND': 'python {package}/test/run_tests.py',
# this shouldn't depend on the version of python, so build only CPython 3.6
'CIBW_BUILD': 'cp36-*',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
package_dir=package_dir,
add_env={
'CIBW_BEFORE_BUILD': 'python {project}/bin/before_build.py',
'CIBW_TEST_COMMAND': 'python {package}/test/run_tests.py',
# this shouldn't depend on the version of python, so build only CPython 3.6
'CIBW_BUILD': 'cp36-*',
},
)
# check that the expected wheels are produced
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0')
if 'cp36' in w]
expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') if 'cp36' in w]
assert set(actual_wheels) == set(expected_wheels)
captured = capfd.readouterr()
+42 -26
View File
@@ -7,13 +7,17 @@ import pytest
from . import test_projects, utils
project_with_a_test = test_projects.new_c_project(
setup_cfg_add=textwrap.dedent(r'''
setup_cfg_add=textwrap.dedent(
r'''
[options.extras_require]
test = nose
''')
'''
)
)
project_with_a_test.files['test/spam_test.py'] = r'''
project_with_a_test.files[
'test/spam_test.py'
] = r'''
import os
import platform
import sys
@@ -75,13 +79,16 @@ def test(tmp_path):
project_with_a_test.generate(project_dir)
# build and test the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_TEST_REQUIRES': 'nose',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'COLOR 00 || nosetests {project}/test',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_TEST_REQUIRES': 'nose',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'COLOR 00 || nosetests {project}/test',
},
)
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
@@ -93,13 +100,16 @@ def test_extras_require(tmp_path):
project_with_a_test.generate(project_dir)
# build and test the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_TEST_EXTRAS': 'test',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'COLOR 00 || nosetests {project}/test',
})
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
'CIBW_TEST_EXTRAS': 'test',
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
'CIBW_TEST_COMMAND': 'false || nosetests {project}/test',
'CIBW_TEST_COMMAND_WINDOWS': 'COLOR 00 || nosetests {project}/test',
},
)
# also check that we got the right wheels
expected_wheels = utils.expected_wheels('spam', '0.1.0')
@@ -107,7 +117,9 @@ def test_extras_require(tmp_path):
project_with_a_failing_test = test_projects.new_c_project()
project_with_a_failing_test.files['test/spam_test.py'] = r'''
project_with_a_failing_test.files[
'test/spam_test.py'
] = r'''
from unittest import TestCase
class TestSpam(TestCase):
@@ -123,13 +135,17 @@ def test_failing_test(tmp_path):
project_with_a_failing_test.generate(project_dir)
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(project_dir, output_dir=output_dir, add_env={
'CIBW_TEST_REQUIRES': 'nose',
'CIBW_TEST_COMMAND': 'nosetests {project}/test',
# manylinux1 has a version of bash that's been shown to have
# problems with this, so let's check that.
'CIBW_MANYLINUX_I686_IMAGE': 'manylinux1',
'CIBW_MANYLINUX_X86_64_IMAGE': 'manylinux1',
})
utils.cibuildwheel_run(
project_dir,
output_dir=output_dir,
add_env={
'CIBW_TEST_REQUIRES': 'nose',
'CIBW_TEST_COMMAND': 'nosetests {project}/test',
# manylinux1 has a version of bash that's been shown to have
# problems with this, so let's check that.
'CIBW_MANYLINUX_I686_IMAGE': 'manylinux1',
'CIBW_MANYLINUX_X86_64_IMAGE': 'manylinux1',
},
)
assert len(os.listdir(output_dir)) == 0
+3 -1
View File
@@ -9,7 +9,9 @@ so_file_project = TestProject()
so_file_project.files['libnothing.so'] = ''
so_file_project.files['setup.py'] = '''
so_file_project.files[
'setup.py'
] = '''
raise Exception('this build will fail')
'''
+7 -5
View File
@@ -5,11 +5,13 @@ import pytest
from . import test_projects, utils
project_with_unicode = test_projects.new_c_project(
spam_c_function_add=textwrap.dedent(r'''
spam_c_function_add=textwrap.dedent(
r'''
{
Py_XDECREF(PyUnicode_FromStringAndSize("foo", 4));
}
'''),
'''
),
)
@@ -21,9 +23,9 @@ def test(tmp_path):
project_with_unicode.generate(project_dir)
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env={
'CIBW_TEST_COMMAND': 'python -c "import spam"'
})
actual_wheels = utils.cibuildwheel_run(
project_dir, add_env={'CIBW_TEST_COMMAND': 'python -c "import spam"'}
)
# check that the expected wheels are produced
expected_wheels = utils.expected_wheels('spam', '0.1.0')
+20 -5
View File
@@ -78,7 +78,14 @@ def cibuildwheel_run(project_path, package_dir='.', env=None, add_env=None, outp
with TemporaryDirectoryIfNone(output_dir) as _output_dir:
subprocess.run(
[sys.executable, '-m', 'cibuildwheel', '--output-dir', str(_output_dir), str(package_dir)],
[
sys.executable,
'-m',
'cibuildwheel',
'--output-dir',
str(_output_dir),
str(package_dir),
],
env=env,
cwd=project_path,
check=True,
@@ -100,9 +107,15 @@ def _get_arm64_macosx_deployment_target(macosx_deployment_target: str) -> str:
return macosx_deployment_target
def expected_wheels(package_name, package_version, manylinux_versions=None,
macosx_deployment_target='10.9', machine_arch=None, *,
exclude_27=platform == 'windows'):
def expected_wheels(
package_name,
package_version,
manylinux_versions=None,
macosx_deployment_target='10.9',
machine_arch=None,
*,
exclude_27=platform == 'windows',
):
'''
Returns a list of expected wheels from a run of cibuildwheel.
'''
@@ -164,7 +177,9 @@ def expected_wheels(package_name, package_version, manylinux_versions=None,
elif platform == 'macos':
if python_abi_tag == 'cp39-cp39' and machine_arch == 'arm64':
arm64_macosx_deployment_target = _get_arm64_macosx_deployment_target(macosx_deployment_target)
arm64_macosx_deployment_target = _get_arm64_macosx_deployment_target(
macosx_deployment_target
)
platform_tags = [
f'macosx_{macosx_deployment_target.replace(".", "_")}_universal2',
f'macosx_{arm64_macosx_deployment_target.replace(".", "_")}_arm64',
+17 -5
View File
@@ -24,7 +24,9 @@ def test_build():
def test_skip():
build_selector = BuildSelector(build_config="*", skip_config="cp27-* cp3?-manylinux1_i686 cp36-win* *-win32")
build_selector = BuildSelector(
build_config="*", skip_config="cp27-* cp3?-manylinux1_i686 cp36-win* *-win32"
)
assert not build_selector('cp27-manylinux1_x86_64')
assert build_selector('cp36-manylinux1_x86_64')
@@ -44,7 +46,9 @@ def test_skip():
def test_build_and_skip():
build_selector = BuildSelector(build_config="cp36-* cp37-macosx* *-manylinux1*", skip_config="cp27-* cp37-manylinux1_i686")
build_selector = BuildSelector(
build_config="cp36-* cp37-macosx* *-manylinux1*", skip_config="cp27-* cp37-manylinux1_i686"
)
assert not build_selector('cp27-manylinux1_x86_64')
assert build_selector('cp36-manylinux1_x86_64')
@@ -74,7 +78,9 @@ def test_build_braces():
def test_build_limited_python():
build_selector = BuildSelector(build_config="*", skip_config="", requires_python=SpecifierSet(">=3.6"))
build_selector = BuildSelector(
build_config="*", skip_config="", requires_python=SpecifierSet(">=3.6")
)
assert not build_selector('cp27-manylinux1_x86_64')
assert build_selector('cp36-manylinux1_x86_64')
@@ -91,7 +97,11 @@ def test_build_limited_python():
def test_build_limited_python_partial():
build_selector = BuildSelector(build_config="*", skip_config="", requires_python=SpecifierSet(">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"))
build_selector = BuildSelector(
build_config="*",
skip_config="",
requires_python=SpecifierSet(">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"),
)
assert build_selector('cp27-manylinux1_x86_64')
assert not build_selector('cp35-manylinux1_x86_64')
@@ -99,7 +109,9 @@ def test_build_limited_python_partial():
def test_build_limited_python_patch():
build_selector = BuildSelector(build_config="*", skip_config="", requires_python=SpecifierSet(">=2.7.9"))
build_selector = BuildSelector(
build_config="*", skip_config="", requires_python=SpecifierSet(">=2.7.9")
)
assert build_selector('cp27-manylinux1_x86_64')
assert build_selector('cp36-manylinux1_x86_64')
+1 -3
View File
@@ -2,9 +2,7 @@ import pytest
def pytest_addoption(parser):
parser.addoption(
"--run-docker", action="store_true", default=False, help="run docker tests"
)
parser.addoption("--run-docker", action="store_true", default=False, help="run docker tests")
def pytest_configure(config):
+15 -5
View File
@@ -10,8 +10,18 @@ def test_defaults():
resources_dir = project_root / 'cibuildwheel' / 'resources'
assert dependency_constraints.base_file_path.samefile(resources_dir / 'constraints.txt')
assert dependency_constraints.get_for_python_version('3.99').samefile(resources_dir / 'constraints.txt')
assert dependency_constraints.get_for_python_version('3.9').samefile(resources_dir / 'constraints-python39.txt')
assert dependency_constraints.get_for_python_version('3.6').samefile(resources_dir / 'constraints-python36.txt')
assert dependency_constraints.get_for_python_version('3.5').samefile(resources_dir / 'constraints-python35.txt')
assert dependency_constraints.get_for_python_version('2.7').samefile(resources_dir / 'constraints-python27.txt')
assert dependency_constraints.get_for_python_version('3.99').samefile(
resources_dir / 'constraints.txt'
)
assert dependency_constraints.get_for_python_version('3.9').samefile(
resources_dir / 'constraints-python39.txt'
)
assert dependency_constraints.get_for_python_version('3.6').samefile(
resources_dir / 'constraints-python36.txt'
)
assert dependency_constraints.get_for_python_version('3.5').samefile(
resources_dir / 'constraints-python35.txt'
)
assert dependency_constraints.get_for_python_version('2.7').samefile(
resources_dir / 'constraints-python27.txt'
)
+50 -13
View File
@@ -38,7 +38,12 @@ def test_no_lf():
@pytest.mark.docker
def test_environment():
with DockerContainer(DEFAULT_IMAGE) as container:
assert container.call(['sh', '-c', 'echo $TEST_VAR'], env={'TEST_VAR': '1'}, capture_output=True) == '1\n'
assert (
container.call(
['sh', '-c', 'echo $TEST_VAR'], env={'TEST_VAR': '1'}, capture_output=True
)
== '1\n'
)
@pytest.mark.docker
@@ -51,28 +56,43 @@ def test_cwd():
@pytest.mark.docker
def test_container_removed():
with DockerContainer(DEFAULT_IMAGE) as container:
docker_containers_listing = subprocess.run('docker container ls', shell=True, check=True, stdout=subprocess.PIPE, universal_newlines=True).stdout
docker_containers_listing = subprocess.run(
'docker container ls',
shell=True,
check=True,
stdout=subprocess.PIPE,
universal_newlines=True,
).stdout
assert container.name in docker_containers_listing
old_container_name = container.name
docker_containers_listing = subprocess.run('docker container ls', shell=True, check=True, stdout=subprocess.PIPE, universal_newlines=True).stdout
docker_containers_listing = subprocess.run(
'docker container ls',
shell=True,
check=True,
stdout=subprocess.PIPE,
universal_newlines=True,
).stdout
assert old_container_name not in docker_containers_listing
@pytest.mark.docker
def test_large_environment():
# max environment variable size is 128kB
long_env_var_length = 127*1024
long_env_var_length = 127 * 1024
large_environment = {
'a': '0'*long_env_var_length,
'b': '0'*long_env_var_length,
'c': '0'*long_env_var_length,
'd': '0'*long_env_var_length,
'a': '0' * long_env_var_length,
'b': '0' * long_env_var_length,
'c': '0' * long_env_var_length,
'd': '0' * long_env_var_length,
}
with DockerContainer(DEFAULT_IMAGE) as container:
# check the length of d
assert container.call(['sh', '-c', 'echo ${#d}'], env=large_environment, capture_output=True) == f'{long_env_var_length}\n'
assert (
container.call(['sh', '-c', 'echo ${#d}'], env=large_environment, capture_output=True)
== f'{long_env_var_length}\n'
)
@pytest.mark.docker
@@ -81,16 +101,33 @@ def test_binary_output():
# note: the below embedded snippets are in python2
# check that we can pass though arbitrary binary data without erroring
container.call(['/usr/bin/python2', '-c', textwrap.dedent('''
container.call(
[
'/usr/bin/python2',
'-c',
textwrap.dedent(
'''
import sys
sys.stdout.write(''.join(chr(n) for n in range(0, 256)))
''')])
'''
),
]
)
# check that we can capture arbitrary binary data
output = container.call(['/usr/bin/python2', '-c', textwrap.dedent('''
output = container.call(
[
'/usr/bin/python2',
'-c',
textwrap.dedent(
'''
import sys
sys.stdout.write(''.join(chr(n % 256) for n in range(0, 512)))
''')], capture_output=True)
'''
),
],
capture_output=True,
)
data = bytes(output, encoding='utf8', errors='surrogateescape')
+13 -23
View File
@@ -6,9 +6,7 @@ from cibuildwheel.environment import parse_environment
def test_basic_parsing():
environment_recipe = parse_environment('VAR=1 VBR=2')
environment_dict = environment_recipe.as_dictionary(
prev_environment={}
)
environment_dict = environment_recipe.as_dictionary(prev_environment={})
environment_cmds = environment_recipe.as_shell_commands()
assert environment_dict == {'VAR': '1', 'VBR': '2'}
@@ -18,9 +16,7 @@ def test_basic_parsing():
def test_quotes():
environment_recipe = parse_environment('A=1 VAR="1 NOT_A_VAR=2" VBR=\'vbr\'')
environment_dict = environment_recipe.as_dictionary(
prev_environment={}
)
environment_dict = environment_recipe.as_dictionary(prev_environment={})
environment_cmds = environment_recipe.as_shell_commands()
assert environment_dict == {'A': '1', 'VAR': '1 NOT_A_VAR=2', 'VBR': 'vbr'}
@@ -30,9 +26,7 @@ def test_quotes():
def test_inheritance():
environment_recipe = parse_environment('PATH=$PATH:/usr/local/bin')
environment_dict = environment_recipe.as_dictionary(
prev_environment={'PATH': '/usr/bin'}
)
environment_dict = environment_recipe.as_dictionary(prev_environment={'PATH': '/usr/bin'})
environment_cmds = environment_recipe.as_shell_commands()
assert environment_dict == {'PATH': '/usr/bin:/usr/local/bin'}
@@ -45,9 +39,7 @@ def test_shell_eval():
env_copy = os.environ.copy()
env_copy.pop('VAR', None)
environment_dict = environment_recipe.as_dictionary(
prev_environment=env_copy
)
environment_dict = environment_recipe.as_dictionary(prev_environment=env_copy)
environment_cmds = environment_recipe.as_shell_commands()
assert environment_dict['VAR'] == 'a test string'
@@ -57,9 +49,7 @@ def test_shell_eval():
def test_shell_eval_and_env():
environment_recipe = parse_environment('VAR="$(echo "$PREV_VAR" string)"')
environment_dict = environment_recipe.as_dictionary(
prev_environment={'PREV_VAR': '1 2 3'}
)
environment_dict = environment_recipe.as_dictionary(prev_environment={'PREV_VAR': '1 2 3'})
environment_cmds = environment_recipe.as_shell_commands()
assert environment_dict == {'PREV_VAR': '1 2 3', 'VAR': '1 2 3 string'}
@@ -69,9 +59,7 @@ def test_shell_eval_and_env():
def test_empty_var():
environment_recipe = parse_environment('CFLAGS=')
environment_dict = environment_recipe.as_dictionary(
prev_environment={'CFLAGS': '-Wall'}
)
environment_dict = environment_recipe.as_dictionary(prev_environment={'CFLAGS': '-Wall'})
environment_cmds = environment_recipe.as_shell_commands()
assert environment_dict == {'CFLAGS': ''}
@@ -91,7 +79,9 @@ def test_no_vars():
def test_no_vars_pass_through():
environment_recipe = parse_environment('')
environment_dict = environment_recipe.as_dictionary(prev_environment={'CIBUILDWHEEL': 'awesome'})
environment_dict = environment_recipe.as_dictionary(
prev_environment={'CIBUILDWHEEL': 'awesome'}
)
assert environment_dict == {'CIBUILDWHEEL': 'awesome'}
@@ -109,15 +99,15 @@ def test_substitution_with_backslash():
environment_recipe = parse_environment('PATH2="somewhere_else;$PATH1"')
# pass the existing process env so PATH is available
environment_dict = environment_recipe.as_dictionary(prev_environment={
'PATH1': 'c:\\folder\\'
})
environment_dict = environment_recipe.as_dictionary(prev_environment={'PATH1': 'c:\\folder\\'})
assert environment_dict.get('PATH2') == 'somewhere_else;c:\\folder\\'
def test_awkwardly_quoted_variable():
environment_recipe = parse_environment('VAR2=something"like this""$VAR1"$VAR1$(echo "theres more")"$(echo "and more!")"')
environment_recipe = parse_environment(
'VAR2=something"like this""$VAR1"$VAR1$(echo "theres more")"$(echo "and more!")"'
)
# pass the existing process env so PATH is available
environment_dict = environment_recipe.as_dictionary({'VAR1': 'but wait'})
+65 -42
View File
@@ -66,36 +66,38 @@ def test_empty_selector(platform, intercepted_build_args, monkeypatch):
assert e.value.code == 3
@pytest.mark.parametrize('architecture, image, full_image', [
('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64:*'),
('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64:*'),
('x86_64', 'manylinux2010', 'quay.io/pypa/manylinux2010_x86_64:*'),
('x86_64', 'manylinux2014', 'quay.io/pypa/manylinux2014_x86_64:*'),
('x86_64', 'manylinux_2_24', 'quay.io/pypa/manylinux_2_24_x86_64:*'),
('x86_64', 'custom_image', 'custom_image'),
('i686', None, 'quay.io/pypa/manylinux2010_i686:*'),
('i686', 'manylinux1', 'quay.io/pypa/manylinux1_i686:*'),
('i686', 'manylinux2010', 'quay.io/pypa/manylinux2010_i686:*'),
('i686', 'manylinux2014', 'quay.io/pypa/manylinux2014_i686:*'),
('i686', 'manylinux_2_24', 'quay.io/pypa/manylinux_2_24_i686:*'),
('i686', 'custom_image', 'custom_image'),
('pypy_x86_64', None, 'pypywheels/manylinux2010-pypy_x86_64:*'),
('pypy_x86_64', 'manylinux1', 'manylinux1'), # Does not exist
('pypy_x86_64', 'manylinux2010', 'pypywheels/manylinux2010-pypy_x86_64:*'),
('pypy_x86_64', 'manylinux2014', 'manylinux2014'), # Does not exist (yet)
('pypy_x86_64', 'custom_image', 'custom_image'),
])
def test_manylinux_images(architecture, image, full_image, platform, intercepted_build_args, monkeypatch):
@pytest.mark.parametrize(
'architecture, image, full_image',
[
('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64:*'),
('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64:*'),
('x86_64', 'manylinux2010', 'quay.io/pypa/manylinux2010_x86_64:*'),
('x86_64', 'manylinux2014', 'quay.io/pypa/manylinux2014_x86_64:*'),
('x86_64', 'manylinux_2_24', 'quay.io/pypa/manylinux_2_24_x86_64:*'),
('x86_64', 'custom_image', 'custom_image'),
('i686', None, 'quay.io/pypa/manylinux2010_i686:*'),
('i686', 'manylinux1', 'quay.io/pypa/manylinux1_i686:*'),
('i686', 'manylinux2010', 'quay.io/pypa/manylinux2010_i686:*'),
('i686', 'manylinux2014', 'quay.io/pypa/manylinux2014_i686:*'),
('i686', 'manylinux_2_24', 'quay.io/pypa/manylinux_2_24_i686:*'),
('i686', 'custom_image', 'custom_image'),
('pypy_x86_64', None, 'pypywheels/manylinux2010-pypy_x86_64:*'),
('pypy_x86_64', 'manylinux1', 'manylinux1'), # Does not exist
('pypy_x86_64', 'manylinux2010', 'pypywheels/manylinux2010-pypy_x86_64:*'),
('pypy_x86_64', 'manylinux2014', 'manylinux2014'), # Does not exist (yet)
('pypy_x86_64', 'custom_image', 'custom_image'),
],
)
def test_manylinux_images(
architecture, image, full_image, platform, intercepted_build_args, monkeypatch
):
if image is not None:
monkeypatch.setenv('CIBW_MANYLINUX_' + architecture.upper() + '_IMAGE', image)
main()
if platform == 'linux':
assert fnmatch(
intercepted_build_args.args[0].manylinux_images[architecture],
full_image
)
assert fnmatch(intercepted_build_args.args[0].manylinux_images[architecture], full_image)
else:
assert intercepted_build_args.args[0].manylinux_images is None
@@ -113,7 +115,9 @@ def get_default_repair_command(platform):
@pytest.mark.parametrize('repair_command', [None, 'repair', 'repair -w {dest_dir} {wheel}'])
@pytest.mark.parametrize('platform_specific', [False, True])
def test_repair_command(repair_command, platform_specific, platform, intercepted_build_args, monkeypatch):
def test_repair_command(
repair_command, platform_specific, platform, intercepted_build_args, monkeypatch
):
if repair_command is not None:
if platform_specific:
monkeypatch.setenv('CIBW_REPAIR_WHEEL_COMMAND_' + platform.upper(), repair_command)
@@ -127,11 +131,10 @@ def test_repair_command(repair_command, platform_specific, platform, intercepted
assert intercepted_build_args.args[0].repair_command == expected_repair
@pytest.mark.parametrize('environment', [
{},
{'something': 'value'},
{'something': 'value', 'something_else': 'other_value'}
])
@pytest.mark.parametrize(
'environment',
[{}, {'something': 'value'}, {'something': 'value', 'something_else': 'other_value'}],
)
@pytest.mark.parametrize('platform_specific', [False, True])
def test_environment(environment, platform_specific, platform, intercepted_build_args, monkeypatch):
env_string = ' '.join(f'{k}={v}' for k, v in environment.items())
@@ -150,7 +153,9 @@ def test_environment(environment, platform_specific, platform, intercepted_build
@pytest.mark.parametrize('test_requires', [None, 'requirement other_requirement'])
@pytest.mark.parametrize('platform_specific', [False, True])
def test_test_requires(test_requires, platform_specific, platform, intercepted_build_args, monkeypatch):
def test_test_requires(
test_requires, platform_specific, platform, intercepted_build_args, monkeypatch
):
if test_requires is not None:
if platform_specific:
monkeypatch.setenv('CIBW_TEST_REQUIRES_' + platform.upper(), test_requires)
@@ -175,12 +180,16 @@ def test_test_extras(test_extras, platform_specific, platform, intercepted_build
main()
assert intercepted_build_args.args[0].test_extras == ('[' + test_extras + ']' if test_extras else '')
assert intercepted_build_args.args[0].test_extras == (
'[' + test_extras + ']' if test_extras else ''
)
@pytest.mark.parametrize('test_command', [None, 'test --command'])
@pytest.mark.parametrize('platform_specific', [False, True])
def test_test_command(test_command, platform_specific, platform, intercepted_build_args, monkeypatch):
def test_test_command(
test_command, platform_specific, platform, intercepted_build_args, monkeypatch
):
if test_command is not None:
if platform_specific:
monkeypatch.setenv('CIBW_TEST_COMMAND_' + platform.upper(), test_command)
@@ -195,7 +204,9 @@ def test_test_command(test_command, platform_specific, platform, intercepted_bui
@pytest.mark.parametrize('before_build', [None, 'before --build'])
@pytest.mark.parametrize('platform_specific', [False, True])
def test_before_build(before_build, platform_specific, platform, intercepted_build_args, monkeypatch):
def test_before_build(
before_build, platform_specific, platform, intercepted_build_args, monkeypatch
):
if before_build is not None:
if platform_specific:
monkeypatch.setenv('CIBW_BEFORE_BUILD_' + platform.upper(), before_build)
@@ -210,7 +221,9 @@ def test_before_build(before_build, platform_specific, platform, intercepted_bui
@pytest.mark.parametrize('build_verbosity', [None, 0, 2, -2, 4, -4])
@pytest.mark.parametrize('platform_specific', [False, True])
def test_build_verbosity(build_verbosity, platform_specific, platform, intercepted_build_args, monkeypatch):
def test_build_verbosity(
build_verbosity, platform_specific, platform, intercepted_build_args, monkeypatch
):
if build_verbosity is not None:
if platform_specific:
monkeypatch.setenv('CIBW_BUILD_VERBOSITY_' + platform.upper(), str(build_verbosity))
@@ -225,13 +238,23 @@ def test_build_verbosity(build_verbosity, platform_specific, platform, intercept
@pytest.mark.parametrize('option_name', ['CIBW_BUILD', 'CIBW_SKIP'])
@pytest.mark.parametrize('option_value, build_selector_patterns', [
('*-manylinux1_*', ['*-manylinux_*']),
('*-macosx_10_6_intel', ['*-macosx_x86_64']),
('*-macosx_10_9_x86_64', ['*-macosx_x86_64']),
('cp37-macosx_10_9_x86_64', ['cp37-macosx_x86_64']),
])
def test_build_selector_migrations(intercepted_build_args, monkeypatch, option_name, option_value, build_selector_patterns, allow_empty):
@pytest.mark.parametrize(
'option_value, build_selector_patterns',
[
('*-manylinux1_*', ['*-manylinux_*']),
('*-macosx_10_6_intel', ['*-macosx_x86_64']),
('*-macosx_10_9_x86_64', ['*-macosx_x86_64']),
('cp37-macosx_10_9_x86_64', ['cp37-macosx_x86_64']),
],
)
def test_build_selector_migrations(
intercepted_build_args,
monkeypatch,
option_name,
option_value,
build_selector_patterns,
allow_empty,
):
monkeypatch.setenv(option_name, option_value)
main()
+12 -2
View File
@@ -167,8 +167,18 @@ def test_archs_platform_all(platform, intercepted_build_args, monkeypatch):
build_options = intercepted_build_args.args[0]
if platform == 'linux':
assert build_options.architectures == {Architecture.x86_64, Architecture.i686, Architecture.aarch64, Architecture.ppc64le, Architecture.s390x}
assert build_options.architectures == {
Architecture.x86_64,
Architecture.i686,
Architecture.aarch64,
Architecture.ppc64le,
Architecture.s390x,
}
elif platform == 'windows':
assert build_options.architectures == {Architecture.x86, Architecture.AMD64}
elif platform == 'macos':
assert build_options.architectures == {Architecture.x86_64, Architecture.arm64, Architecture.universal2}
assert build_options.architectures == {
Architecture.x86_64,
Architecture.arm64,
Architecture.universal2,
}
@@ -50,10 +50,14 @@ def test_override_env(platform, monkeypatch, intercepted_build_args):
def test_override_setup_cfg(platform, monkeypatch, intercepted_build_args, fake_package_dir):
fake_package_dir.joinpath("setup.cfg").write_text(textwrap.dedent("""
fake_package_dir.joinpath("setup.cfg").write_text(
textwrap.dedent(
"""
[options]
python_requires = >=3.8
"""))
"""
)
)
main()
@@ -67,10 +71,14 @@ def test_override_setup_cfg(platform, monkeypatch, intercepted_build_args, fake_
def test_override_pyproject_toml(platform, monkeypatch, intercepted_build_args, fake_package_dir):
fake_package_dir.joinpath("pyproject.toml").write_text(textwrap.dedent("""
fake_package_dir.joinpath("pyproject.toml").write_text(
textwrap.dedent(
"""
[project]
requires-python = ">=3.8"
"""))
"""
)
)
main()
@@ -84,14 +92,18 @@ def test_override_pyproject_toml(platform, monkeypatch, intercepted_build_args,
def test_override_setup_py_simple(platform, monkeypatch, intercepted_build_args, fake_package_dir):
fake_package_dir.joinpath("setup.py").write_text(textwrap.dedent("""
fake_package_dir.joinpath("setup.py").write_text(
textwrap.dedent(
"""
from setuptools import setup
setup(
name = "other",
python_requires = ">=3.7",
)
"""))
"""
)
)
main()
+54 -18
View File
@@ -5,7 +5,9 @@ from cibuildwheel.projectfiles import get_requires_python_str, setup_py_python_r
def test_read_setup_py_simple(tmp_path):
with open(tmp_path / "setup.py", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
from setuptools import setup
setup(
@@ -14,7 +16,9 @@ def test_read_setup_py_simple(tmp_path):
example = ["item", "other"],
python_requires = "1.23",
)
"""))
"""
)
)
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23"
assert get_requires_python_str(tmp_path) == "1.23"
@@ -22,7 +26,9 @@ def test_read_setup_py_simple(tmp_path):
def test_read_setup_py_full(tmp_path):
with open(tmp_path / "setup.py", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
import setuptools
setuptools.randomfunc()
@@ -33,7 +39,9 @@ def test_read_setup_py_full(tmp_path):
example = ["item", "other"],
python_requires = "1.24",
)
"""))
"""
)
)
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.24"
assert get_requires_python_str(tmp_path) == "1.24"
@@ -41,7 +49,9 @@ def test_read_setup_py_full(tmp_path):
def test_read_setup_py_assign(tmp_path):
with open(tmp_path / "setup.py", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
from setuptools import setup
REQUIRES = "3.21"
@@ -52,7 +62,9 @@ def test_read_setup_py_assign(tmp_path):
example = ["item", "other"],
python_requires = REQUIRES,
)
"""))
"""
)
)
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None
assert get_requires_python_str(tmp_path) is None
@@ -60,7 +72,9 @@ def test_read_setup_py_assign(tmp_path):
def test_read_setup_py_None(tmp_path):
with open(tmp_path / "setup.py", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
from setuptools import setup
REQUIRES = None
@@ -71,7 +85,9 @@ def test_read_setup_py_None(tmp_path):
example = ["item", "other"],
python_requires = None,
)
"""))
"""
)
)
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None
assert get_requires_python_str(tmp_path) is None
@@ -79,7 +95,9 @@ def test_read_setup_py_None(tmp_path):
def test_read_setup_py_empty(tmp_path):
with open(tmp_path / "setup.py", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
from setuptools import setup
REQUIRES = "3.21"
@@ -89,7 +107,9 @@ def test_read_setup_py_empty(tmp_path):
other = 23,
example = ["item", "other"],
)
"""))
"""
)
)
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None
assert get_requires_python_str(tmp_path) is None
@@ -97,46 +117,62 @@ def test_read_setup_py_empty(tmp_path):
def test_read_setup_cfg(tmp_path):
with open(tmp_path / "setup.cfg", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
[options]
python_requires = 1.234
[metadata]
something = other
"""))
"""
)
)
assert get_requires_python_str(tmp_path) == "1.234"
def test_read_setup_cfg_empty(tmp_path):
with open(tmp_path / "setup.cfg", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
[options]
other = 1.234
[metadata]
something = other
"""))
"""
)
)
assert get_requires_python_str(tmp_path) is None
def test_read_pyproject_toml(tmp_path):
with open(tmp_path / "pyproject.toml", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
[project]
requires-python = "1.654"
[tool.cibuildwheel]
something = "other"
"""))
"""
)
)
assert get_requires_python_str(tmp_path) == "1.654"
def test_read_pyproject_toml_empty(tmp_path):
with open(tmp_path / "pyproject.toml", "w") as f:
f.write(dedent("""
f.write(
dedent(
"""
[project]
other = 1.234
"""))
"""
)
)
assert get_requires_python_str(tmp_path) is None