Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions cibuildwheel/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys
import textwrap
import time
from collections.abc import Generator
from collections.abc import Generator, Sequence
from pathlib import Path
from typing import IO, TYPE_CHECKING, AnyStr, Final, Literal

Expand Down Expand Up @@ -166,7 +166,7 @@ def build_start(self, identifier: str) -> None:
self.build_start_time = time.time()
self.active_build_identifier = identifier

def build_end(self, filename: Path | None) -> None:
def build_end(self, filename: Path | Sequence[Path] | None) -> None:
assert self.build_start_time is not None
assert self.active_build_identifier is not None
self.step_end()
Expand All @@ -178,9 +178,33 @@ def build_end(self, filename: Path | None) -> None:

print()
print(f"{c.green}{s.done} {c.end}{self.active_build_identifier} finished in {duration_str}")
self.summary.append(
BuildInfo(identifier=self.active_build_identifier, filename=filename, duration=duration)
)

match filename:
case []:
self.summary.append(
BuildInfo(
identifier=self.active_build_identifier,
filename=None,
duration=duration,
)
)
case [*_]:
for f in filename:
self.summary.append(
BuildInfo(
identifier=self.active_build_identifier,
filename=f,
duration=duration,
)
)
case _:
self.summary.append(
BuildInfo(
identifier=self.active_build_identifier,
filename=filename,
duration=duration,
)
)

self.build_start_time = None
self.active_build_identifier = None
Expand Down
91 changes: 54 additions & 37 deletions cibuildwheel/platforms/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file
from cibuildwheel.util.helpers import prepare_command
from cibuildwheel.util.packaging import find_compatible_wheel
from cibuildwheel.util.packaging import (
find_all_compatible_wheels,
should_skip_build,
)
from cibuildwheel.util.python_build_standalone import create_python_build_standalone_environment
from cibuildwheel.venv import constraint_flags, find_uv, virtualenv

Expand Down Expand Up @@ -139,29 +142,32 @@ def build(options: Options, tmp_path: Path) -> None:
config, build_options, build_path, python_dir, build_env, android_env
)

compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel:
skip_build = should_skip_build(built_wheels, config.identifier)
if skip_build:
compatible_wheels = find_all_compatible_wheels(built_wheels, config.identifier)
print(
f"\nFound previously built wheel {compatible_wheel.name} that is "
f"compatible with {config.identifier}. Skipping build step..."
f"\nFound previously built wheels {[p.name for p in compatible_wheels]} "
f"that are compatible with {config.identifier}. Skipping build step..."
)
repaired_wheel = compatible_wheel
repaired_wheels = compatible_wheels
else:
before_build(state)
built_wheel = build_wheel(state)
repaired_wheel = repair_wheel(state, built_wheel)
built_wheels_list = build_wheels(state)
repaired_wheels = [repair_wheel(state, bw) for bw in built_wheels_list]

test_wheel(state, repaired_wheel, build_frontend=build_options.build_frontend.name)
test_wheels(state, repaired_wheels, build_frontend=build_options.build_frontend.name)

output_wheel: Path | None = None
if compatible_wheel is None:
output_wheel = move_file(
repaired_wheel, build_options.output_dir / repaired_wheel.name
)
built_wheels.append(output_wheel)
output_wheels: list[Path] = []
if not skip_build:
for repaired_wheel in repaired_wheels:
output_wheel = move_file(
repaired_wheel, build_options.output_dir / repaired_wheel.name
)
built_wheels.append(output_wheel)
output_wheels.append(output_wheel)

shutil.rmtree(build_path)
log.build_end(output_wheel)
log.build_end(output_wheels or None)

except subprocess.CalledProcessError as error:
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
Expand Down Expand Up @@ -446,7 +452,7 @@ def before_build(state: BuildState) -> None:
)


def build_wheel(state: BuildState) -> Path:
def build_wheels(state: BuildState) -> list[Path]:
log.step("Building wheel...")
built_wheel_dir = state.build_path / "built_wheel"
match state.options.build_frontend.name:
Expand Down Expand Up @@ -491,20 +497,20 @@ def build_wheel(state: BuildState) -> Path:
raise AssertionError(msg)

