Files
cibuildwheel/cibuildwheel/util.py
T
2020-06-22 13:55:04 +02:00

129 lines
3.8 KiB
Python

import os
import urllib.request
from fnmatch import fnmatch
from pathlib import Path
from time import sleep
from typing import Dict, List, NamedTuple, Optional, Union
from .environment import ParsedEnvironment
def prepare_command(command: str, **kwargs: Union[str, os.PathLike]) -> str:
'''
Preprocesses a command by expanding variables like {python}.
For example, used in the test_command option to specify the path to the
project's root.
'''
return command.format(python='python', pip='pip', **kwargs)
def get_build_verbosity_extra_flags(level: int) -> List[str]:
if level > 0:
return ['-' + level * 'v']
elif level < 0:
return ['-' + -level * 'q']
else:
return []
class BuildSelector:
def __init__(self, build_config: str, skip_config: str):
self.build_patterns = build_config.split()
self.skip_patterns = skip_config.split()
def __call__(self, build_id: str) -> bool:
def match_any(patterns: List[str]) -> bool:
return any(fnmatch(build_id, pattern) for pattern in patterns)
return match_any(self.build_patterns) and not match_any(self.skip_patterns)
def __repr__(self) -> str:
return f'BuildSelector({" ".join(self.build_patterns)!r} - {" ".join(self.skip_patterns)!r})'
# Taken from https://stackoverflow.com/a/107717
class Unbuffered:
def __init__(self, stream): # type: ignore
self.stream = stream
def write(self, data): # type: ignore
self.stream.write(data)
self.stream.flush()
def writelines(self, datas): # type: ignore
self.stream.writelines(datas)
self.stream.flush()
def __getattr__(self, attr): # type: ignore
return getattr(self.stream, attr)
def download(url: str, dest: Path) -> None:
print(f'+ Download {url} to {dest}')
dest_dir = dest.parent
if not dest_dir.exists():
dest_dir.mkdir(parents=True)
repeat_num = 3
for i in range(repeat_num):
try:
response = urllib.request.urlopen(url)
except Exception:
if i == repeat_num - 1:
raise
sleep(3)
continue
break
try:
dest.write_bytes(response.read())
finally:
response.close()
class DependencyConstraints:
def __init__(self, base_file_path: Path):
assert base_file_path.exists()
self.base_file_path = base_file_path.resolve()
@staticmethod
def with_defaults() -> 'DependencyConstraints':
return DependencyConstraints(
base_file_path=resources_dir / 'constraints.txt'
)
def get_for_python_version(self, version: str) -> Path:
version_parts = version.split('.')
# try to find a version-specific dependency file e.g. if
# ./constraints.txt is the base, look for ./constraints-python27.txt
specific_stem = self.base_file_path.stem + f'-python{version_parts[0]}{version_parts[1]}'
specific_name = specific_stem + self.base_file_path.suffix
specific_file_path = self.base_file_path.with_name(specific_name)
if specific_file_path.exists():
return specific_file_path
else:
return self.base_file_path
class BuildOptions(NamedTuple):
package_dir: Path
output_dir: Path
build_selector: BuildSelector
environment: ParsedEnvironment
before_all: str
before_build: Optional[str]
repair_command: str
manylinux_images: Optional[Dict[str, str]]
dependency_constraints: Optional[DependencyConstraints]
test_command: Optional[str]
before_test: Optional[str]
test_requires: List[str]
test_extras: str
build_verbosity: int
resources_dir = Path(__file__).resolve().parent / 'resources'
get_pip_script = resources_dir / 'get-pip.py'