We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
on windows, setup_py_python_requires attempts to open utf-8 setup.py as Windows-1252 and fails
### Description

This [setup.py file](https://github.com/fgregg/fastcluster/blob/master/setup.py) is valid utf-8, and has a few non-ascii characters. In a windows build, `setup_py_python_requires` appears to be opening this file as if it was encoded like Windows-1252 and thus fails on some non-ascii characters.

### Build log

https://github.com/fgregg/fastcluster/runs/4660766954?check_suite_focus=true#step:5:40

### CI config

https://github.com/fgregg/fastcluster/blob/master/.github/workflows/pythonpackage.yml#L41-L47

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cibuildwheel/projectfiles.py`
Content:
```
1 import ast
2 import sys
3 from configparser import ConfigParser
4 from pathlib import Path
5 from typing import Any, Optional
6 
7 import tomli
8 
9 if sys.version_info < (3, 8):
10     Constant = ast.Str
11 
12     def get_constant(x: ast.Str) -> str:
13         return x.s
14 
15 else:
16     Constant = ast.Constant
17 
18     def get_constant(x: ast.Constant) -> Any:
19         return x.value
20 
21 
22 class Analyzer(ast.NodeVisitor):
23     def __init__(self) -> None:
24         self.requires_python: Optional[str] = None
25 
26     def visit(self, content: ast.AST) -> None:
27         for node in ast.walk(content):
28             for child in ast.iter_child_nodes(node):
29                 child.parent = node  # type: ignore[attr-defined]
30         super().visit(content)
31 
32     def visit_keyword(self, node: ast.keyword) -> None:
33         self.generic_visit(node)
34         if node.arg == "python_requires":
35             # Must not be nested in an if or other structure
36             # This will be Module -> Expr -> Call -> keyword
37             if not hasattr(node.parent.parent.parent, "parent") and isinstance(  # type: ignore[attr-defined]
38                 node.value, Constant
39             ):
40                 self.requires_python = get_constant(node.value)
41 
42 
43 def setup_py_python_requires(content: str) -> Optional[str]:
44     try:
45         tree = ast.parse(content)
46         analyzer = Analyzer()
47         analyzer.visit(tree)
48         return analyzer.requires_python or None
49     except Exception:
50         return None
51 
52 
53 def get_requires_python_str(package_dir: Path) -> Optional[str]:
54     """Return the python requires string from the most canonical source available, or None"""
55 
56     # Read in from pyproject.toml:project.requires-python
57     try:
58         with (package_dir / "pyproject.toml").open("rb") as f1:
59             info = tomli.load(f1)
60         return str(info["project"]["requires-python"])
61     except (FileNotFoundError, KeyError, IndexError, TypeError):
62         pass
63 
64     # Read in from setup.cfg:options.python_requires
65     try:
66         config = ConfigParser()
67         config.read(package_dir / "setup.cfg")
68         return str(config["options"]["python_requires"])
69     except (FileNotFoundError, KeyError, IndexError, TypeError):
70         pass
71 
72     try:
73         with (package_dir / "setup.py").open() as f2:
74             return setup_py_python_requires(f2.read())
75     except FileNotFoundError:
76         pass
77 
78     return None
79
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py
--- a/cibuildwheel/projectfiles.py
+++ b/cibuildwheel/projectfiles.py
@@ -70,7 +70,7 @@
         pass
 
     try:
-        with (package_dir / "setup.py").open() as f2:
+        with (package_dir / "setup.py").open(encoding="utf8") as f2:
             return setup_py_python_requires(f2.read())
     except FileNotFoundError:
         pass

{"golden_diff": "diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py\n--- a/cibuildwheel/projectfiles.py\n+++ b/cibuildwheel/projectfiles.py\n@@ -70,7 +70,7 @@\n         pass\n \n     try:\n-        with (package_dir / \"setup.py\").open() as f2:\n+        with (package_dir / \"setup.py\").open(encoding=\"utf8\") as f2:\n             return setup_py_python_requires(f2.read())\n     except FileNotFoundError:\n         pass\n", "issue": "on windows, setup_py_python_requires attempts to open utf-8 setup.py as Windows-1252 and fails\n### Description\r\n\r\nThis [setup.py file](https://github.com/fgregg/fastcluster/blob/master/setup.py) is valid utf-8, and has a few non-ascii characters. In a windows build, `setup_py_python_requires` appears to be opening this file as if it was encoded like Windows-1252 and thus fails on some non-ascii characters.\r\n\r\n### Build log\r\n\r\nhttps://github.com/fgregg/fastcluster/runs/4660766954?check_suite_focus=true#step:5:40\r\n\r\n### CI config\r\n\r\nhttps://github.com/fgregg/fastcluster/blob/master/.github/workflows/pythonpackage.yml#L41-L47\n", "before_files": [{"content": "import ast\nimport sys\nfrom configparser import ConfigParser\nfrom pathlib import Path\nfrom typing import Any, Optional\n\nimport tomli\n\nif sys.version_info < (3, 8):\n    Constant = ast.Str\n\n    def get_constant(x: ast.Str) -> str:\n        return x.s\n\nelse:\n    Constant = ast.Constant\n\n    def get_constant(x: ast.Constant) -> Any:\n        return x.value\n\n\nclass Analyzer(ast.NodeVisitor):\n    def __init__(self) -> None:\n        self.requires_python: Optional[str] = None\n\n    def visit(self, content: ast.AST) -> None:\n        for node in ast.walk(content):\n            for child in ast.iter_child_nodes(node):\n                child.parent = node  # type: ignore[attr-defined]\n        super().visit(content)\n\n    def visit_keyword(self, node: ast.keyword) -> None:\n        self.generic_visit(node)\n        if node.arg == \"python_requires\":\n            # Must not be nested in an if or other structure\n            # This will be Module -> Expr -> Call -> keyword\n            if not hasattr(node.parent.parent.parent, \"parent\") and isinstance(  # type: ignore[attr-defined]\n                node.value, Constant\n            ):\n                self.requires_python = get_constant(node.value)\n\n\ndef setup_py_python_requires(content: str) -> Optional[str]:\n    try:\n        tree = ast.parse(content)\n        analyzer = Analyzer()\n        analyzer.visit(tree)\n        return analyzer.requires_python or None\n    except Exception:\n        return None\n\n\ndef get_requires_python_str(package_dir: Path) -> Optional[str]:\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    try:\n        with (package_dir / \"pyproject.toml\").open(\"rb\") as f1:\n            info = tomli.load(f1)\n        return str(info[\"project\"][\"requires-python\"])\n    except (FileNotFoundError, KeyError, IndexError, TypeError):\n        pass\n\n    # Read in from setup.cfg:options.python_requires\n    try:\n        config = ConfigParser()\n        config.read(package_dir / \"setup.cfg\")\n        return str(config[\"options\"][\"python_requires\"])\n    except (FileNotFoundError, KeyError, IndexError, TypeError):\n        pass\n\n    try:\n        with (package_dir / \"setup.py\").open() as f2:\n            return setup_py_python_requires(f2.read())\n    except FileNotFoundError:\n        pass\n\n    return None\n", "path": "cibuildwheel/projectfiles.py"}], "after_files": [{"content": "import ast\nimport sys\nfrom configparser import ConfigParser\nfrom pathlib import Path\nfrom typing import Any, Optional\n\nimport tomli\n\nif sys.version_info < (3, 8):\n    Constant = ast.Str\n\n    def get_constant(x: ast.Str) -> str:\n        return x.s\n\nelse:\n    Constant = ast.Constant\n\n    def get_constant(x: ast.Constant) -> Any:\n        return x.value\n\n\nclass Analyzer(ast.NodeVisitor):\n    def __init__(self) -> None:\n        self.requires_python: Optional[str] = None\n\n    def visit(self, content: ast.AST) -> None:\n        for node in ast.walk(content):\n            for child in ast.iter_child_nodes(node):\n                child.parent = node  # type: ignore[attr-defined]\n        super().visit(content)\n\n    def visit_keyword(self, node: ast.keyword) -> None:\n        self.generic_visit(node)\n        if node.arg == \"python_requires\":\n            # Must not be nested in an if or other structure\n            # This will be Module -> Expr -> Call -> keyword\n            if not hasattr(node.parent.parent.parent, \"parent\") and isinstance(  # type: ignore[attr-defined]\n                node.value, Constant\n            ):\n                self.requires_python = get_constant(node.value)\n\n\ndef setup_py_python_requires(content: str) -> Optional[str]:\n    try:\n        tree = ast.parse(content)\n        analyzer = Analyzer()\n        analyzer.visit(tree)\n        return analyzer.requires_python or None\n    except Exception:\n        return None\n\n\ndef get_requires_python_str(package_dir: Path) -> Optional[str]:\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    try:\n        with (package_dir / \"pyproject.toml\").open(\"rb\") as f1:\n            info = tomli.load(f1)\n        return str(info[\"project\"][\"requires-python\"])\n    except (FileNotFoundError, KeyError, IndexError, TypeError):\n        pass\n\n    # Read in from setup.cfg:options.python_requires\n    try:\n        config = ConfigParser()\n        config.read(package_dir / \"setup.cfg\")\n        return str(config[\"options\"][\"python_requires\"])\n    except (FileNotFoundError, KeyError, IndexError, TypeError):\n        pass\n\n    try:\n        with (package_dir / \"setup.py\").open(encoding=\"utf8\") as f2:\n            return setup_py_python_requires(f2.read())\n    except FileNotFoundError:\n        pass\n\n    return None\n", "path": "cibuildwheel/projectfiles.py"}]}