prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  setup.py setup() not detected in __name__ == '__main__' block
  ### Description
  
  My setup.py setup() includes:
  
      python_requires=">=3.8"
  
  However cibuildwheel still tries and fails to compile under Python 3.6.
  
  I understand there is [CIBW_BUILD / CIBW_SKIP](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) but that is then duplicating the python requires information.
  
  I can add a \[project\] section to pyproject.toml but that leads to a lot of problems because it ends up fighting with setup() parameters and they **really** don't like it.
  
  I believe cibuildwheel should establish the Python version support automatically whether it comes from setuptools or pyproject.toml, and not try to build on unsupported versions.  My [pyproject.toml](https://github.com/rogerbinns/apsw/blob/master/pyproject.toml) is:
  
  ````
  [build-system]
  requires = ["setuptools"]
  build-backend = "setuptools.build_meta"
  ````
  
  
  ### Build log
  
  https://github.com/rogerbinns/apsw/actions/runs/6175182758/job/16761477543
  
  ### CI config
  
  https://github.com/rogerbinns/apsw/actions/runs/6175182758/workflow
  
  </issue>
  <code>
  [start of cibuildwheel/projectfiles.py]
  1 from __future__ import annotations
  2 
  3 import ast
  4 import configparser
  5 import contextlib
  6 from pathlib import Path
  7 
  8 from ._compat import tomllib
  9 
  10 
  11 class Analyzer(ast.NodeVisitor):
  12     def __init__(self) -> None:
  13         self.requires_python: str | None = None
  14 
  15     def visit(self, node: ast.AST) -> None:
  16         for inner_node in ast.walk(node):
  17             for child in ast.iter_child_nodes(inner_node):
  18                 child.parent = inner_node  # type: ignore[attr-defined]
  19         super().visit(node)
  20 
  21     def visit_keyword(self, node: ast.keyword) -> None:
  22         self.generic_visit(node)
  23         # Must not be nested in an if or other structure
  24         # This will be Module -> Expr -> Call -> keyword
  25         if (
  26             node.arg == "python_requires"
  27             and not hasattr(node.parent.parent.parent, "parent")  # type: ignore[attr-defined]
  28             and isinstance(node.value, ast.Constant)
  29         ):
  30             self.requires_python = node.value.value
  31 
  32 
  33 def setup_py_python_requires(content: str) -> str | None:
  34     try:
  35         tree = ast.parse(content)
  36         analyzer = Analyzer()
  37         analyzer.visit(tree)
  38         return analyzer.requires_python or None
  39     except Exception:  # pylint: disable=broad-except
  40         return None
  41 
  42 
  43 def get_requires_python_str(package_dir: Path) -> str | None:
  44     """Return the python requires string from the most canonical source available, or None"""
  45 
  46     # Read in from pyproject.toml:project.requires-python
  47     with contextlib.suppress(FileNotFoundError):
  48         with (package_dir / "pyproject.toml").open("rb") as f1:
  49             info = tomllib.load(f1)
  50         with contextlib.suppress(KeyError, IndexError, TypeError):
  51             return str(info["project"]["requires-python"])
  52 
  53     # Read in from setup.cfg:options.python_requires
  54     config = configparser.ConfigParser()
  55     with contextlib.suppress(FileNotFoundError):
  56         config.read(package_dir / "setup.cfg")
  57         with contextlib.suppress(KeyError, IndexError, TypeError):
  58             return str(config["options"]["python_requires"])
  59 
  60     setup_py = package_dir / "setup.py"
  61     with contextlib.suppress(FileNotFoundError), setup_py.open(encoding="utf8") as f2:
  62         return setup_py_python_requires(f2.read())
  63 
  64     return None
  65
  [end of cibuildwheel/projectfiles.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py
  --- a/cibuildwheel/projectfiles.py
  +++ b/cibuildwheel/projectfiles.py
  @@ -8,6 +8,43 @@
   from ._compat import tomllib
   
   
  +def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None:
  +    for _ in range(depth):
  +        node = getattr(node, "parent", None)
  +    return node
  +
  +
  +def is_main(parent: ast.AST | None) -> bool:
  +    if parent is None:
  +        return False
  +
  +    # This would be much nicer with 3.10's pattern matching!
  +    if not isinstance(parent, ast.If):
  +        return False
  +    if not isinstance(parent.test, ast.Compare):
  +        return False
  +
  +    try:
  +        (op,) = parent.test.ops
  +        (comp,) = parent.test.comparators
  +    except ValueError:
  +        return False
  +
  +    if not isinstance(op, ast.Eq):
  +        return False
  +
  +    values = {comp, parent.test.left}
  +
  +    mains = {x for x in values if isinstance(x, ast.Constant) and x.value == "__main__"}
  +    if len(mains) != 1:
  +        return False
  +    consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"}
  +    if len(consts) != 1:
  +        return False
  +
  +    return True
  +
  +
   class Analyzer(ast.NodeVisitor):
       def __init__(self) -> None:
           self.requires_python: str | None = None
  @@ -19,13 +56,22 @@
           super().visit(node)
   
       def visit_keyword(self, node: ast.keyword) -> None:
  +        # Must not be nested except for if __name__ == "__main__"
  +
           self.generic_visit(node)
  -        # Must not be nested in an if or other structure
           # This will be Module -> Expr -> Call -> keyword
  +        parent = get_parent(node, 4)
  +        unnested = parent is None
  +
  +        # This will be Module -> If -> Expr -> Call -> keyword
  +        name_main_unnested = (
  +            parent is not None and get_parent(parent) is None and is_main(get_parent(node, 3))
  +        )
  +
           if (
               node.arg == "python_requires"
  -            and not hasattr(node.parent.parent.parent, "parent")  # type: ignore[attr-defined]
               and isinstance(node.value, ast.Constant)
  +            and (unnested or name_main_unnested)
           ):
               self.requires_python = node.value.value
  
verification_info: {"golden_diff": "diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py\n--- a/cibuildwheel/projectfiles.py\n+++ b/cibuildwheel/projectfiles.py\n@@ -8,6 +8,43 @@\n from ._compat import tomllib\n \n \n+def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None:\n+    for _ in range(depth):\n+        node = getattr(node, \"parent\", None)\n+    return node\n+\n+\n+def is_main(parent: ast.AST | None) -> bool:\n+    if parent is None:\n+        return False\n+\n+    # This would be much nicer with 3.10's pattern matching!\n+    if not isinstance(parent, ast.If):\n+        return False\n+    if not isinstance(parent.test, ast.Compare):\n+        return False\n+\n+    try:\n+        (op,) = parent.test.ops\n+        (comp,) = parent.test.comparators\n+    except ValueError:\n+        return False\n+\n+    if not isinstance(op, ast.Eq):\n+        return False\n+\n+    values = {comp, parent.test.left}\n+\n+    mains = {x for x in values if isinstance(x, ast.Constant) and x.value == \"__main__\"}\n+    if len(mains) != 1:\n+        return False\n+    consts = {x for x in values if isinstance(x, ast.Name) and x.id == \"__name__\"}\n+    if len(consts) != 1:\n+        return False\n+\n+    return True\n+\n+\n class Analyzer(ast.NodeVisitor):\n     def __init__(self) -> None:\n         self.requires_python: str | None = None\n@@ -19,13 +56,22 @@\n         super().visit(node)\n \n     def visit_keyword(self, node: ast.keyword) -> None:\n+        # Must not be nested except for if __name__ == \"__main__\"\n+\n         self.generic_visit(node)\n-        # Must not be nested in an if or other structure\n         # This will be Module -> Expr -> Call -> keyword\n+        parent = get_parent(node, 4)\n+        unnested = parent is None\n+\n+        # This will be Module -> If -> Expr -> Call -> keyword\n+        name_main_unnested = (\n+            parent is not None and get_parent(parent) is None and is_main(get_parent(node, 3))\n+        )\n+\n         if (\n             node.arg == \"python_requires\"\n-            and not hasattr(node.parent.parent.parent, \"parent\")  # type: ignore[attr-defined]\n             and isinstance(node.value, ast.Constant)\n+            and (unnested or name_main_unnested)\n         ):\n             self.requires_python = node.value.value\n", "issue": "setup.py setup() not detected in __name__ == '__main__' block\n### Description\n\nMy setup.py setup() includes:\r\n\r\n    python_requires=\">=3.8\"\r\n\r\nHowever cibuildwheel still tries and fails to compile under Python 3.6.\r\n\r\nI understand there is [CIBW_BUILD / CIBW_SKIP](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) but that is then duplicating the python requires information.\r\n\r\nI can add a \\[project\\] section to pyproject.toml but that leads to a lot of problems because it ends up fighting with setup() parameters and they **really** don't like it.\r\n\r\nI believe cibuildwheel should establish the Python version support automatically whether it comes from setuptools or pyproject.toml, and not try to build on unsupported versions.  My [pyproject.toml](https://github.com/rogerbinns/apsw/blob/master/pyproject.toml) is:\r\n\r\n````\r\n[build-system]\r\nrequires = [\"setuptools\"]\r\nbuild-backend = \"setuptools.build_meta\"\r\n````\r\n\n\n### Build log\n\nhttps://github.com/rogerbinns/apsw/actions/runs/6175182758/job/16761477543\n\n### CI config\n\nhttps://github.com/rogerbinns/apsw/actions/runs/6175182758/workflow\n", "before_files": [{"content": "from __future__ import annotations\n\nimport ast\nimport configparser\nimport contextlib\nfrom pathlib import Path\n\nfrom ._compat import tomllib\n\n\nclass Analyzer(ast.NodeVisitor):\n    def __init__(self) -> None:\n        self.requires_python: str | None = None\n\n    def visit(self, node: ast.AST) -> None:\n        for inner_node in ast.walk(node):\n            for child in ast.iter_child_nodes(inner_node):\n                child.parent = inner_node  # type: ignore[attr-defined]\n        super().visit(node)\n\n    def visit_keyword(self, node: ast.keyword) -> None:\n        self.generic_visit(node)\n        # Must not be nested in an if or other structure\n        # This will be Module -> Expr -> Call -> keyword\n        if (\n            node.arg == \"python_requires\"\n            and not hasattr(node.parent.parent.parent, \"parent\")  # type: ignore[attr-defined]\n            and isinstance(node.value, ast.Constant)\n        ):\n            self.requires_python = node.value.value\n\n\ndef setup_py_python_requires(content: str) -> str | None:\n    try:\n        tree = ast.parse(content)\n        analyzer = Analyzer()\n        analyzer.visit(tree)\n        return analyzer.requires_python or None\n    except Exception:  # pylint: disable=broad-except\n        return None\n\n\ndef get_requires_python_str(package_dir: Path) -> str | None:\n    \"\"\"Return the python requires string from the most canonical source available, or None\"\"\"\n\n    # Read in from pyproject.toml:project.requires-python\n    with contextlib.suppress(FileNotFoundError):\n        with (package_dir / \"pyproject.toml\").open(\"rb\") as f1:\n            info = tomllib.load(f1)\n        with contextlib.suppress(KeyError, IndexError, TypeError):\n            return str(info[\"project\"][\"requires-python\"])\n\n    # Read in from setup.cfg:options.python_requires\n    config = configparser.ConfigParser()\n    with contextlib.suppress(FileNotFoundError):\n        config.read(package_dir / \"setup.cfg\")\n        with contextlib.suppress(KeyError, IndexError, TypeError):\n            return str(config[\"options\"][\"python_requires\"])\n\n    setup_py = package_dir / \"setup.py\"\n    with contextlib.suppress(FileNotFoundError), setup_py.open(encoding=\"utf8\") as f2:\n        return setup_py_python_requires(f2.read())\n\n    return None\n", "path": "cibuildwheel/projectfiles.py"}]}