built_wheels = list(built_wheel_dir.glob("*.whl"))
if len(built_wheels) != 1:
msg = f"{built_wheel_dir} contains {len(built_wheels)} wheels; expected 1"
if not built_wheels:
msg = f"{built_wheel_dir} contains no wheels"
raise errors.FatalError(msg)
built_wheel = built_wheels[0]

if built_wheel.name.endswith("none-any.whl"):
raise errors.NonPlatformWheelError()
return built_wheel
for built_wheel in built_wheels:
if built_wheel.name.endswith("none-any.whl"):
raise errors.NonPlatformWheelError()
return built_wheels


def repair_wheel(state: BuildState, built_wheel: Path) -> Path:
log.step("Repairing wheel...")
repaired_wheel_dir = state.build_path / "repaired_wheel"
repaired_wheel_dir.mkdir()
repaired_wheel_dir.mkdir(parents=True, exist_ok=True)

if state.options.repair_command:
shell(
Expand Down Expand Up @@ -633,7 +639,7 @@ def soname_with_hash(src_path: Path) -> str:
return src_name


def test_wheel(state: BuildState, wheel: Path, *, build_frontend: str) -> None:
def test_wheels(state: BuildState, wheels: list[Path], *, build_frontend: str) -> None:
test_command = state.options.test_command
if not (test_command and state.options.test_selector(state.config.identifier)):
return
Expand Down Expand Up @@ -670,20 +676,31 @@ def test_wheel(state: BuildState, wheel: Path, *, build_frontend: str) -> None:
]
)

# Install the wheel and test-requires.
# Install the wheels and test-requires.
site_packages_dir = state.build_path / "site-packages"
site_packages_dir.mkdir()
call(
*pip,
"install",
"--only-binary=:all:",
*platform_args,
"--target",
site_packages_dir,
f"{wheel}{state.options.test_extras}",
*state.options.test_requires,
env=state.android_env,
)
for wheel in wheels:
call(
*pip,
"install",
"--only-binary=:all:",
*platform_args,
"--target",
site_packages_dir,
f"{wheel}{state.options.test_extras}",
env=state.android_env,
)
if state.options.test_requires:
call(
*pip,
"install",
"--only-binary=:all:",
*platform_args,
"--target",
site_packages_dir,
*state.options.test_requires,
env=state.android_env,
)

# Copy test-sources.
cwd_dir = state.build_path / "cwd"
Expand Down
133 changes: 73 additions & 60 deletions cibuildwheel/platforms/ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
from cibuildwheel.util.cmd import call, shell, split_command
from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file
from cibuildwheel.util.helpers import prepare_command, unwrap_preserving_paragraphs
from cibuildwheel.util.packaging import find_compatible_wheel
from cibuildwheel.util.packaging import (
find_all_compatible_wheels,
should_skip_build,
)
from cibuildwheel.venv import constraint_flags, virtualenv


Expand Down Expand Up @@ -437,7 +440,6 @@ def build(options: Options, tmp_path: Path) -> None:
identifier_tmp_dir = tmp_path / config.identifier
identifier_tmp_dir.mkdir()
built_wheel_dir = identifier_tmp_dir / "built_wheel"
repaired_wheel_dir = identifier_tmp_dir / "repaired_wheel"

constraints_path = build_options.dependency_constraints.get_for_python_version(
version=config.version, tmp_dir=identifier_tmp_dir
Expand All @@ -452,15 +454,16 @@ def build(options: Options, tmp_path: Path) -> None:
xbuild_tools=build_options.xbuild_tools,
)

compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel:
skip_build = should_skip_build(built_wheels, config.identifier)
repaired_wheels: list[Path]
if skip_build:
compatible_wheels = find_all_compatible_wheels(built_wheels, config.identifier)
log.step_end()
print(
f"\nFound previously built wheel {compatible_wheel.name} "
f"that is compatible with {config.identifier}. "
"Skipping build step..."
f"\nFound previously built wheels {[p.name for p in compatible_wheels]}, "
f"that is compatible with {config.identifier}. Skipping build step..."
)
test_wheel = compatible_wheel
repaired_wheels = compatible_wheels
else:
if build_options.before_build:
log.step("Running before_build...")
Expand Down Expand Up @@ -511,35 +514,42 @@ def build(options: Options, tmp_path: Path) -> None:
case _:
assert_never(build_frontend)

