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'
|
description: 'File containing the config, defaults to {package}/pyproject.toml'
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
|
only:
|
||||||
|
description: 'Build a specific wheel only. No need for arch/platform if this is set'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
branding:
|
branding:
|
||||||
icon: package
|
icon: package
|
||||||
color: yellow
|
color: yellow
|
||||||
@@ -36,5 +40,6 @@ runs:
|
|||||||
${{ inputs.package-dir }}
|
${{ inputs.package-dir }}
|
||||||
--output-dir ${{ inputs.output-dir }}
|
--output-dir ${{ inputs.output-dir }}
|
||||||
--config-file "${{ inputs.config-file }}"
|
--config-file "${{ inputs.config-file }}"
|
||||||
|
--only "${{ inputs.only }}"
|
||||||
2>&1
|
2>&1
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import shutil
|
|||||||
import sys
|
import sys
|
||||||
import tarfile
|
import tarfile
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import typing
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ def main() -> None:
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--platform",
|
"--platform",
|
||||||
choices=["auto", "linux", "macos", "windows"],
|
choices=["auto", "linux", "macos", "windows"],
|
||||||
default=os.environ.get("CIBW_PLATFORM", "auto"),
|
default=None,
|
||||||
help="""
|
help="""
|
||||||
Platform to build for. Use this option to override the
|
Platform to build for. Use this option to override the
|
||||||
auto-detected platform or to run cibuildwheel on your development
|
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(
|
parser.add_argument(
|
||||||
"--output-dir",
|
"--output-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
@@ -154,10 +165,40 @@ def main() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def build_in_directory(args: CommandLineArguments) -> None:
|
def build_in_directory(args: CommandLineArguments) -> None:
|
||||||
|
platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "auto")
|
||||||
platform: PlatformName
|
platform: PlatformName
|
||||||
|
|
||||||
if args.platform != "auto":
|
if args.only:
|
||||||
platform = args.platform
|
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:
|
else:
|
||||||
ci_provider = detect_ci_provider()
|
ci_provider = detect_ci_provider()
|
||||||
if ci_provider is None:
|
if ci_provider is None:
|
||||||
@@ -187,10 +228,6 @@ def build_in_directory(args: CommandLineArguments) -> None:
|
|||||||
)
|
)
|
||||||
sys.exit(2)
|
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)
|
options = compute_options(platform=platform, command_line_arguments=args)
|
||||||
|
|
||||||
package_dir = options.globals.package_dir
|
package_dir = options.globals.package_dir
|
||||||
|
|||||||
+15
-5
@@ -42,9 +42,10 @@ from .util import (
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CommandLineArguments:
|
class CommandLineArguments:
|
||||||
platform: Literal["auto", "linux", "macos", "windows"]
|
platform: Literal["auto", "linux", "macos", "windows"] | None
|
||||||
archs: str | None
|
archs: str | None
|
||||||
output_dir: Path
|
output_dir: Path
|
||||||
|
only: str | None
|
||||||
config_file: str
|
config_file: str
|
||||||
package_dir: Path
|
package_dir: Path
|
||||||
print_build_identifiers: bool
|
print_build_identifiers: bool
|
||||||
@@ -403,6 +404,15 @@ class Options:
|
|||||||
)
|
)
|
||||||
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)
|
||||||
|
|
||||||
|
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_selector = BuildSelector(
|
||||||
build_config=build_config,
|
build_config=build_config,
|
||||||
skip_config=skip_config,
|
skip_config=skip_config,
|
||||||
@@ -411,9 +421,6 @@ class Options:
|
|||||||
)
|
)
|
||||||
test_selector = TestSelector(skip_config=test_skip)
|
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")
|
container_engine_str = self.reader.get("container-engine")
|
||||||
|
|
||||||
if container_engine_str not in ["docker", "podman"]:
|
if container_engine_str not in ["docker", "podman"]:
|
||||||
@@ -588,6 +595,9 @@ class Options:
|
|||||||
]
|
]
|
||||||
|
|
||||||
build_option_defaults = self.build_options(identifier=None)
|
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()):
|
for option_name, default_value in sorted(asdict(build_option_defaults).items()):
|
||||||
if option_name == "globals":
|
if option_name == "globals":
|
||||||
@@ -597,7 +607,7 @@ class Options:
|
|||||||
|
|
||||||
# if any identifiers have an overridden value, print that too
|
# if any identifiers have an overridden value, print that too
|
||||||
for identifier in identifiers:
|
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:
|
if option_value != default_value:
|
||||||
lines.append(f" {identifier}: {option_value!r}")
|
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)
|
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
|
Works similarly to `template.format(**kwargs)`, except that unmatched
|
||||||
fields in `template` are passed through untouched.
|
fields in `template` are passed through untouched.
|
||||||
@@ -173,11 +173,9 @@ def format_safe(template: str, **kwargs: Any) -> str:
|
|||||||
re.VERBOSE,
|
re.VERBOSE,
|
||||||
)
|
)
|
||||||
|
|
||||||
# we use a function for repl to prevent re.sub interpreting backslashes
|
|
||||||
# in repl as escape sequences.
|
|
||||||
result = re.sub(
|
result = re.sub(
|
||||||
pattern=find_pattern,
|
pattern=find_pattern,
|
||||||
repl=lambda _: str(value), # pylint: disable=cell-var-from-loop
|
repl=str(value).replace("\\", r"\\"),
|
||||||
string=result,
|
string=result,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -113,7 +113,7 @@ The complete set of defaults for the current version of cibuildwheel are shown b
|
|||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
Static configuration works across all CI systems, and can be used locally if
|
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
|
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
|
(`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
|
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).
|
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}
|
### `CIBW_BUILD`, `CIBW_SKIP` {: #build-skip}
|
||||||
|
|
||||||
> Choose the Python versions to build
|
> Choose the Python versions to build
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ from cibuildwheel import linux, macos, util, windows
|
|||||||
|
|
||||||
|
|
||||||
class ArgsInterceptor:
|
class ArgsInterceptor:
|
||||||
|
def __init__(self):
|
||||||
|
self.call_count = 0
|
||||||
|
self.args = None
|
||||||
|
self.kwargs = None
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
|
self.call_count += 1
|
||||||
self.args = args
|
self.args = args
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
@@ -75,16 +81,14 @@ def platform(request, monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def intercepted_build_args(platform, monkeypatch):
|
def intercepted_build_args(monkeypatch):
|
||||||
intercepted = ArgsInterceptor()
|
intercepted = ArgsInterceptor()
|
||||||
|
|
||||||
if platform == "linux":
|
monkeypatch.setattr(linux, "build", intercepted)
|
||||||
monkeypatch.setattr(linux, "build", intercepted)
|
monkeypatch.setattr(macos, "build", intercepted)
|
||||||
elif platform == "macos":
|
monkeypatch.setattr(windows, "build", intercepted)
|
||||||
monkeypatch.setattr(macos, "build", intercepted)
|
|
||||||
elif platform == "windows":
|
|
||||||
monkeypatch.setattr(windows, "build", intercepted)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"unknown platform value: {platform}")
|
|
||||||
|
|
||||||
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.arm64,
|
||||||
Architecture.universal2,
|
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",
|
platform="auto",
|
||||||
allow_empty=False,
|
allow_empty=False,
|
||||||
archs=None,
|
archs=None,
|
||||||
|
only=None,
|
||||||
config_file="",
|
config_file="",
|
||||||
output_dir=Path("wheelhouse"),
|
output_dir=Path("wheelhouse"),
|
||||||
package_dir=Path("."),
|
package_dir=Path("."),
|
||||||
|
|||||||
Reference in New Issue
Block a user