feat: add --only (#1098)
* fix(style): use sub recommended replacement method See https://docs.python.org/3/library/re.html#re.escape Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * feat: add --build * refactor: switch to using --only Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * docs: cover --only Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * docs: undo change to --plat for auditwheel Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * (unrelated) speed up running the unit tests based on a profile run, this speeds up the unit tests by 3x * Remove `intercepted_build_args` reliance on `platform` fixture * Add test that checks if 'only' overrides env var options * Ensure that the --only command line option overrides env vars * Fix UnboundLocalError * Test cleanups * style: typing.cast instead of plain cast * feat: add only: to the GHA Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Joe Rickerby <joerick@mac.com>
This commit is contained in:
co-authored by
Joe Rickerby
parent
3a46bde3fb
commit
eb39da0b10
@@ -13,6 +13,10 @@ inputs:
|
||||
description: 'File containing the config, defaults to {package}/pyproject.toml'
|
||||
required: false
|
||||
default: ''
|
||||
only:
|
||||
description: 'Build a specific wheel only. No need for arch/platform if this is set'
|
||||
required: false
|
||||
default: ''
|
||||
branding:
|
||||
icon: package
|
||||
color: yellow
|
||||
@@ -36,5 +40,6 @@ runs:
|
||||
${{ inputs.package-dir }}
|
||||
--output-dir ${{ inputs.output-dir }}
|
||||
--config-file "${{ inputs.config-file }}"
|
||||
--only "${{ inputs.only }}"
|
||||
2>&1
|
||||
shell: bash
|
||||
|
||||
@@ -6,6 +6,7 @@ import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import textwrap
|
||||
import typing
|
||||
from pathlib import Path
|
||||
from tempfile import mkdtemp
|
||||
|
||||
@@ -40,7 +41,7 @@ def main() -> None:
|
||||
parser.add_argument(
|
||||
"--platform",
|
||||
choices=["auto", "linux", "macos", "windows"],
|
||||
default=os.environ.get("CIBW_PLATFORM", "auto"),
|
||||
default=None,
|
||||
help="""
|
||||
Platform to build for. Use this option to override the
|
||||
auto-detected platform or to run cibuildwheel on your development
|
||||
@@ -64,6 +65,16 @@ def main() -> None:
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--only",
|
||||
default=None,
|
||||
help="""
|
||||
Force a single wheel build when given an identifier. Overrides
|
||||
CIBW_BUILD/CIBW_SKIP. --platform and --arch cannot be specified
|
||||
if this is given.
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
@@ -154,10 +165,40 @@ def main() -> None:
|
||||
|
||||
|
||||
def build_in_directory(args: CommandLineArguments) -> None:
|
||||
platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "auto")
|
||||
platform: PlatformName
|
||||
|
||||
if args.platform != "auto":
|
||||
platform = args.platform
|
||||
if args.only:
|
||||
if "linux_" in args.only:
|
||||
platform = "linux"
|
||||
elif "macosx_" in args.only:
|
||||
platform = "macos"
|
||||
elif "win_" in args.only:
|
||||
platform = "windows"
|
||||
else:
|
||||
print(
|
||||
f"Invalid --only='{args.only}', must be a build selector with a known platform",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
if args.platform is not None:
|
||||
print(
|
||||
"--platform cannot be specified with --only, it is computed from --only",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
if args.archs is not None:
|
||||
print(
|
||||
"--arch cannot be specified with --only, it is computed from --only",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
elif platform_option_value != "auto":
|
||||
if platform_option_value not in PLATFORMS:
|
||||
print(f"cibuildwheel: Unsupported platform: {platform_option_value}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
platform = typing.cast(PlatformName, platform_option_value)
|
||||
else:
|
||||
ci_provider = detect_ci_provider()
|
||||
if ci_provider is None:
|
||||
@@ -187,10 +228,6 @@ def build_in_directory(args: CommandLineArguments) -> None:
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
if platform not in PLATFORMS:
|
||||
print(f"cibuildwheel: Unsupported platform: {platform}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
options = compute_options(platform=platform, command_line_arguments=args)
|
||||
|
||||
package_dir = options.globals.package_dir
|
||||
|
||||
+15
-5
@@ -42,9 +42,10 @@ from .util import (
|
||||
|
||||
@dataclass
|
||||
class CommandLineArguments:
|
||||
platform: Literal["auto", "linux", "macos", "windows"]
|
||||
platform: Literal["auto", "linux", "macos", "windows"] | None
|
||||
archs: str | None
|
||||
output_dir: Path
|
||||
only: str | None
|
||||
config_file: str
|
||||
package_dir: Path
|
||||
print_build_identifiers: bool
|
||||
@@ -403,6 +404,15 @@ class Options:
|
||||
)
|
||||
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
|
||||
|
||||
archs_config_str = args.archs or self.reader.get("archs", sep=" ")
|
||||
architectures = Architecture.parse_config(archs_config_str, platform=self.platform)
|
||||
|
||||
# Process `--only`
|
||||
if args.only:
|
||||
build_config = args.only
|
||||
skip_config = ""
|
||||
architectures = Architecture.all_archs(self.platform)
|
||||
|
||||
build_selector = BuildSelector(
|
||||
build_config=build_config,
|
||||
skip_config=skip_config,
|
||||
@@ -411,9 +421,6 @@ class Options:
|
||||
)
|
||||
test_selector = TestSelector(skip_config=test_skip)
|
||||
|
||||
archs_config_str = args.archs or self.reader.get("archs", sep=" ")
|
||||
architectures = Architecture.parse_config(archs_config_str, platform=self.platform)
|
||||
|
||||
container_engine_str = self.reader.get("container-engine")
|
||||
|
||||
if container_engine_str not in ["docker", "podman"]:
|
||||
@@ -588,6 +595,9 @@ class Options:
|
||||
]
|
||||
|
||||
build_option_defaults = self.build_options(identifier=None)
|
||||
build_options_for_identifier = {
|
||||
identifier: self.build_options(identifier) for identifier in identifiers
|
||||
}
|
||||
|
||||
for option_name, default_value in sorted(asdict(build_option_defaults).items()):
|
||||
if option_name == "globals":
|
||||
@@ -597,7 +607,7 @@ class Options:
|
||||
|
||||
# if any identifiers have an overridden value, print that too
|
||||
for identifier in identifiers:
|
||||
option_value = getattr(self.build_options(identifier=identifier), option_name)
|
||||
option_value = getattr(build_options_for_identifier[identifier], option_name)
|
||||
if option_value != default_value:
|
||||
lines.append(f" {identifier}: {option_value!r}")
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ def shell(*commands: str, env: dict[str, str] | None = None, cwd: PathOrStr | No
|
||||
subprocess.run(command, env=env, cwd=cwd, shell=True, check=True)
|
||||
|
||||
|
||||
def format_safe(template: str, **kwargs: Any) -> str:
|
||||
def format_safe(template: str, **kwargs: str | os.PathLike[str]) -> str:
|
||||
"""
|
||||
Works similarly to `template.format(**kwargs)`, except that unmatched
|
||||
fields in `template` are passed through untouched.
|
||||
@@ -173,11 +173,9 @@ def format_safe(template: str, **kwargs: Any) -> str:
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
# we use a function for repl to prevent re.sub interpreting backslashes
|
||||
# in repl as escape sequences.
|
||||
result = re.sub(
|
||||
pattern=find_pattern,
|
||||
repl=lambda _: str(value), # pylint: disable=cell-var-from-loop
|
||||
repl=str(value).replace("\\", r"\\"),
|
||||
string=result,
|
||||
)
|
||||
|
||||
|
||||
+5
-1
@@ -113,7 +113,7 @@ The complete set of defaults for the current version of cibuildwheel are shown b
|
||||
|
||||
!!! tip
|
||||
Static configuration works across all CI systems, and can be used locally if
|
||||
you run `cibuildwheel --plat linux`. This is preferred, but environment
|
||||
you run `cibuildwheel --platform linux`. This is preferred, but environment
|
||||
variables are better if you need to change per-matrix element
|
||||
(`CIBW_BUILD` is often in this category, for example), or if you cannot or do
|
||||
not want to change a `pyproject.toml` file. You can specify a different file to
|
||||
@@ -202,6 +202,10 @@ This option can also be set using the [command-line option](#command-line) `--pl
|
||||
|
||||
This is even more convenient if you store your cibuildwheel config in [`pyproject.toml`](#configuration-file).
|
||||
|
||||
You can also run a single identifier with `--only <identifier>`. This will
|
||||
not require `--platform` or `--arch`, and will override any build/skip
|
||||
configuration.
|
||||
|
||||
### `CIBW_BUILD`, `CIBW_SKIP` {: #build-skip}
|
||||
|
||||
> Choose the Python versions to build
|
||||
|
||||
@@ -12,7 +12,13 @@ from cibuildwheel import linux, macos, util, windows
|
||||
|
||||
|
||||
class ArgsInterceptor:
|
||||
def __init__(self):
|
||||
self.call_count = 0
|
||||
self.args = None
|
||||
self.kwargs = None
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.call_count += 1
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
@@ -75,16 +81,14 @@ def platform(request, monkeypatch):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def intercepted_build_args(platform, monkeypatch):
|
||||
def intercepted_build_args(monkeypatch):
|
||||
intercepted = ArgsInterceptor()
|
||||
|
||||
if platform == "linux":
|
||||
monkeypatch.setattr(linux, "build", intercepted)
|
||||
elif platform == "macos":
|
||||
monkeypatch.setattr(macos, "build", intercepted)
|
||||
elif platform == "windows":
|
||||
monkeypatch.setattr(windows, "build", intercepted)
|
||||
else:
|
||||
raise ValueError(f"unknown platform value: {platform}")
|
||||
monkeypatch.setattr(linux, "build", intercepted)
|
||||
monkeypatch.setattr(macos, "build", intercepted)
|
||||
monkeypatch.setattr(windows, "build", intercepted)
|
||||
|
||||
return intercepted
|
||||
yield intercepted
|
||||
|
||||
# check that intercepted_build_args only ever had one set of args
|
||||
assert intercepted.call_count <= 1
|
||||
|
||||
@@ -192,3 +192,73 @@ def test_archs_platform_all(platform, intercepted_build_args, monkeypatch):
|
||||
Architecture.arm64,
|
||||
Architecture.universal2,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"only,plat",
|
||||
(
|
||||
("cp311-manylinux_x86_64", "linux"),
|
||||
("cp310-win_amd64", "windows"),
|
||||
("cp311-macosx_x86_64", "macos"),
|
||||
),
|
||||
)
|
||||
def test_only_argument(intercepted_build_args, monkeypatch, only, plat):
|
||||
monkeypatch.setenv("CIBW_BUILD", "unused")
|
||||
monkeypatch.setenv("CIBW_SKIP", "unused")
|
||||
monkeypatch.setattr(sys, "argv", sys.argv + ["--only", only])
|
||||
|
||||
main()
|
||||
|
||||
options = intercepted_build_args.args[0]
|
||||
assert options.globals.build_selector.build_config == only
|
||||
assert options.globals.build_selector.skip_config == ""
|
||||
assert options.platform == plat
|
||||
assert options.globals.architectures == Architecture.all_archs(plat)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("only", ("cp311-manylxinux_x86_64", "some_linux_thing"))
|
||||
def test_only_failed(monkeypatch, only):
|
||||
monkeypatch.setattr(sys, "argv", sys.argv + ["--only", only])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
main()
|
||||
|
||||
|
||||
def test_only_no_platform(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
sys, "argv", sys.argv + ["--only", "cp311-manylinux_x86_64", "--platform", "macos"]
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
main()
|
||||
|
||||
|
||||
def test_only_no_archs(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
sys, "argv", sys.argv + ["--only", "cp311-manylinux_x86_64", "--archs", "x86_64"]
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
main()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"envvar_name,envvar_value",
|
||||
(
|
||||
("CIBW_BUILD", "cp310-*"),
|
||||
("CIBW_SKIP", "cp311-*"),
|
||||
("CIBW_ARCHS", "auto32"),
|
||||
("CIBW_PLATFORM", "macos"),
|
||||
),
|
||||
)
|
||||
def test_only_overrides_env_vars(monkeypatch, intercepted_build_args, envvar_name, envvar_value):
|
||||
monkeypatch.setattr(sys, "argv", sys.argv + ["--only", "cp311-manylinux_x86_64"])
|
||||
monkeypatch.setenv(envvar_name, envvar_value)
|
||||
|
||||
main()
|
||||
|
||||
options = intercepted_build_args.args[0]
|
||||
assert options.globals.build_selector.build_config == "cp311-manylinux_x86_64"
|
||||
assert options.globals.build_selector.skip_config == ""
|
||||
assert options.platform == "linux"
|
||||
assert options.globals.architectures == Architecture.all_archs("linux")
|
||||
|
||||
@@ -10,6 +10,7 @@ def get_default_command_line_arguments() -> CommandLineArguments:
|
||||
platform="auto",
|
||||
allow_empty=False,
|
||||
archs=None,
|
||||
only=None,
|
||||
config_file="",
|
||||
output_dir=Path("wheelhouse"),
|
||||
package_dir=Path("."),
|
||||
|
||||
Reference in New Issue
Block a user