We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
fix: pass through unexpected format strings
Closes #840.


--- 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/util.py`
Content:
```
1 import contextlib
2 import fnmatch
3 import itertools
4 import os
5 import re
6 import ssl
7 import subprocess
8 import sys
9 import textwrap
10 import time
11 import urllib.request
12 from enum import Enum
13 from pathlib import Path
14 from time import sleep
15 from typing import Any, Dict, Iterable, Iterator, List, Optional, TextIO
16 
17 import bracex
18 import certifi
19 import tomli
20 from packaging.specifiers import SpecifierSet
21 from packaging.version import Version
22 
23 from .typing import Literal, PathOrStr, PlatformName
24 
25 resources_dir = Path(__file__).parent / "resources"
26 
27 install_certifi_script = resources_dir / "install_certifi.py"
28 
29 BuildFrontend = Literal["pip", "build"]
30 
31 MANYLINUX_ARCHS = (
32     "x86_64",
33     "i686",
34     "pypy_x86_64",
35     "aarch64",
36     "ppc64le",
37     "s390x",
38     "pypy_aarch64",
39     "pypy_i686",
40 )
41 
42 MUSLLINUX_ARCHS = (
43     "x86_64",
44     "i686",
45     "aarch64",
46     "ppc64le",
47     "s390x",
48 )
49 
50 
51 def prepare_command(command: str, **kwargs: PathOrStr) -> str:
52     """
53     Preprocesses a command by expanding variables like {python}.
54 
55     For example, used in the test_command option to specify the path to the
56     project's root.
57     """
58     return command.format(python="python", pip="pip", **kwargs)
59 
60 
61 def get_build_verbosity_extra_flags(level: int) -> List[str]:
62     if level > 0:
63         return ["-" + level * "v"]
64     elif level < 0:
65         return ["-" + -level * "q"]
66     else:
67         return []
68 
69 
70 def read_python_configs(config: PlatformName) -> List[Dict[str, str]]:
71     input_file = resources_dir / "build-platforms.toml"
72     with input_file.open("rb") as f:
73         loaded_file = tomli.load(f)
74     results: List[Dict[str, str]] = list(loaded_file[config]["python_configurations"])
75     return results
76 
77 
78 def selector_matches(patterns: str, string: str) -> bool:
79     """
80     Returns True if `string` is matched by any of the wildcard patterns in
81     `patterns`.
82 
83     Matching is according to fnmatch, but with shell-like curly brace
84     expansion. For example, 'cp{36,37}-*' would match either of 'cp36-*' or
85     'cp37-*'.
86     """
87     patterns_list = patterns.split()
88     expanded_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in patterns_list)
89     return any(fnmatch.fnmatch(string, pat) for pat in expanded_patterns)
90 
91 
92 class IdentifierSelector:
93     """
94     This class holds a set of build/skip patterns. You call an instance with a
95     build identifier, and it returns True if that identifier should be
96     included. Only call this on valid identifiers, ones that have at least 2
97     numeric digits before the first dash. If a pre-release version X.Y is present,
98     you can filter it with prerelease="XY".
99     """
100 
101     # a pattern that skips prerelease versions, when include_prereleases is False.
102     PRERELEASE_SKIP = ""
103 
104     def __init__(
105         self,
106         *,
107         build_config: str,
108         skip_config: str,
109         requires_python: Optional[SpecifierSet] = None,
110         prerelease_pythons: bool = False,
111     ):
112         self.build_config = build_config
113         self.skip_config = skip_config
114         self.requires_python = requires_python
115         self.prerelease_pythons = prerelease_pythons
116 
117     def __call__(self, build_id: str) -> bool:
118         # Filter build selectors by python_requires if set
119         if self.requires_python is not None:
120             py_ver_str = build_id.split("-")[0]
121             major = int(py_ver_str[2])
122             minor = int(py_ver_str[3:])
123             version = Version(f"{major}.{minor}.99")
124             if not self.requires_python.contains(version):
125                 return False
126 
127         # filter out the prerelease pythons if self.prerelease_pythons is False
128         if not self.prerelease_pythons and selector_matches(
129             BuildSelector.PRERELEASE_SKIP, build_id
130         ):
131             return False
132 
133         should_build = selector_matches(self.build_config, build_id)
134         should_skip = selector_matches(self.skip_config, build_id)
135 
136         return should_build and not should_skip
137 
138     def __repr__(self) -> str:
139         result = f"{self.__class__.__name__}(build_config={self.build_config!r}"
140 
141         if self.skip_config:
142             result += f", skip_config={self.skip_config!r}"
143         if self.prerelease_pythons:
144             result += ", prerelease_pythons=True"
145 
146         result += ")"
147 
148         return result
149 
150 
151 class BuildSelector(IdentifierSelector):
152     pass
153 
154 
155 # Note that requires-python is not needed for TestSelector, as you can't test
156 # what you can't build.
157 class TestSelector(IdentifierSelector):
158     def __init__(self, *, skip_config: str):
159         super().__init__(build_config="*", skip_config=skip_config)
160 
161 
162 # Taken from https://stackoverflow.com/a/107717
163 class Unbuffered:
164     def __init__(self, stream: TextIO) -> None:
165         self.stream = stream
166 
167     def write(self, data: str) -> None:
168         self.stream.write(data)
169         self.stream.flush()
170 
171     def writelines(self, data: Iterable[str]) -> None:
172         self.stream.writelines(data)
173         self.stream.flush()
174 
175     def __getattr__(self, attr: str) -> Any:
176         return getattr(self.stream, attr)
177 
178 
179 def download(url: str, dest: Path) -> None:
180     print(f"+ Download {url} to {dest}")
181     dest_dir = dest.parent
182     if not dest_dir.exists():
183         dest_dir.mkdir(parents=True)
184 
185     # we've had issues when relying on the host OS' CA certificates on Windows,
186     # so we use certifi (this sounds odd but requests also does this by default)
187     cafile = os.environ.get("SSL_CERT_FILE", certifi.where())
188     context = ssl.create_default_context(cafile=cafile)
189     repeat_num = 3
190     for i in range(repeat_num):
191         try:
192             response = urllib.request.urlopen(url, context=context)
193         except Exception:
194             if i == repeat_num - 1:
195                 raise
196             sleep(3)
197             continue
198         break
199 
200     try:
201         dest.write_bytes(response.read())
202     finally:
203         response.close()
204 
205 
206 class DependencyConstraints:
207     def __init__(self, base_file_path: Path):
208         assert base_file_path.exists()
209         self.base_file_path = base_file_path.resolve()
210 
211     @staticmethod
212     def with_defaults() -> "DependencyConstraints":
213         return DependencyConstraints(base_file_path=resources_dir / "constraints.txt")
214 
215     def get_for_python_version(self, version: str) -> Path:
216         version_parts = version.split(".")
217 
218         # try to find a version-specific dependency file e.g. if
219         # ./constraints.txt is the base, look for ./constraints-python36.txt
220         specific_stem = self.base_file_path.stem + f"-python{version_parts[0]}{version_parts[1]}"
221         specific_name = specific_stem + self.base_file_path.suffix
222         specific_file_path = self.base_file_path.with_name(specific_name)
223         if specific_file_path.exists():
224             return specific_file_path
225         else:
226             return self.base_file_path
227 
228     def __repr__(self) -> str:
229         return f"{self.__class__.__name__}({self.base_file_path!r})"
230 
231     def __eq__(self, o: object) -> bool:
232         if not isinstance(o, DependencyConstraints):
233             return False
234 
235         return self.base_file_path == o.base_file_path
236 
237 
238 class NonPlatformWheelError(Exception):
239     def __init__(self) -> None:
240         message = textwrap.dedent(
241             """
242             cibuildwheel: Build failed because a pure Python wheel was generated.
243 
244             If you intend to build a pure-Python wheel, you don't need cibuildwheel - use
245             `pip wheel -w DEST_DIR .` instead.
246 
247             If you expected a platform wheel, check your project configuration, or run
248             cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.
249             """
250         )
251 
252         super().__init__(message)
253 
254 
255 def strtobool(val: str) -> bool:
256     return val.lower() in {"y", "yes", "t", "true", "on", "1"}
257 
258 
259 class CIProvider(Enum):
260     travis_ci = "travis"
261     appveyor = "appveyor"
262     circle_ci = "circle_ci"
263     azure_pipelines = "azure_pipelines"
264     github_actions = "github_actions"
265     gitlab = "gitlab"
266     other = "other"
267 
268 
269 def detect_ci_provider() -> Optional[CIProvider]:
270     if "TRAVIS" in os.environ:
271         return CIProvider.travis_ci
272     elif "APPVEYOR" in os.environ:
273         return CIProvider.appveyor
274     elif "CIRCLECI" in os.environ:
275         return CIProvider.circle_ci
276     elif "AZURE_HTTP_USER_AGENT" in os.environ:
277         return CIProvider.azure_pipelines
278     elif "GITHUB_ACTIONS" in os.environ:
279         return CIProvider.github_actions
280     elif "GITLAB_CI" in os.environ:
281         return CIProvider.gitlab
282     elif strtobool(os.environ.get("CI", "false")):
283         return CIProvider.other
284     else:
285         return None
286 
287 
288 def unwrap(text: str) -> str:
289     """
290     Unwraps multi-line text to a single line
291     """
292     # remove initial line indent
293     text = textwrap.dedent(text)
294     # remove leading/trailing whitespace
295     text = text.strip()
296     # remove consecutive whitespace
297     return re.sub(r"\s+", " ", text)
298 
299 
300 @contextlib.contextmanager
301 def print_new_wheels(msg: str, output_dir: Path) -> Iterator[None]:
302     """
303     Prints the new items in a directory upon exiting. The message to display
304     can include {n} for number of wheels, {s} for total number of seconds,
305     and/or {m} for total number of minutes. Does not print anything if this
306     exits via exception.
307     """
308 
309     start_time = time.time()
310     existing_contents = set(output_dir.iterdir())
311     yield
312     final_contents = set(output_dir.iterdir())
313     new_contents = final_contents - existing_contents
314     n = len(new_contents)
315     s = time.time() - start_time
316     m = s / 60
317     print(msg.format(n=n, s=s, m=m), *sorted(f"  {f.name}" for f in new_contents), sep="\n")
318 
319 
320 def get_pip_version(env: Dict[str, str]) -> str:
321     # we use shell=True here for windows, even though we don't need a shell due to a bug
322     # https://bugs.python.org/issue8557
323     shell = sys.platform.startswith("win")
324     versions_output_text = subprocess.check_output(
325         ["python", "-m", "pip", "freeze", "--all"], universal_newlines=True, shell=shell, env=env
326     )
327     (pip_version,) = (
328         version[5:]
329         for version in versions_output_text.strip().splitlines()
330         if version.startswith("pip==")
331     )
332     return pip_version
333
```
--- 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/util.py b/cibuildwheel/util.py
--- a/cibuildwheel/util.py
+++ b/cibuildwheel/util.py
@@ -48,14 +48,56 @@
 )
 
 
+def format_safe(template: str, **kwargs: Any) -> str:
+    """
+    Works similarly to `template.format(**kwargs)`, except that unmatched
+    fields in `template` are passed through untouched.
+
+    >>> format_safe('{a} {b}', a='123')
+    '123 {b}'
+    >>> format_safe('{a} {b[4]:3f}', a='123')
+    '123 {b[4]:3f}'
+
+    To avoid variable expansion, precede with a single backslash e.g.
+    >>> format_safe('\\{a} {b}', a='123')
+    '{a} {b}'
+    """
+
+    result = template
+
+    for key, value in kwargs.items():
+        find_pattern = re.compile(
+            fr"""
+                (?<!\#)  # don't match if preceded by a hash
+                {{  # literal open curly bracket
+                {re.escape(key)}  # the field name
+                }}  # literal close curly bracket
+            """,
+            re.VERBOSE,
+        )
+
+        # we use a lambda for repl to prevent re.sub interpreting backslashes
+        # in repl as escape sequences
+        result = re.sub(
+            pattern=find_pattern,
+            repl=lambda _: str(value),
+            string=result,
+        )
+
+        # transform escaped sequences into their literal equivalents
+        result = result.replace(f"#{{{key}}}", f"{{{key}}}")
+
+    return result
+
+
 def prepare_command(command: str, **kwargs: PathOrStr) -> str:
     """
     Preprocesses a command by expanding variables like {python}.
 
     For example, used in the test_command option to specify the path to the
-    project's root.
+    project's root. Unmatched syntax will mostly be allowed through.
     """
-    return command.format(python="python", pip="pip", **kwargs)
+    return format_safe(command, python="python", pip="pip", **kwargs)
 
 
 def get_build_verbosity_extra_flags(level: int) -> List[str]:

{"golden_diff": "diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py\n--- a/cibuildwheel/util.py\n+++ b/cibuildwheel/util.py\n@@ -48,14 +48,56 @@\n )\n \n \n+def format_safe(template: str, **kwargs: Any) -> str:\n+    \"\"\"\n+    Works similarly to `template.format(**kwargs)`, except that unmatched\n+    fields in `template` are passed through untouched.\n+\n+    >>> format_safe('{a} {b}', a='123')\n+    '123 {b}'\n+    >>> format_safe('{a} {b[4]:3f}', a='123')\n+    '123 {b[4]:3f}'\n+\n+    To avoid variable expansion, precede with a single backslash e.g.\n+    >>> format_safe('\\\\{a} {b}', a='123')\n+    '{a} {b}'\n+    \"\"\"\n+\n+    result = template\n+\n+    for key, value in kwargs.items():\n+        find_pattern = re.compile(\n+            fr\"\"\"\n+                (?<!\\#)  # don't match if preceded by a hash\n+                {{  # literal open curly bracket\n+                {re.escape(key)}  # the field name\n+                }}  # literal close curly bracket\n+            \"\"\",\n+            re.VERBOSE,\n+        )\n+\n+        # we use a lambda for repl to prevent re.sub interpreting backslashes\n+        # in repl as escape sequences\n+        result = re.sub(\n+            pattern=find_pattern,\n+            repl=lambda _: str(value),\n+            string=result,\n+        )\n+\n+        # transform escaped sequences into their literal equivalents\n+        result = result.replace(f\"#{{{key}}}\", f\"{{{key}}}\")\n+\n+    return result\n+\n+\n def prepare_command(command: str, **kwargs: PathOrStr) -> str:\n     \"\"\"\n     Preprocesses a command by expanding variables like {python}.\n \n     For example, used in the test_command option to specify the path to the\n-    project's root.\n+    project's root. Unmatched syntax will mostly be allowed through.\n     \"\"\"\n-    return command.format(python=\"python\", pip=\"pip\", **kwargs)\n+    return format_safe(command, python=\"python\", pip=\"pip\", **kwargs)\n \n \n def get_build_verbosity_extra_flags(level: int) -> List[str]:\n", "issue": "fix: pass through unexpected format strings\nCloses #840.\n\n", "before_files": [{"content": "import contextlib\nimport fnmatch\nimport itertools\nimport os\nimport re\nimport ssl\nimport subprocess\nimport sys\nimport textwrap\nimport time\nimport urllib.request\nfrom enum import Enum\nfrom pathlib import Path\nfrom time import sleep\nfrom typing import Any, Dict, Iterable, Iterator, List, Optional, TextIO\n\nimport bracex\nimport certifi\nimport tomli\nfrom packaging.specifiers import SpecifierSet\nfrom packaging.version import Version\n\nfrom .typing import Literal, PathOrStr, PlatformName\n\nresources_dir = Path(__file__).parent / \"resources\"\n\ninstall_certifi_script = resources_dir / \"install_certifi.py\"\n\nBuildFrontend = Literal[\"pip\", \"build\"]\n\nMANYLINUX_ARCHS = (\n    \"x86_64\",\n    \"i686\",\n    \"pypy_x86_64\",\n    \"aarch64\",\n    \"ppc64le\",\n    \"s390x\",\n    \"pypy_aarch64\",\n    \"pypy_i686\",\n)\n\nMUSLLINUX_ARCHS = (\n    \"x86_64\",\n    \"i686\",\n    \"aarch64\",\n    \"ppc64le\",\n    \"s390x\",\n)\n\n\ndef prepare_command(command: str, **kwargs: PathOrStr) -> str:\n    \"\"\"\n    Preprocesses a command by expanding variables like {python}.\n\n    For example, used in the test_command option to specify the path to the\n    project's root.\n    \"\"\"\n    return command.format(python=\"python\", pip=\"pip\", **kwargs)\n\n\ndef get_build_verbosity_extra_flags(level: int) -> List[str]:\n    if level > 0:\n        return [\"-\" + level * \"v\"]\n    elif level < 0:\n        return [\"-\" + -level * \"q\"]\n    else:\n        return []\n\n\ndef read_python_configs(config: PlatformName) -> List[Dict[str, str]]:\n    input_file = resources_dir / \"build-platforms.toml\"\n    with input_file.open(\"rb\") as f:\n        loaded_file = tomli.load(f)\n    results: List[Dict[str, str]] = list(loaded_file[config][\"python_configurations\"])\n    return results\n\n\ndef selector_matches(patterns: str, string: str) -> bool:\n    \"\"\"\n    Returns True if `string` is matched by any of the wildcard patterns in\n    `patterns`.\n\n    Matching is according to fnmatch, but with shell-like curly brace\n    expansion. For example, 'cp{36,37}-*' would match either of 'cp36-*' or\n    'cp37-*'.\n    \"\"\"\n    patterns_list = patterns.split()\n    expanded_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in patterns_list)\n    return any(fnmatch.fnmatch(string, pat) for pat in expanded_patterns)\n\n\nclass IdentifierSelector:\n    \"\"\"\n    This class holds a set of build/skip patterns. You call an instance with a\n    build identifier, and it returns True if that identifier should be\n    included. Only call this on valid identifiers, ones that have at least 2\n    numeric digits before the first dash. If a pre-release version X.Y is present,\n    you can filter it with prerelease=\"XY\".\n    \"\"\"\n\n    # a pattern that skips prerelease versions, when include_prereleases is False.\n    PRERELEASE_SKIP = \"\"\n\n    def __init__(\n        self,\n        *,\n        build_config: str,\n        skip_config: str,\n        requires_python: Optional[SpecifierSet] = None,\n        prerelease_pythons: bool = False,\n    ):\n        self.build_config = build_config\n        self.skip_config = skip_config\n        self.requires_python = requires_python\n        self.prerelease_pythons = prerelease_pythons\n\n    def __call__(self, build_id: str) -> bool:\n        # Filter build selectors by python_requires if set\n        if self.requires_python is not None:\n            py_ver_str = build_id.split(\"-\")[0]\n            major = int(py_ver_str[2])\n            minor = int(py_ver_str[3:])\n            version = Version(f\"{major}.{minor}.99\")\n            if not self.requires_python.contains(version):\n                return False\n\n        # filter out the prerelease pythons if self.prerelease_pythons is False\n        if not self.prerelease_pythons and selector_matches(\n            BuildSelector.PRERELEASE_SKIP, build_id\n        ):\n            return False\n\n        should_build = selector_matches(self.build_config, build_id)\n        should_skip = selector_matches(self.skip_config, build_id)\n\n        return should_build and not should_skip\n\n    def __repr__(self) -> str:\n        result = f\"{self.__class__.__name__}(build_config={self.build_config!r}\"\n\n        if self.skip_config:\n            result += f\", skip_config={self.skip_config!r}\"\n        if self.prerelease_pythons:\n            result += \", prerelease_pythons=True\"\n\n        result += \")\"\n\n        return result\n\n\nclass BuildSelector(IdentifierSelector):\n    pass\n\n\n# Note that requires-python is not needed for TestSelector, as you can't test\n# what you can't build.\nclass TestSelector(IdentifierSelector):\n    def __init__(self, *, skip_config: str):\n        super().__init__(build_config=\"*\", skip_config=skip_config)\n\n\n# Taken from https://stackoverflow.com/a/107717\nclass Unbuffered:\n    def __init__(self, stream: TextIO) -> None:\n        self.stream = stream\n\n    def write(self, data: str) -> None:\n        self.stream.write(data)\n        self.stream.flush()\n\n    def writelines(self, data: Iterable[str]) -> None:\n        self.stream.writelines(data)\n        self.stream.flush()\n\n    def __getattr__(self, attr: str) -> Any:\n        return getattr(self.stream, attr)\n\n\ndef download(url: str, dest: Path) -> None:\n    print(f\"+ Download {url} to {dest}\")\n    dest_dir = dest.parent\n    if not dest_dir.exists():\n        dest_dir.mkdir(parents=True)\n\n    # we've had issues when relying on the host OS' CA certificates on Windows,\n    # so we use certifi (this sounds odd but requests also does this by default)\n    cafile = os.environ.get(\"SSL_CERT_FILE\", certifi.where())\n    context = ssl.create_default_context(cafile=cafile)\n    repeat_num = 3\n    for i in range(repeat_num):\n        try:\n            response = urllib.request.urlopen(url, context=context)\n        except Exception:\n            if i == repeat_num - 1:\n                raise\n            sleep(3)\n            continue\n        break\n\n    try:\n        dest.write_bytes(response.read())\n    finally:\n        response.close()\n\n\nclass DependencyConstraints:\n    def __init__(self, base_file_path: Path):\n        assert base_file_path.exists()\n        self.base_file_path = base_file_path.resolve()\n\n    @staticmethod\n    def with_defaults() -> \"DependencyConstraints\":\n        return DependencyConstraints(base_file_path=resources_dir / \"constraints.txt\")\n\n    def get_for_python_version(self, version: str) -> Path:\n        version_parts = version.split(\".\")\n\n        # try to find a version-specific dependency file e.g. if\n        # ./constraints.txt is the base, look for ./constraints-python36.txt\n        specific_stem = self.base_file_path.stem + f\"-python{version_parts[0]}{version_parts[1]}\"\n        specific_name = specific_stem + self.base_file_path.suffix\n        specific_file_path = self.base_file_path.with_name(specific_name)\n        if specific_file_path.exists():\n            return specific_file_path\n        else:\n            return self.base_file_path\n\n    def __repr__(self) -> str:\n        return f\"{self.__class__.__name__}({self.base_file_path!r})\"\n\n    def __eq__(self, o: object) -> bool:\n        if not isinstance(o, DependencyConstraints):\n            return False\n\n        return self.base_file_path == o.base_file_path\n\n\nclass NonPlatformWheelError(Exception):\n    def __init__(self) -> None:\n        message = textwrap.dedent(\n            \"\"\"\n            cibuildwheel: Build failed because a pure Python wheel was generated.\n\n            If you intend to build a pure-Python wheel, you don't need cibuildwheel - use\n            `pip wheel -w DEST_DIR .` instead.\n\n            If you expected a platform wheel, check your project configuration, or run\n            cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.\n            \"\"\"\n        )\n\n        super().__init__(message)\n\n\ndef strtobool(val: str) -> bool:\n    return val.lower() in {\"y\", \"yes\", \"t\", \"true\", \"on\", \"1\"}\n\n\nclass CIProvider(Enum):\n    travis_ci = \"travis\"\n    appveyor = \"appveyor\"\n    circle_ci = \"circle_ci\"\n    azure_pipelines = \"azure_pipelines\"\n    github_actions = \"github_actions\"\n    gitlab = \"gitlab\"\n    other = \"other\"\n\n\ndef detect_ci_provider() -> Optional[CIProvider]:\n    if \"TRAVIS\" in os.environ:\n        return CIProvider.travis_ci\n    elif \"APPVEYOR\" in os.environ:\n        return CIProvider.appveyor\n    elif \"CIRCLECI\" in os.environ:\n        return CIProvider.circle_ci\n    elif \"AZURE_HTTP_USER_AGENT\" in os.environ:\n        return CIProvider.azure_pipelines\n    elif \"GITHUB_ACTIONS\" in os.environ:\n        return CIProvider.github_actions\n    elif \"GITLAB_CI\" in os.environ:\n        return CIProvider.gitlab\n    elif strtobool(os.environ.get(\"CI\", \"false\")):\n        return CIProvider.other\n    else:\n        return None\n\n\ndef unwrap(text: str) -> str:\n    \"\"\"\n    Unwraps multi-line text to a single line\n    \"\"\"\n    # remove initial line indent\n    text = textwrap.dedent(text)\n    # remove leading/trailing whitespace\n    text = text.strip()\n    # remove consecutive whitespace\n    return re.sub(r\"\\s+\", \" \", text)\n\n\n@contextlib.contextmanager\ndef print_new_wheels(msg: str, output_dir: Path) -> Iterator[None]:\n    \"\"\"\n    Prints the new items in a directory upon exiting. The message to display\n    can include {n} for number of wheels, {s} for total number of seconds,\n    and/or {m} for total number of minutes. Does not print anything if this\n    exits via exception.\n    \"\"\"\n\n    start_time = time.time()\n    existing_contents = set(output_dir.iterdir())\n    yield\n    final_contents = set(output_dir.iterdir())\n    new_contents = final_contents - existing_contents\n    n = len(new_contents)\n    s = time.time() - start_time\n    m = s / 60\n    print(msg.format(n=n, s=s, m=m), *sorted(f\"  {f.name}\" for f in new_contents), sep=\"\\n\")\n\n\ndef get_pip_version(env: Dict[str, str]) -> str:\n    # we use shell=True here for windows, even though we don't need a shell due to a bug\n    # https://bugs.python.org/issue8557\n    shell = sys.platform.startswith(\"win\")\n    versions_output_text = subprocess.check_output(\n        [\"python\", \"-m\", \"pip\", \"freeze\", \"--all\"], universal_newlines=True, shell=shell, env=env\n    )\n    (pip_version,) = (\n        version[5:]\n        for version in versions_output_text.strip().splitlines()\n        if version.startswith(\"pip==\")\n    )\n    return pip_version\n", "path": "cibuildwheel/util.py"}], "after_files": [{"content": "import contextlib\nimport fnmatch\nimport itertools\nimport os\nimport re\nimport ssl\nimport subprocess\nimport sys\nimport textwrap\nimport time\nimport urllib.request\nfrom enum import Enum\nfrom pathlib import Path\nfrom time import sleep\nfrom typing import Any, Dict, Iterable, Iterator, List, Optional, TextIO\n\nimport bracex\nimport certifi\nimport tomli\nfrom packaging.specifiers import SpecifierSet\nfrom packaging.version import Version\n\nfrom .typing import Literal, PathOrStr, PlatformName\n\nresources_dir = Path(__file__).parent / \"resources\"\n\ninstall_certifi_script = resources_dir / \"install_certifi.py\"\n\nBuildFrontend = Literal[\"pip\", \"build\"]\n\nMANYLINUX_ARCHS = (\n    \"x86_64\",\n    \"i686\",\n    \"pypy_x86_64\",\n    \"aarch64\",\n    \"ppc64le\",\n    \"s390x\",\n    \"pypy_aarch64\",\n    \"pypy_i686\",\n)\n\nMUSLLINUX_ARCHS = (\n    \"x86_64\",\n    \"i686\",\n    \"aarch64\",\n    \"ppc64le\",\n    \"s390x\",\n)\n\n\ndef format_safe(template: str, **kwargs: Any) -> str:\n    \"\"\"\n    Works similarly to `template.format(**kwargs)`, except that unmatched\n    fields in `template` are passed through untouched.\n\n    >>> format_safe('{a} {b}', a='123')\n    '123 {b}'\n    >>> format_safe('{a} {b[4]:3f}', a='123')\n    '123 {b[4]:3f}'\n\n    To avoid variable expansion, precede with a single backslash e.g.\n    >>> format_safe('\\\\{a} {b}', a='123')\n    '{a} {b}'\n    \"\"\"\n\n    result = template\n\n    for key, value in kwargs.items():\n        find_pattern = re.compile(\n            fr\"\"\"\n                (?<!\\#)  # don't match if preceded by a hash\n                {{  # literal open curly bracket\n                {re.escape(key)}  # the field name\n                }}  # literal close curly bracket\n            \"\"\",\n            re.VERBOSE,\n        )\n\n        # we use a lambda for repl to prevent re.sub interpreting backslashes\n        # in repl as escape sequences\n        result = re.sub(\n            pattern=find_pattern,\n            repl=lambda _: str(value),\n            string=result,\n        )\n\n        # transform escaped sequences into their literal equivalents\n        result = result.replace(f\"#{{{key}}}\", f\"{{{key}}}\")\n\n    return result\n\n\ndef prepare_command(command: str, **kwargs: PathOrStr) -> str:\n    \"\"\"\n    Preprocesses a command by expanding variables like {python}.\n\n    For example, used in the test_command option to specify the path to the\n    project's root. Unmatched syntax will mostly be allowed through.\n    \"\"\"\n    return format_safe(command, python=\"python\", pip=\"pip\", **kwargs)\n\n\ndef get_build_verbosity_extra_flags(level: int) -> List[str]:\n    if level > 0:\n        return [\"-\" + level * \"v\"]\n    elif level < 0:\n        return [\"-\" + -level * \"q\"]\n    else:\n        return []\n\n\ndef read_python_configs(config: PlatformName) -> List[Dict[str, str]]:\n    input_file = resources_dir / \"build-platforms.toml\"\n    with input_file.open(\"rb\") as f:\n        loaded_file = tomli.load(f)\n    results: List[Dict[str, str]] = list(loaded_file[config][\"python_configurations\"])\n    return results\n\n\ndef selector_matches(patterns: str, string: str) -> bool:\n    \"\"\"\n    Returns True if `string` is matched by any of the wildcard patterns in\n    `patterns`.\n\n    Matching is according to fnmatch, but with shell-like curly brace\n    expansion. For example, 'cp{36,37}-*' would match either of 'cp36-*' or\n    'cp37-*'.\n    \"\"\"\n    patterns_list = patterns.split()\n    expanded_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in patterns_list)\n    return any(fnmatch.fnmatch(string, pat) for pat in expanded_patterns)\n\n\nclass IdentifierSelector:\n    \"\"\"\n    This class holds a set of build/skip patterns. You call an instance with a\n    build identifier, and it returns True if that identifier should be\n    included. Only call this on valid identifiers, ones that have at least 2\n    numeric digits before the first dash. If a pre-release version X.Y is present,\n    you can filter it with prerelease=\"XY\".\n    \"\"\"\n\n    # a pattern that skips prerelease versions, when include_prereleases is False.\n    PRERELEASE_SKIP = \"\"\n\n    def __init__(\n        self,\n        *,\n        build_config: str,\n        skip_config: str,\n        requires_python: Optional[SpecifierSet] = None,\n        prerelease_pythons: bool = False,\n    ):\n        self.build_config = build_config\n        self.skip_config = skip_config\n        self.requires_python = requires_python\n        self.prerelease_pythons = prerelease_pythons\n\n    def __call__(self, build_id: str) -> bool:\n        # Filter build selectors by python_requires if set\n        if self.requires_python is not None:\n            py_ver_str = build_id.split(\"-\")[0]\n            major = int(py_ver_str[2])\n            minor = int(py_ver_str[3:])\n            version = Version(f\"{major}.{minor}.99\")\n            if not self.requires_python.contains(version):\n                return False\n\n        # filter out the prerelease pythons if self.prerelease_pythons is False\n        if not self.prerelease_pythons and selector_matches(\n            BuildSelector.PRERELEASE_SKIP, build_id\n        ):\n            return False\n\n        should_build = selector_matches(self.build_config, build_id)\n        should_skip = selector_matches(self.skip_config, build_id)\n\n        return should_build and not should_skip\n\n    def __repr__(self) -> str:\n        result = f\"{self.__class__.__name__}(build_config={self.build_config!r}\"\n\n        if self.skip_config:\n            result += f\", skip_config={self.skip_config!r}\"\n        if self.prerelease_pythons:\n            result += \", prerelease_pythons=True\"\n\n        result += \")\"\n\n        return result\n\n\nclass BuildSelector(IdentifierSelector):\n    pass\n\n\n# Note that requires-python is not needed for TestSelector, as you can't test\n# what you can't build.\nclass TestSelector(IdentifierSelector):\n    def __init__(self, *, skip_config: str):\n        super().__init__(build_config=\"*\", skip_config=skip_config)\n\n\n# Taken from https://stackoverflow.com/a/107717\nclass Unbuffered:\n    def __init__(self, stream: TextIO) -> None:\n        self.stream = stream\n\n    def write(self, data: str) -> None:\n        self.stream.write(data)\n        self.stream.flush()\n\n    def writelines(self, data: Iterable[str]) -> None:\n        self.stream.writelines(data)\n        self.stream.flush()\n\n    def __getattr__(self, attr: str) -> Any:\n        return getattr(self.stream, attr)\n\n\ndef download(url: str, dest: Path) -> None:\n    print(f\"+ Download {url} to {dest}\")\n    dest_dir = dest.parent\n    if not dest_dir.exists():\n        dest_dir.mkdir(parents=True)\n\n    # we've had issues when relying on the host OS' CA certificates on Windows,\n    # so we use certifi (this sounds odd but requests also does this by default)\n    cafile = os.environ.get(\"SSL_CERT_FILE\", certifi.where())\n    context = ssl.create_default_context(cafile=cafile)\n    repeat_num = 3\n    for i in range(repeat_num):\n        try:\n            response = urllib.request.urlopen(url, context=context)\n        except Exception:\n            if i == repeat_num - 1:\n                raise\n            sleep(3)\n            continue\n        break\n\n    try:\n        dest.write_bytes(response.read())\n    finally:\n        response.close()\n\n\nclass DependencyConstraints:\n    def __init__(self, base_file_path: Path):\n        assert base_file_path.exists()\n        self.base_file_path = base_file_path.resolve()\n\n    @staticmethod\n    def with_defaults() -> \"DependencyConstraints\":\n        return DependencyConstraints(base_file_path=resources_dir / \"constraints.txt\")\n\n    def get_for_python_version(self, version: str) -> Path:\n        version_parts = version.split(\".\")\n\n        # try to find a version-specific dependency file e.g. if\n        # ./constraints.txt is the base, look for ./constraints-python36.txt\n        specific_stem = self.base_file_path.stem + f\"-python{version_parts[0]}{version_parts[1]}\"\n        specific_name = specific_stem + self.base_file_path.suffix\n        specific_file_path = self.base_file_path.with_name(specific_name)\n        if specific_file_path.exists():\n            return specific_file_path\n        else:\n            return self.base_file_path\n\n    def __repr__(self) -> str:\n        return f\"{self.__class__.__name__}({self.base_file_path!r})\"\n\n    def __eq__(self, o: object) -> bool:\n        if not isinstance(o, DependencyConstraints):\n            return False\n\n        return self.base_file_path == o.base_file_path\n\n\nclass NonPlatformWheelError(Exception):\n    def __init__(self) -> None:\n        message = textwrap.dedent(\n            \"\"\"\n            cibuildwheel: Build failed because a pure Python wheel was generated.\n\n            If you intend to build a pure-Python wheel, you don't need cibuildwheel - use\n            `pip wheel -w DEST_DIR .` instead.\n\n            If you expected a platform wheel, check your project configuration, or run\n            cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.\n            \"\"\"\n        )\n\n        super().__init__(message)\n\n\ndef strtobool(val: str) -> bool:\n    return val.lower() in {\"y\", \"yes\", \"t\", \"true\", \"on\", \"1\"}\n\n\nclass CIProvider(Enum):\n    travis_ci = \"travis\"\n    appveyor = \"appveyor\"\n    circle_ci = \"circle_ci\"\n    azure_pipelines = \"azure_pipelines\"\n    github_actions = \"github_actions\"\n    gitlab = \"gitlab\"\n    other = \"other\"\n\n\ndef detect_ci_provider() -> Optional[CIProvider]:\n    if \"TRAVIS\" in os.environ:\n        return CIProvider.travis_ci\n    elif \"APPVEYOR\" in os.environ:\n        return CIProvider.appveyor\n    elif \"CIRCLECI\" in os.environ:\n        return CIProvider.circle_ci\n    elif \"AZURE_HTTP_USER_AGENT\" in os.environ:\n        return CIProvider.azure_pipelines\n    elif \"GITHUB_ACTIONS\" in os.environ:\n        return CIProvider.github_actions\n    elif \"GITLAB_CI\" in os.environ:\n        return CIProvider.gitlab\n    elif strtobool(os.environ.get(\"CI\", \"false\")):\n        return CIProvider.other\n    else:\n        return None\n\n\ndef unwrap(text: str) -> str:\n    \"\"\"\n    Unwraps multi-line text to a single line\n    \"\"\"\n    # remove initial line indent\n    text = textwrap.dedent(text)\n    # remove leading/trailing whitespace\n    text = text.strip()\n    # remove consecutive whitespace\n    return re.sub(r\"\\s+\", \" \", text)\n\n\n@contextlib.contextmanager\ndef print_new_wheels(msg: str, output_dir: Path) -> Iterator[None]:\n    \"\"\"\n    Prints the new items in a directory upon exiting. The message to display\n    can include {n} for number of wheels, {s} for total number of seconds,\n    and/or {m} for total number of minutes. Does not print anything if this\n    exits via exception.\n    \"\"\"\n\n    start_time = time.time()\n    existing_contents = set(output_dir.iterdir())\n    yield\n    final_contents = set(output_dir.iterdir())\n    new_contents = final_contents - existing_contents\n    n = len(new_contents)\n    s = time.time() - start_time\n    m = s / 60\n    print(msg.format(n=n, s=s, m=m), *sorted(f\"  {f.name}\" for f in new_contents), sep=\"\\n\")\n\n\ndef get_pip_version(env: Dict[str, str]) -> str:\n    # we use shell=True here for windows, even though we don't need a shell due to a bug\n    # https://bugs.python.org/issue8557\n    shell = sys.platform.startswith(\"win\")\n    versions_output_text = subprocess.check_output(\n        [\"python\", \"-m\", \"pip\", \"freeze\", \"--all\"], universal_newlines=True, shell=shell, env=env\n    )\n    (pip_version,) = (\n        version[5:]\n        for version in versions_output_text.strip().splitlines()\n        if version.startswith(\"pip==\")\n    )\n    return pip_version\n", "path": "cibuildwheel/util.py"}]}