From 178aaea6c7d7cd2a1dbcee596c991f41d4d82605 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 30 Apr 2021 17:56:34 -0400 Subject: [PATCH] style: apply black via pre-commit run -a --- bin/bump_version.py | 53 ++-- bin/make_dependency_update_pr.py | 19 +- bin/projects.py | 16 +- bin/run_example_ci_configs.py | 27 ++- bin/run_tests.py | 5 +- bin/sample_build.py | 8 +- bin/update_dependencies.py | 63 ++--- bin/update_pythons.py | 20 +- cibuildwheel/__main__.py | 190 ++++++++++----- cibuildwheel/architecture.py | 8 +- cibuildwheel/bashlex_eval.py | 22 +- cibuildwheel/docker_container.py | 93 ++++--- cibuildwheel/environment.py | 16 +- cibuildwheel/linux.py | 151 +++++++++--- cibuildwheel/logger.py | 14 +- cibuildwheel/macos.py | 229 +++++++++++++----- cibuildwheel/projectfiles.py | 5 +- cibuildwheel/resources/get-pip.py | 20 +- cibuildwheel/resources/install_certifi.py | 21 +- cibuildwheel/typing.py | 13 +- cibuildwheel/util.py | 18 +- cibuildwheel/windows.py | 152 ++++++++---- test/test_0_basic.py | 13 +- test/test_before_all.py | 45 ++-- test/test_before_build.py | 49 ++-- test/test_before_test.py | 29 ++- test/test_build_skip.py | 22 +- test/test_cpp_standards.py | 35 ++- test/test_dependency_versions.py | 89 ++++--- test/test_docker_images.py | 30 ++- test/test_emulation.py | 26 +- test/test_environment.py | 41 ++-- test/test_macos_archs.py | 50 ++-- test/test_manylinuxXXXX_only.py | 20 +- test/test_projects/__main__.py | 26 +- test/test_projects/base.py | 1 + test/test_projects/c.py | 38 +-- test/test_pure_wheel.py | 8 +- test/test_ssl.py | 6 +- test/test_subdir_package.py | 31 ++- test/test_testing.py | 68 ++++-- test/test_troubleshooting.py | 4 +- test/test_unicode.py | 12 +- test/utils.py | 25 +- unit_test/build_selector_test.py | 22 +- unit_test/conftest.py | 4 +- unit_test/dependency_constraints_test.py | 20 +- unit_test/docker_container_test.py | 63 ++++- unit_test/environment_test.py | 36 +-- unit_test/main_tests/main_options_test.py | 107 ++++---- unit_test/main_tests/main_platform_test.py | 14 +- .../main_tests/main_requires_python_test.py | 24 +- unit_test/projectfiles_test.py | 72 ++++-- 53 files changed, 1479 insertions(+), 714 deletions(-) diff --git a/bin/bump_version.py b/bin/bump_version.py index 81da1a2e..88587646 100755 --- a/bin/bump_version.py +++ b/bin/bump_version.py @@ -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.') diff --git a/bin/make_dependency_update_pr.py b/bin/make_dependency_update_pr.py index 01acf664..1c409fed 100755 --- a/bin/make_dependency_update_pr.py +++ b/bin/make_dependency_update_pr.py @@ -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.') diff --git a/bin/projects.py b/bin/projects.py index d6025846..eeb1ae5f 100755 --- a/bin/projects.py +++ b/bin/projects.py @@ -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) diff --git a/bin/run_example_ci_configs.py b/bin/run_example_ci_configs.py index d436cb32..3be18b64 100755 --- a/bin/run_example_ci_configs.py +++ b/bin/run_example_ci_configs.py @@ -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`') diff --git a/bin/run_tests.py b/bin/run_tests.py index 94bc00c9..fc4dac01 100755 --- a/bin/run_tests.py +++ b/bin/run_tests.py @@ -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, + ) diff --git a/bin/sample_build.py b/bin/sample_build.py index 0f04e5cb..04b86db1 100755 --- a/bin/sample_build.py +++ b/bin/sample_build.py @@ -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) diff --git a/bin/update_dependencies.py b/bin/update_dependencies.py index 0b5f7fc4..408c37e6 100755 --- a/bin/update_dependencies.py +++ b/bin/update_dependencies.py @@ -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'] diff --git a/bin/update_pythons.py b/bin/update_pythons.py index bf801590..4e1e288b 100755 --- a/bin/update_pythons.py +++ b/bin/update_pythons.py @@ -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( diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 987cdb61..56530bee 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -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) diff --git a/cibuildwheel/architecture.py b/cibuildwheel/architecture.py index 7aeb7be3..38438673 100644 --- a/cibuildwheel/architecture.py +++ b/cibuildwheel/architecture.py @@ -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': diff --git a/cibuildwheel/bashlex_eval.py b/cibuildwheel/bashlex_eval.py index 05dd46af..44fcd314 100644 --- a/cibuildwheel/bashlex_eval.py +++ b/cibuildwheel/bashlex_eval.py @@ -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) diff --git a/cibuildwheel/docker_container.py b/cibuildwheel/docker_container.py index 204a0d2a..f7cbe174 100644 --- a/cibuildwheel/docker_container.py +++ b/cibuildwheel/docker_container.py @@ -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: diff --git a/cibuildwheel/environment.py b/cibuildwheel/environment.py index 9d29bcb1..25f7bb59 100644 --- a/cibuildwheel/environment.py +++ b/cibuildwheel/environment.py @@ -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: diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 3e99e467..258c4fe8 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -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)) diff --git a/cibuildwheel/logger.py b/cibuildwheel/logger.py index 60e1b3c2..aac836e0 100644 --- a/cibuildwheel/logger.py +++ b/cibuildwheel/logger.py @@ -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 ''' diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index b58d8a95..a8a07b49 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -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) diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index 8d49fe4d..0ade03f5 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -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) diff --git a/cibuildwheel/resources/get-pip.py b/cibuildwheel/resources/get-pip.py index c6dadf8a..1ffc5aca 100644 --- a/cibuildwheel/resources/get-pip.py +++ b/cibuildwheel/resources/get-pip.py @@ -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 diff --git a/cibuildwheel/resources/install_certifi.py b/cibuildwheel/resources/install_certifi.py index e33ac530..5d8c500f 100644 --- a/cibuildwheel/resources/install_certifi.py +++ b/cibuildwheel/resources/install_certifi.py @@ -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 diff --git a/cibuildwheel/typing.py b/cibuildwheel/typing.py index 9a650d54..c7012bd4 100644 --- a/cibuildwheel/typing.py +++ b/cibuildwheel/typing.py @@ -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: diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 6ec7fe91..88817f5c 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -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) diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 4e40d00e..c27cac92 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -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) diff --git a/test/test_0_basic.py b/test/test_0_basic.py index 22905722..d43f7b5a 100644 --- a/test/test_0_basic.py +++ b/test/test_0_basic.py @@ -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) diff --git a/test/test_before_all.py b/test/test_before_all.py index 2306fb27..a094ce38 100644 --- a/test/test_before_all.py +++ b/test/test_before_all.py @@ -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) diff --git a/test/test_before_build.py b/test/test_before_build.py index 6939bebe..9b04481a 100644 --- a/test/test_before_build.py +++ b/test/test_before_build.py @@ -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) diff --git a/test/test_before_test.py b/test/test_before_test.py index 2fa79024..9d6155d2 100644 --- a/test/test_before_test.py +++ b/test/test_before_test.py @@ -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') diff --git a/test/test_build_skip.py b/test/test_build_skip.py index e5c6482e..1e96344d 100644 --- a/test/test_build_skip.py +++ b/test/test_build_skip.py @@ -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) diff --git a/test/test_cpp_standards.py b/test/test_cpp_standards.py index a614da58..f0517473 100644 --- a/test/test_cpp_standards.py +++ b/test/test_cpp_standards.py @@ -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 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) diff --git a/test/test_dependency_versions.py b/test/test_dependency_versions.py index 47af34c1..106ecb20 100644 --- a/test/test_dependency_versions.py +++ b/test/test_dependency_versions.py @@ -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) diff --git a/test/test_docker_images.py b/test/test_docker_images.py index 7328d3a2..0790906f 100644 --- a/test/test_docker_images.py +++ b/test/test_docker_images.py @@ -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) diff --git a/test/test_emulation.py b/test/test_emulation.py index 779af3aa..2928e864 100644 --- a/test/test_emulation.py +++ b/test/test_emulation.py @@ -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 diff --git a/test/test_environment.py b/test/test_environment.py index e5d64563..f165fce2 100644 --- a/test/test_environment.py +++ b/test/test_environment.py @@ -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() diff --git a/test/test_macos_archs.py b/test/test_macos_archs.py index b525c52b..2ec759cb 100644 --- a/test/test_macos_archs.py +++ b/test/test_macos_archs.py @@ -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() diff --git a/test/test_manylinuxXXXX_only.py b/test/test_manylinuxXXXX_only.py index f71e6708..4432fe27 100644 --- a/test/test_manylinuxXXXX_only.py +++ b/test/test_manylinuxXXXX_only.py @@ -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 #include #include @@ -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'}: diff --git a/test/test_projects/__main__.py b/test/test_projects/__main__.py index efd7d7d3..e0fed58e 100644 --- a/test/test_projects/__main__.py +++ b/test/test_projects/__main__.py @@ -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('.') diff --git a/test/test_projects/base.py b/test/test_projects/base.py index cbf6426f..f58abb12 100644 --- a/test/test_projects/base.py +++ b/test/test_projects/base.py @@ -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 diff --git a/test/test_projects/c.py b/test/test_projects/c.py index 85b4ddd5..95ce7fdb 100644 --- a/test/test_projects/c.py +++ b/test/test_projects/c.py @@ -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 diff --git a/test/test_pure_wheel.py b/test/test_pure_wheel.py index 33ac5926..f6f042d1 100644 --- a/test/test_pure_wheel.py +++ b/test/test_pure_wheel.py @@ -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 ''' diff --git a/test/test_ssl.py b/test/test_ssl.py index 00900669..8b10b4c8 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -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") - ''') + ''' + ) ) diff --git a/test/test_subdir_package.py b/test/test_subdir_package.py index 14a7a58b..6eca2e8c 100644 --- a/test/test_subdir_package.py +++ b/test/test_subdir_package.py @@ -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() diff --git a/test/test_testing.py b/test/test_testing.py index 54198415..2ef751c8 100644 --- a/test/test_testing.py +++ b/test/test_testing.py @@ -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 diff --git a/test/test_troubleshooting.py b/test/test_troubleshooting.py index bedab4d9..6da19df0 100644 --- a/test/test_troubleshooting.py +++ b/test/test_troubleshooting.py @@ -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') ''' diff --git a/test/test_unicode.py b/test/test_unicode.py index abecfc69..b924be9e 100644 --- a/test/test_unicode.py +++ b/test/test_unicode.py @@ -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') diff --git a/test/utils.py b/test/utils.py index bd9270b9..5d4bcca2 100644 --- a/test/utils.py +++ b/test/utils.py @@ -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', diff --git a/unit_test/build_selector_test.py b/unit_test/build_selector_test.py index 227636bc..d17adf86 100644 --- a/unit_test/build_selector_test.py +++ b/unit_test/build_selector_test.py @@ -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') diff --git a/unit_test/conftest.py b/unit_test/conftest.py index a54658a9..89d56ffa 100644 --- a/unit_test/conftest.py +++ b/unit_test/conftest.py @@ -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): diff --git a/unit_test/dependency_constraints_test.py b/unit_test/dependency_constraints_test.py index 6aafd1e3..4e2ca528 100644 --- a/unit_test/dependency_constraints_test.py +++ b/unit_test/dependency_constraints_test.py @@ -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' + ) diff --git a/unit_test/docker_container_test.py b/unit_test/docker_container_test.py index bd07b50b..bf608e9b 100644 --- a/unit_test/docker_container_test.py +++ b/unit_test/docker_container_test.py @@ -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') diff --git a/unit_test/environment_test.py b/unit_test/environment_test.py index ab690a84..030d46fc 100644 --- a/unit_test/environment_test.py +++ b/unit_test/environment_test.py @@ -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'}) diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index dbfc7539..035d809a 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -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() diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index 45fe7d1e..3bb456de 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -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, + } diff --git a/unit_test/main_tests/main_requires_python_test.py b/unit_test/main_tests/main_requires_python_test.py index a9c86bc2..26b5333f 100644 --- a/unit_test/main_tests/main_requires_python_test.py +++ b/unit_test/main_tests/main_requires_python_test.py @@ -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() diff --git a/unit_test/projectfiles_test.py b/unit_test/projectfiles_test.py index 6b80a13a..bc9a7a54 100644 --- a/unit_test/projectfiles_test.py +++ b/unit_test/projectfiles_test.py @@ -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