built_wheel = next(built_wheel_dir.glob("*.whl"))

if built_wheel.name.endswith("none-any.whl"):
raise errors.NonPlatformWheelError()

repaired_wheel_dir.mkdir()
if build_options.repair_command:
log.step("Repairing wheel...")

repair_command_prepared = prepare_command(
build_options.repair_command,
wheel=built_wheel,
dest_dir=repaired_wheel_dir,
package=build_options.package_dir,
project=".",
)
shell(repair_command_prepared, env=env)
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
built_wheels_list = list(built_wheel_dir.glob("*.whl"))
if not built_wheels_list:
msg = "Build step did not produce any wheels"
raise errors.FatalError(msg)

for built_wheel in built_wheels_list:
if built_wheel.name.endswith("none-any.whl"):
raise errors.NonPlatformWheelError()

repaired_wheels = []
for wheel_idx, built_wheel in enumerate(built_wheels_list):
this_repaired_dir = identifier_tmp_dir / f"repaired_wheel_{wheel_idx}"
this_repaired_dir.mkdir()
if build_options.repair_command:
log.step("Repairing wheel...")

repair_command_prepared = prepare_command(
build_options.repair_command,
wheel=built_wheel,
dest_dir=this_repaired_dir,
package=build_options.package_dir,
project=".",
)
shell(repair_command_prepared, env=env)
else:
shutil.move(str(built_wheel), this_repaired_dir)

try:
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
except StopIteration:
raise errors.RepairStepProducedNoWheelError() from None
try:
repaired_wheel = next(this_repaired_dir.glob("*.whl"))
except StopIteration:
raise errors.RepairStepProducedNoWheelError() from None

if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)

test_wheel = repaired_wheel
repaired_wheels.append(repaired_wheel)

log.step_end()

Expand Down Expand Up @@ -585,27 +595,28 @@ def build(options: Options, tmp_path: Path) -> None:
)

log.step("Installing test requirements...")
# Install the compiled wheel (with any test extras), plus
# Install the compiled wheels (with any test extras), plus
# the test requirements. Use the --platform tag to force
# the installation of iOS wheels; this requires the use of
# --only-binary=:all:
ios_version = test_env["IPHONEOS_DEPLOYMENT_TARGET"]
platform_tag = f"ios_{ios_version.replace('.', '_')}_{config.arch}_{config.sdk}"

call(
"python",
"-m",
"pip",
"install",
"--only-binary=:all:",
"--platform",
platform_tag,
"--target",
testbed_path / "iOSTestbed" / "app_packages",
f"{test_wheel}{build_options.test_extras}",
*build_options.test_requires,
env=test_env,
)
for test_wheel in repaired_wheels:
call(
"python",
"-m",
"pip",
"install",
"--only-binary=:all:",
"--platform",
platform_tag,
"--target",
testbed_path / "iOSTestbed" / "app_packages",
f"{test_wheel}{build_options.test_extras}",
*build_options.test_requires,
env=test_env,
)

log.step("Running test suite...")

Expand Down Expand Up @@ -705,21 +716,23 @@ def build(options: Options, tmp_path: Path) -> None:

log.step_end()

# We're all done here; move it to output (overwrite existing)
output_wheel: Path | None = None
if compatible_wheel is None:
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
moved_wheel = move_file(repaired_wheel, output_wheel)
if moved_wheel != output_wheel.resolve():
log.warning(
f"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
)
built_wheels.append(output_wheel)
# We're all done here; move wheels to output (overwrite existing)
output_wheels: list[Path] = []
if not skip_build:
for repaired_wheel in repaired_wheels:
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
moved_wheel = move_file(repaired_wheel, output_wheel)
if moved_wheel != output_wheel.resolve():
log.warning(
f"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
)
built_wheels.append(output_wheel)
output_wheels.append(output_wheel)

# Clean up
shutil.rmtree(identifier_tmp_dir)

log.build_end(output_wheel)
log.build_end(output_wheels or None)
except subprocess.CalledProcessError as error:
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
raise errors.FatalError(msg) from error
Loading
Loading