style: apply black via pre-commit run -a

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