diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index f22304c05..24008cdf6 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -306,18 +306,27 @@ def build(options: BuildOptions) -> None: log.step_end_with_error( f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}" ) - troubleshoot(options.package_dir, error) + troubleshoot(options, error) sys.exit(1) -def troubleshoot(package_dir: Path, error: Exception) -> None: +def _matches_prepared_command(error_cmd: List[str], command_template: str) -> bool: + if len(error_cmd) < 3 or error_cmd[0:2] != ["sh", "-c"]: + return False + command_prefix = command_template.split("{", maxsplit=1)[0].strip() + return error_cmd[2].startswith(command_prefix) + + +def troubleshoot(options: BuildOptions, error: Exception) -> None: + if isinstance(error, subprocess.CalledProcessError) and ( error.cmd[0:4] == ["python", "-m", "pip", "wheel"] or error.cmd[0:3] == ["python", "-m", "build"] + or _matches_prepared_command(error.cmd, options.repair_command) ): # the wheel build step failed print("Checking for common errors...") - so_files = list(package_dir.glob("**/*.so")) + so_files = list(options.package_dir.glob("**/*.so")) if so_files: print( @@ -326,10 +335,17 @@ def troubleshoot(package_dir: Path, error: Exception) -> None: NOTE: Shared object (.so) files found in this project. These files might be built against the wrong OS, causing problems with - auditwheel. + auditwheel. If possible, run cibuildwheel in a clean checkout. If you're using Cython and have previously done an in-place build, remove those build files (*.so and *.c) before starting cibuildwheel. + + setuptools uses the build/ folder to store its build cache. It + may be necessary to remove those build files (*.so and *.o) before + starting cibuildwheel. + + Files that belong to a virtual environment are probably not an issue + unless you used a custom command telling cibuildwheel to activate it. """ ), file=sys.stderr, diff --git a/test/test_troubleshooting.py b/test/test_troubleshooting.py index 6a58ca1fd..7039c2687 100644 --- a/test/test_troubleshooting.py +++ b/test/test_troubleshooting.py @@ -3,25 +3,23 @@ import pytest from . import utils -from .test_projects import TestProject +from .test_projects import TestProject, new_c_project -so_file_project = TestProject() +SO_FILE_WARNING = "NOTE: Shared object (.so) files found in this project." -so_file_project.files["libnothing.so"] = "" -so_file_project.files[ - "setup.py" -] = """ -raise Exception('this build will fail') -""" +@pytest.mark.parametrize("project_contains_so_files", [False, True]) +def test_failed_build_with_so_files(tmp_path, capfd, build_frontend_env, project_contains_so_files): + project = TestProject() + project.files["setup.py"] = "raise Exception('this build will fail')\n" + if project_contains_so_files: + project.files["libnothing.so"] = "" - -def test_failed_project_with_so_files(tmp_path, capfd, build_frontend_env): if utils.platform != "linux": pytest.skip("this test is only relevant to the linux build") project_dir = tmp_path / "project" - so_file_project.generate(project_dir) + project.generate(project_dir) with pytest.raises(subprocess.CalledProcessError): utils.cibuildwheel_run(project_dir, add_env=build_frontend_env) @@ -29,4 +27,34 @@ def test_failed_project_with_so_files(tmp_path, capfd, build_frontend_env): captured = capfd.readouterr() print("out", captured.out) print("err", captured.err) - assert "NOTE: Shared object (.so) files found in this project." in captured.err + + if project_contains_so_files: + assert SO_FILE_WARNING in captured.err + else: + assert SO_FILE_WARNING not in captured.err + + +@pytest.mark.parametrize("project_contains_so_files", [False, True]) +def test_failed_repair_with_so_files(tmp_path, capfd, project_contains_so_files): + if utils.platform != "linux": + pytest.skip("this test is only relevant to the linux build") + + project = new_c_project() + + if project_contains_so_files: + project.files["libnothing.so"] = "" + + project_dir = tmp_path / "project" + project.generate(project_dir) + + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run(project_dir, add_env={"CIBW_REPAIR_WHEEL_COMMAND": "false"}) + + captured = capfd.readouterr() + print("out", captured.out) + print("err", captured.err) + + if project_contains_so_files: + assert SO_FILE_WARNING in captured.err + else: + assert SO_FILE_WARNING not in captured.err