Repository: pypa/cibuildwheel

Issue:
Move the default for PyPy to manylinux2014
@mattip suggested moving to manylinux2014, since pypy is looking at dropping manylinux2010 in the future. cibuildwheel 2.3 (which will add pypy3.8) might be a good place to do it.


Discussion:
One thought; when do we plan to move from manylinux2010 to manylinux2014 as the default for everything? manylinux2014 is already a much better choice (#901) for Python 3.10, NumPy doesn't even bother providing 2010 wheels for it, and that would avoid two docker launches if someone left the default alone. I think most users pick the manylinux image they want to use, anyway; usually because they want manylinux1.
@joerick (and @mayeut and @YannickJadoul if you have any opinions here) what do you think? Should we bump PyPy to 2014 by default? And/or all Pythons? Due to the fact that 2014 is the best choice for Python 3.10, I'm mildly in favor of bumping all defaults to 2014.
I don't have a strong opinion on this, so I'd defer to @mattip - happy to bump the PyPy default to 2014.

> One thought; when do we plan to move from manylinux2010 to manylinux2014 as the default for everything?

An excellent question, for which I don't have a firm answer on! I think it's fairly reasonable to lag here, since most packagers will normally want to build on the oldest (most compatible) image that works for their package (assuming the image is still maintained/secure).

With the default being something old, either they'll succeed on the first build, which gives them the most compatible wheel, or they'll keep trying newer and newer images until it works.

Do we fully understand the nature of the 3.10 problems you mention? I've [not had any trouble](https://github.com/joerick/pyinstrument/runs/4203588010?check_suite_focus=true) building 3.10 wheels on manylinux2010.

I think #901 was hitting an issue with manylinux2010, but mostly, since any Pip that supports Python 3.10 is new enough to understand manylinux2014, there's not much reason in using a past EOL distribution to build images. NumPy does not provide manylinux2010 wheels, only 2014, so anyone using NumPy will have to try to build NumPy from scratch, etc. If someone doesn't know what they are doing, they are more likely to hit issues with manylinux2010 than 2014 for Python 3.10. 
Pinning 3.10 to manylinux2014 will take a bit of refactoring, since currently the [pinned_docker_images.cfg](https://github.com/pypa/cibuildwheel/blob/main/cibuildwheel/resources/pinned_docker_images.cfg) only relates to platform, not to particular combinations of platform,python_version.

I think we should just pin everything to 2014, simpler for consistency and users, and anyone wanting 2010 would just manually pin it, just like they do already with manylinux1. The slice of users serviced by 2010 is really pretty small; most users are stuck on pip 9 (manylinux1) or they have a pip from the last few years and are fine with manylinux2014. See the orange slices in https://github.com/pypa/manylinux/issues/994#issuecomment-792210874 - Python 3.7 is the only one with a significant number of manylinux2010 as a maximum (3.6 is stuck with Pip 9 more often). The orange is always less than manylinux1, so it's less harsh than the manylinux1 -> manylinux2010 that we already default to.
Another way to see it, https://mayeut.github.io/manylinux-timeline/ under "Policy readiness for python 3.*" plots. There's a small sliver for 2010 for 3.6, a wider but shrinking sliver for 2010 in 3.7, and it's simply too small to really see on any newer version.

> With the default being something old, either they'll succeed on the first build, which gives them the most compatible wheel, or they'll keep trying newer and newer images until it works.

This is not how we handle our current selection; we've been on manylinux2010 for years; by this logic, we should set manylinux1 (and last I checked, 70% of our packages did exactly this). I think the logic is (and should be) we select a recommended default that a "basic" user should be happy with, and allow an advanced user to select whatever they want - if they really need extra compatibility, they can set manylinux2010 (or manylinux1, as they do now). A small, "normal" package should be happy with manylinux2014 (at least if they were with manylinux2010) - and manylinux2010 is based on a past-EOL operating system.

The reason for manylinux2014 is that it's now a significantly better choice for Python 3.10, but also, it's consistent - aarch64 and family all use 2014 already, since that's the earliest version that supported them, and now PyPy will be 2014 too; I think it's easier to explain if we just say 2014 is the default across the board. We even save a docker launch if pypy and cpython match.

Resolving patch:
diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py
--- a/cibuildwheel/options.py
+++ b/cibuildwheel/options.py
@@ -469,10 +469,8 @@ def build_options(self, identifier: Optional[str]) -> BuildOptions:
                     )
 
                     if not config_value:
-                        # default to manylinux2010 if it's available, otherwise manylinux2014
-                        image = pinned_images.get("manylinux2010") or pinned_images.get(
-                            "manylinux2014"
-                        )
+                        # default to manylinux2014
+                        image = pinned_images.get("manylinux2014")
                     elif config_value in pinned_images:
                         image = pinned_images[config_value]
                     else:


Test patch:
diff --git a/test/test_docker_images.py b/test/test_docker_images.py
--- a/test/test_docker_images.py
+++ b/test/test_docker_images.py
@@ -35,8 +35,8 @@ def test(tmp_path):
     actual_wheels = utils.cibuildwheel_run(
         project_dir,
         add_env={
-            "CIBW_MANYLINUX_X86_64_IMAGE": "dockcross/manylinux2010-x64",
-            "CIBW_MANYLINUX_I686_IMAGE": "dockcross/manylinux2010-x86",
+            "CIBW_MANYLINUX_X86_64_IMAGE": "dockcross/manylinux2014-x64",
+            "CIBW_MANYLINUX_I686_IMAGE": "dockcross/manylinux2014-x86",
             "CIBW_BUILD": "cp3{6,7,8,9}-manylinux*",
         },
     )
diff --git a/test/utils.py b/test/utils.py
--- a/test/utils.py
+++ b/test/utils.py
@@ -120,7 +120,12 @@ def expected_wheels(
 
     if manylinux_versions is None:
         if machine_arch == "x86_64":
-            manylinux_versions = ["manylinux_2_5", "manylinux1", "manylinux_2_12", "manylinux2010"]
+            manylinux_versions = [
+                "manylinux_2_5",
+                "manylinux1",
+                "manylinux_2_17",
+                "manylinux2014",
+            ]
         else:
             manylinux_versions = ["manylinux_2_17", "manylinux2014"]
 
diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py
--- a/unit_test/main_tests/main_options_test.py
+++ b/unit_test/main_tests/main_options_test.py
@@ -71,19 +71,19 @@ def test_empty_selector(platform, intercepted_build_args, monkeypatch):
 @pytest.mark.parametrize(
     "architecture, image, full_image",
     [
-        ("x86_64", None, "quay.io/pypa/manylinux2010_x86_64:*"),
+        ("x86_64", None, "quay.io/pypa/manylinux2014_x86_64:*"),
         ("x86_64", "manylinux1", "quay.io/pypa/manylinux1_x86_64:*"),
         ("x86_64", "manylinux2010", "quay.io/pypa/manylinux2010_x86_64:*"),
         ("x86_64", "manylinux2014", "quay.io/pypa/manylinux2014_x86_64:*"),
         ("x86_64", "manylinux_2_24", "quay.io/pypa/manylinux_2_24_x86_64:*"),
         ("x86_64", "custom_image", "custom_image"),
-        ("i686", None, "quay.io/pypa/manylinux2010_i686:*"),
+        ("i686", None, "quay.io/pypa/manylinux2014_i686:*"),
         ("i686", "manylinux1", "quay.io/pypa/manylinux1_i686:*"),
         ("i686", "manylinux2010", "quay.io/pypa/manylinux2010_i686:*"),
         ("i686", "manylinux2014", "quay.io/pypa/manylinux2014_i686:*"),
         ("i686", "manylinux_2_24", "quay.io/pypa/manylinux_2_24_i686:*"),
         ("i686", "custom_image", "custom_image"),
-        ("pypy_x86_64", None, "quay.io/pypa/manylinux2010_x86_64:*"),
+        ("pypy_x86_64", None, "quay.io/pypa/manylinux2014_x86_64:*"),
         ("pypy_x86_64", "manylinux1", "manylinux1"),  # Does not exist
         ("pypy_x86_64", "manylinux2010", "quay.io/pypa/manylinux2010_x86_64:*"),
         ("pypy_x86_64", "manylinux2014", "quay.io/pypa/manylinux2014_x86_64:*"),
diff --git a/unit_test/option_prepare_test.py b/unit_test/option_prepare_test.py
--- a/unit_test/option_prepare_test.py
+++ b/unit_test/option_prepare_test.py
@@ -52,7 +52,7 @@ def test_build_default_launches(mock_build_docker, fake_package_dir, monkeypatch
 
     # In Python 3.8+, this can be simplified to [0].kwargs
     kwargs = build_on_docker.call_args_list[0][1]
-    assert "quay.io/pypa/manylinux2010_x86_64" in kwargs["docker"]["docker_image"]
+    assert "quay.io/pypa/manylinux2014_x86_64" in kwargs["docker"]["docker_image"]
     assert kwargs["docker"]["cwd"] == Path("/project")
     assert not kwargs["docker"]["simulate_32_bit"]
 
@@ -60,7 +60,7 @@ def test_build_default_launches(mock_build_docker, fake_package_dir, monkeypatch
     assert identifiers == {f"{x}-manylinux_x86_64" for x in ALL_IDS}
 
     kwargs = build_on_docker.call_args_list[1][1]
-    assert "quay.io/pypa/manylinux2010_i686" in kwargs["docker"]["docker_image"]
+    assert "quay.io/pypa/manylinux2014_i686" in kwargs["docker"]["docker_image"]
     assert kwargs["docker"]["cwd"] == Path("/project")
     assert kwargs["docker"]["simulate_32_bit"]
 
@@ -94,13 +94,13 @@ def test_build_with_override_launches(mock_build_docker, monkeypatch, tmp_path):
     cibw_toml.write_text(
         """
 [tool.cibuildwheel]
-manylinux-x86_64-image = "manylinux2014"
+manylinux-x86_64-image = "manylinux_2_24"
 
-# Before Python 3.10, manylinux2010 is the most compatible
+# Before Python 3.10, use manylinux2014
 [[tool.cibuildwheel.overrides]]
 select = "cp3?-*"
-manylinux-x86_64-image = "manylinux2010"
-manylinux-i686-image = "manylinux2010"
+manylinux-x86_64-image = "manylinux2014"
+manylinux-i686-image = "manylinux2014"
 
 [[tool.cibuildwheel.overrides]]
 select = "cp36-manylinux_x86_64"
@@ -118,7 +118,7 @@ def test_build_with_override_launches(mock_build_docker, monkeypatch, tmp_path):
     assert build_on_docker.call_count == 6
 
     kwargs = build_on_docker.call_args_list[0][1]
-    assert "quay.io/pypa/manylinux2010_x86_64" in kwargs["docker"]["docker_image"]
+    assert "quay.io/pypa/manylinux2014_x86_64" in kwargs["docker"]["docker_image"]
     assert kwargs["docker"]["cwd"] == Path("/project")
     assert not kwargs["docker"]["simulate_32_bit"]
 
@@ -127,7 +127,7 @@ def test_build_with_override_launches(mock_build_docker, monkeypatch, tmp_path):
     assert kwargs["options"].build_options("cp36-manylinux_x86_64").before_all == "true"
 
     kwargs = build_on_docker.call_args_list[1][1]
-    assert "quay.io/pypa/manylinux2010_x86_64" in kwargs["docker"]["docker_image"]
+    assert "quay.io/pypa/manylinux2014_x86_64" in kwargs["docker"]["docker_image"]
     assert kwargs["docker"]["cwd"] == Path("/project")
     assert not kwargs["docker"]["simulate_32_bit"]
 
@@ -138,7 +138,7 @@ def test_build_with_override_launches(mock_build_docker, monkeypatch, tmp_path):
     assert kwargs["options"].build_options("cp37-manylinux_x86_64").before_all == ""
 
     kwargs = build_on_docker.call_args_list[2][1]
-    assert "quay.io/pypa/manylinux2014_x86_64" in kwargs["docker"]["docker_image"]
+    assert "quay.io/pypa/manylinux_2_24_x86_64" in kwargs["docker"]["docker_image"]
     assert kwargs["docker"]["cwd"] == Path("/project")
     assert not kwargs["docker"]["simulate_32_bit"]
     identifiers = {x.identifier for x in kwargs["platform_configs"]}
@@ -149,7 +149,7 @@ def test_build_with_override_launches(mock_build_docker, monkeypatch, tmp_path):
     }
 
     kwargs = build_on_docker.call_args_list[3][1]
-    assert "quay.io/pypa/manylinux2010_i686" in kwargs["docker"]["docker_image"]
+    assert "quay.io/pypa/manylinux2014_i686" in kwargs["docker"]["docker_image"]
     assert kwargs["docker"]["cwd"] == Path("/project")
     assert kwargs["docker"]["simulate_32_bit"]
 
diff --git a/unit_test/options_toml_test.py b/unit_test/options_toml_test.py
--- a/unit_test/options_toml_test.py
+++ b/unit_test/options_toml_test.py
@@ -60,7 +60,7 @@ def test_simple_settings(tmp_path, platform, fname):
     )
 
     assert options_reader.get("manylinux-x86_64-image") == "manylinux1"
-    assert options_reader.get("manylinux-i686-image") == "manylinux2010"
+    assert options_reader.get("manylinux-i686-image") == "manylinux2014"
 
     with pytest.raises(ConfigOptionError):
         options_reader.get("environment", sep=" ")
@@ -71,7 +71,7 @@ def test_simple_settings(tmp_path, platform, fname):
 
 def test_envvar_override(tmp_path, platform, monkeypatch):
     monkeypatch.setenv("CIBW_BUILD", "cp38*")
-    monkeypatch.setenv("CIBW_MANYLINUX_X86_64_IMAGE", "manylinux2014")
+    monkeypatch.setenv("CIBW_MANYLINUX_X86_64_IMAGE", "manylinux_2_24")
     monkeypatch.setenv("CIBW_TEST_COMMAND", "mytest")
     monkeypatch.setenv("CIBW_TEST_REQUIRES", "docs")
     monkeypatch.setenv("CIBW_TEST_REQUIRES_LINUX", "scod")
@@ -84,8 +84,8 @@ def test_envvar_override(tmp_path, platform, monkeypatch):
     assert options_reader.get("archs", sep=" ") == "auto"
 
     assert options_reader.get("build", sep=" ") == "cp38*"
-    assert options_reader.get("manylinux-x86_64-image") == "manylinux2014"
-    assert options_reader.get("manylinux-i686-image") == "manylinux2010"
+    assert options_reader.get("manylinux-x86_64-image") == "manylinux_2_24"
+    assert options_reader.get("manylinux-i686-image") == "manylinux2014"
 
     assert (
         options_reader.get("test-requires", sep=" ")
@@ -222,7 +222,7 @@ def test_environment_override_empty(tmp_path, monkeypatch):
     assert options_reader.get("manylinux-i686-image") == ""
     assert options_reader.get("manylinux-aarch64-image") == "manylinux1"
 
-    assert options_reader.get("manylinux-x86_64-image", ignore_empty=True) == "manylinux2010"
+    assert options_reader.get("manylinux-x86_64-image", ignore_empty=True) == "manylinux2014"
     assert options_reader.get("manylinux-i686-image", ignore_empty=True) == "manylinux1"
     assert options_reader.get("manylinux-aarch64-image", ignore_empty=True) == "manylinux1"