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
21 changes: 20 additions & 1 deletion cibuildwheel/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from cibuildwheel.logger import log
from cibuildwheel.options import CommandLineArguments, Options, compute_options
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers, native_platform
from cibuildwheel.platforms._run import Stage
from cibuildwheel.selector import BuildSelector, EnableGroup, selector_matches
from cibuildwheel.typing import PLATFORMS, PlatformName
from cibuildwheel.util.file import CIBW_CACHE_PATH, ensure_cache_sentinel
Expand Down Expand Up @@ -208,6 +209,18 @@ def main_inner(global_options: GlobalOptions) -> None:
help="Print a full traceback for all errors",
)

parser.add_argument(
"--stage",
choices=["all", "build", "test"],
default=os.environ.get("CIBW_STAGE", "all"),
help="""
Which stage(s) to run. 'build' builds wheels into the output
directory and skips tests; 'test' skips building and runs tests
against wheels already in the output directory; 'all' (the default)
does both.
""",
)

args = CommandLineArguments(**vars(parser.parse_args()))

global_options.print_traceback_on_error = args.debug_traceback
Expand Down Expand Up @@ -388,10 +401,16 @@ def build_in_directory(args: CommandLineArguments) -> None:

output_dir.mkdir(parents=True, exist_ok=True)

stages = {
"all": frozenset({Stage.BUILD, Stage.TEST}),
"build": frozenset({Stage.BUILD}),
"test": frozenset({Stage.TEST}),
}[args.stage]

tmp_path = Path(mkdtemp(prefix="cibw-run-")).resolve(strict=True)
try:
with log.print_summary(options=options):
platform_module.build(options, tmp_path)
platform_module.build(options, tmp_path, stages)
finally:
# avoid https://github.com/python/cpython/issues/86962 by performing
# cleanup manually
Expand Down
2 changes: 2 additions & 0 deletions cibuildwheel/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class CommandLineArguments:
debug_traceback: bool
enable: list[str]
clean_cache: bool
stage: str

@classmethod
def defaults(cls) -> Self:
Expand All @@ -86,6 +87,7 @@ def defaults(cls) -> Self:
debug_traceback=False,
enable=[],
clean_cache=False,
stage="all",
)


Expand Down
6 changes: 5 additions & 1 deletion cibuildwheel/platforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from cibuildwheel import errors
from cibuildwheel.platforms import android, ios, linux, macos, pyodide, windows
from cibuildwheel.platforms._run import ALL_STAGES

TYPE_CHECKING = False
if TYPE_CHECKING:
Expand All @@ -14,6 +15,7 @@

from cibuildwheel.architecture import Architecture
from cibuildwheel.options import Options
from cibuildwheel.platforms._run import Stage
from cibuildwheel.selector import BuildSelector
from cibuildwheel.typing import GenericPythonConfiguration, PlatformName

Expand All @@ -27,7 +29,9 @@ def get_python_configurations(
self, build_selector: BuildSelector, architectures: set[Architecture]
) -> Sequence[GenericPythonConfiguration]: ...

def build(self, options: Options, tmp_path: Path) -> None: ...
def build(
self, options: Options, tmp_path: Path, stages: frozenset[Stage] = ALL_STAGES
) -> None: ...


ALL_PLATFORM_MODULES: Final[dict[PlatformName, PlatformModule]] = {
Expand Down
9 changes: 6 additions & 3 deletions cibuildwheel/platforms/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from cibuildwheel.audit import run_audit
from cibuildwheel.logger import log
from cibuildwheel.util.file import move_file
from cibuildwheel.util.packaging import find_compatible_wheel
from cibuildwheel.util.packaging import find_built_wheel, find_compatible_wheel

TYPE_CHECKING = False
if TYPE_CHECKING:
Expand Down Expand Up @@ -82,9 +82,12 @@ def find_prebuilt_wheel(output_dir: Path, identifier: str) -> Path:
Used by the test-only stage, which consumes wheels produced by an earlier
build-only stage rather than building them itself.
"""
wheel = find_compatible_wheel(sorted(output_dir.glob("*.whl")), identifier)
wheel = find_built_wheel(sorted(output_dir.glob("*.whl")), identifier)
if wheel is None:
msg = f"No pre-built wheel for {identifier!r} found in {output_dir}"
msg = (
f"No pre-built wheel for {identifier!r} found in {output_dir}. "
"Run the build stage first, or check --output-dir."
)
raise errors.FatalError(msg)
return wheel

Expand Down
7 changes: 4 additions & 3 deletions cibuildwheel/platforms/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
prepare_config_settings,
)
from cibuildwheel.logger import log
from cibuildwheel.platforms._run import run_host_build
from cibuildwheel.platforms._run import ALL_STAGES, run_host_build
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import (
Expand All @@ -44,6 +44,7 @@
from collections.abc import Sequence

from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.platforms._run import Stage
from cibuildwheel.selector import BuildSelector
from cibuildwheel.typing import PathOrStr

Expand Down Expand Up @@ -121,15 +122,15 @@ class BuildState:
android_env: dict[str, str]


def build(options: Options, tmp_path: Path) -> None:
def build(options: Options, tmp_path: Path, stages: frozenset[Stage] = ALL_STAGES) -> None:
if "ANDROID_HOME" not in os.environ:
msg = (
"ANDROID_HOME environment variable is not set. For instructions, see "
"https://cibuildwheel.pypa.io/en/stable/platforms/#android"
)
raise errors.FatalError(msg)

run_host_build(platforms.android, options, tmp_path)
run_host_build(platforms.android, options, tmp_path, stages=stages)


def setup(config: PythonConfiguration, options: Options, tmp_path: Path) -> BuildState:
Expand Down
7 changes: 4 additions & 3 deletions cibuildwheel/platforms/ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
prepare_config_settings,
)
from cibuildwheel.logger import log
from cibuildwheel.platforms._run import run_host_build
from cibuildwheel.platforms._run import ALL_STAGES, run_host_build
from cibuildwheel.platforms.macos import install_cpython as install_build_cpython
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell, split_command
Expand All @@ -41,6 +41,7 @@
from cibuildwheel.architecture import Architecture
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.platforms._run import Stage
from cibuildwheel.selector import BuildSelector


Expand Down Expand Up @@ -445,12 +446,12 @@ class BuildState:
env: dict[str, str]


def build(options: Options, tmp_path: Path) -> None:
def build(options: Options, tmp_path: Path, stages: frozenset[Stage] = ALL_STAGES) -> None:
if sys.platform != "darwin":
msg = "iOS binaries can only be built on macOS"
raise errors.FatalError(msg)

run_host_build(platforms.ios, options, tmp_path)
run_host_build(platforms.ios, options, tmp_path, stages=stages)


def before_all(options: Options, python_configurations: Sequence[PythonConfiguration]) -> None:
Expand Down
46 changes: 35 additions & 11 deletions cibuildwheel/platforms/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
from cibuildwheel.frontend import get_build_frontend_extra_flags, prepare_config_settings
from cibuildwheel.logger import log
from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
from cibuildwheel.platforms._run import ALL_STAGES, Stage
from cibuildwheel.util import resources
from cibuildwheel.util.file import copy_test_sources
from cibuildwheel.util.helpers import prepare_command, unwrap
from cibuildwheel.util.packaging import find_compatible_wheel
from cibuildwheel.util.packaging import find_built_wheel, find_compatible_wheel

TYPE_CHECKING = False
if TYPE_CHECKING:
Expand Down Expand Up @@ -177,6 +178,7 @@ def build_in_container(
container_project_path: PurePath,
container_package_dir: PurePath,
local_tmp_dir: Path,
stages: frozenset[Stage] = ALL_STAGES,
) -> None:
container_output_dir = PurePosixPath("/output")

Expand All @@ -188,7 +190,7 @@ def build_in_container(
before_all_options_identifier = platform_configs[0].identifier
before_all_options = options.build_options(before_all_options_identifier)

if before_all_options.before_all:
if Stage.BUILD in stages and before_all_options.before_all:
log.step("Running before_all...")

env = container.get_environment()
Expand Down Expand Up @@ -257,8 +259,24 @@ def build_in_container(
msg = "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."
raise errors.FatalError(msg)

compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel:
compatible_wheel = None
if Stage.BUILD not in stages:
# test-only: bring the already-built wheel into the container
host_wheel = find_built_wheel(
sorted(options.globals.output_dir.glob("*.whl")), config.identifier
)
if host_wheel is None:
msg = (
f"No pre-built wheel for {config.identifier!r} found in "
f"{options.globals.output_dir}. Run the build stage first."
)
raise errors.FatalError(msg)
prebuilt_dir = PurePosixPath("/tmp/cibuildwheel/prebuilt_wheel")
container.call(["rm", "-rf", prebuilt_dir])
container.call(["mkdir", "-p", prebuilt_dir])
container.copy_into(host_wheel, prebuilt_dir / host_wheel.name)
repaired_wheel = prebuilt_dir / host_wheel.name
elif compatible_wheel := find_compatible_wheel(built_wheels, config.identifier):
log.step_end()
print(
f"\nFound previously built wheel {compatible_wheel.name}, that's compatible with {config.identifier}. Skipping build step..."
Expand Down Expand Up @@ -391,7 +409,11 @@ def build_in_container(
finally:
shutil.rmtree(local_abi3audit_dir, ignore_errors=True)

if build_options.test_command and build_options.test_selector(config.identifier):
if (
Stage.TEST in stages
and build_options.test_command
and build_options.test_selector(config.identifier)
):
log.step("Testing wheel...")

# set up a virtual environment to install and test from, to make sure
Expand Down Expand Up @@ -470,21 +492,22 @@ def build_in_container(

# move repaired wheel to output
output_wheel: Path | None = None
if compatible_wheel is None:
if Stage.BUILD in stages and compatible_wheel is None:
container.call(["mkdir", "-p", container_output_dir])
container.call(["mv", repaired_wheel, container_output_dir])
built_wheels.append(container_output_dir / repaired_wheel.name)
output_wheel = options.globals.output_dir / repaired_wheel.name

log.build_end(output_wheel)

log.step("Copying wheels back to host...")
# copy the output back into the host
container.copy_out(container_output_dir, options.globals.output_dir)
log.step_end()
if Stage.BUILD in stages:
log.step("Copying wheels back to host...")
# copy the output back into the host
container.copy_out(container_output_dir, options.globals.output_dir)
log.step_end()


def build(options: Options, tmp_path: Path) -> None:
def build(options: Options, tmp_path: Path, stages: frozenset[Stage] = ALL_STAGES) -> None:
python_configurations = get_python_configurations(
options.globals.build_selector, options.globals.architectures
)
Expand Down Expand Up @@ -538,6 +561,7 @@ def build(options: Options, tmp_path: Path) -> None:
container_project_path=container_project_path,
container_package_dir=container_package_dir,
local_tmp_dir=tmp_path,
stages=stages,
)

except subprocess.CalledProcessError as error:
Expand Down
7 changes: 4 additions & 3 deletions cibuildwheel/platforms/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
prepare_config_settings,
)
from cibuildwheel.logger import log
from cibuildwheel.platforms._run import run_host_build
from cibuildwheel.platforms._run import ALL_STAGES, run_host_build
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import (
Expand All @@ -44,6 +44,7 @@
from cibuildwheel.architecture import Architecture
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.platforms._run import Stage
from cibuildwheel.selector import BuildSelector


Expand Down Expand Up @@ -441,8 +442,8 @@ class BuildState:
pip_version: str | None


def build(options: Options, tmp_path: Path) -> None:
run_host_build(platforms.macos, options, tmp_path)
def build(options: Options, tmp_path: Path, stages: frozenset[Stage] = ALL_STAGES) -> None:
run_host_build(platforms.macos, options, tmp_path, stages=stages)


def before_all(options: Options, python_configurations: Sequence[PythonConfiguration]) -> None:
Expand Down
7 changes: 4 additions & 3 deletions cibuildwheel/platforms/pyodide.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from cibuildwheel.architecture import Architecture
from cibuildwheel.frontend import get_build_frontend_extra_flags, prepare_config_settings
from cibuildwheel.logger import log
from cibuildwheel.platforms._run import run_host_build
from cibuildwheel.platforms._run import ALL_STAGES, run_host_build
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import (
Expand All @@ -43,6 +43,7 @@

from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.platforms._run import Stage
from cibuildwheel.selector import BuildSelector

IS_WIN: Final[bool] = sys.platform.startswith("win")
Expand Down Expand Up @@ -352,8 +353,8 @@ class BuildState:
pip_version: str


def build(options: Options, tmp_path: Path) -> None:
run_host_build(platforms.pyodide, options, tmp_path)
def build(options: Options, tmp_path: Path, stages: frozenset[Stage] = ALL_STAGES) -> None:
run_host_build(platforms.pyodide, options, tmp_path, stages=stages)


def before_all(options: Options, python_configurations: Sequence[PythonConfiguration]) -> None:
Expand Down
7 changes: 4 additions & 3 deletions cibuildwheel/platforms/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
prepare_config_settings,
)
from cibuildwheel.logger import log
from cibuildwheel.platforms._run import run_host_build
from cibuildwheel.platforms._run import ALL_STAGES, run_host_build
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import (
Expand All @@ -40,6 +40,7 @@

from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.platforms._run import Stage
from cibuildwheel.selector import BuildSelector


Expand Down Expand Up @@ -437,8 +438,8 @@ class BuildState:
pip_version: str | None


def build(options: Options, tmp_path: Path) -> None:
run_host_build(platforms.windows, options, tmp_path)
def build(options: Options, tmp_path: Path, stages: frozenset[Stage] = ALL_STAGES) -> None:
run_host_build(platforms.windows, options, tmp_path, stages=stages)


def before_all(options: Options, python_configurations: Sequence[PythonConfiguration]) -> None:
Expand Down
Loading
Loading