We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Stderr on docker run at the top
Currently, stdout is captured and placed in the error. But stderr is not, so it's displayed inline. `CalledProcessError` does support stderr too, so should we do the same thing with stderr? (Not sure I'm a big fan of separating stderr and stdout, sometimes they might be best in the original order).

Thoughts?

--- 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/oci_container.py`
Content:
```
1 from __future__ import annotations
2 
3 import io
4 import json
5 import os
6 import platform
7 import shlex
8 import shutil
9 import subprocess
10 import sys
11 import uuid
12 from pathlib import Path, PurePath, PurePosixPath
13 from types import TracebackType
14 from typing import IO, Dict, Sequence, cast
15 
16 from cibuildwheel.util import CIProvider, detect_ci_provider
17 
18 from .typing import Literal, PathOrStr, PopenBytes
19 
20 ContainerEngine = Literal["docker", "podman"]
21 
22 
23 class OCIContainer:
24     """
25     An object that represents a running OCI (e.g. Docker) container.
26 
27     Intended for use as a context manager e.g.
28     `with OCIContainer(image = 'ubuntu') as docker:`
29 
30     A bash shell is running in the remote container. When `call()` is invoked,
31     the command is relayed to the remote shell, and the results are streamed
32     back to cibuildwheel.
33 
34     Example:
35         >>> from cibuildwheel.oci_container import *  # NOQA
36         >>> from cibuildwheel.options import _get_pinned_container_images
37         >>> image = _get_pinned_container_images()['x86_64']['manylinux2014']
38         >>> # Test the default container
39         >>> with OCIContainer(image=image) as self:
40         ...     self.call(["echo", "hello world"])
41         ...     self.call(["cat", "/proc/1/cgroup"])
42         ...     print(self.get_environment())
43         ...     print(self.debug_info())
44     """
45 
46     UTILITY_PYTHON = "/opt/python/cp38-cp38/bin/python"
47 
48     process: PopenBytes
49     bash_stdin: IO[bytes]
50     bash_stdout: IO[bytes]
51 
52     def __init__(
53         self,
54         *,
55         image: str,
56         simulate_32_bit: bool = False,
57         cwd: PathOrStr | None = None,
58         engine: ContainerEngine = "docker",
59     ):
60         if not image:
61             msg = "Must have a non-empty image to run."
62             raise ValueError(msg)
63 
64         self.image = image
65         self.simulate_32_bit = simulate_32_bit
66         self.cwd = cwd
67         self.name: str | None = None
68         self.engine = engine
69 
70     def __enter__(self) -> OCIContainer:
71 
72         self.name = f"cibuildwheel-{uuid.uuid4()}"
73 
74         # work-around for Travis-CI PPC64le Docker runs since 2021:
75         # this avoids network splits
76         # https://github.com/pypa/cibuildwheel/issues/904
77         # https://github.com/conda-forge/conda-smithy/pull/1520
78         network_args = []
79         if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == "ppc64le":
80             network_args = ["--network=host"]
81 
82         shell_args = ["linux32", "/bin/bash"] if self.simulate_32_bit else ["/bin/bash"]
83 
84         subprocess.run(
85             [
86                 self.engine,
87                 "create",
88                 "--env=CIBUILDWHEEL",
89                 f"--name={self.name}",
90                 "--interactive",
91                 "--volume=/:/host",  # ignored on CircleCI
92                 *network_args,
93                 self.image,
94                 *shell_args,
95             ],
96             check=True,
97         )
98 
99         self.process = subprocess.Popen(
100             [
101                 self.engine,
102                 "start",
103                 "--attach",
104                 "--interactive",
105                 self.name,
106             ],
107             stdin=subprocess.PIPE,
108             stdout=subprocess.PIPE,
109         )
110 
111         assert self.process.stdin and self.process.stdout
112         self.bash_stdin = self.process.stdin
113         self.bash_stdout = self.process.stdout
114 
115         # run a noop command to block until the container is responding
116         self.call(["/bin/true"], cwd="/")
117 
118         if self.cwd:
119             # Although `docker create -w` does create the working dir if it
120             # does not exist, podman does not. There does not seem to be a way
121             # to setup a workdir for a container running in podman.
122             self.call(["mkdir", "-p", os.fspath(self.cwd)], cwd="/")
123 
124         return self
125 
126     def __exit__(
127         self,
128         exc_type: type[BaseException] | None,
129         exc_val: BaseException | None,
130         exc_tb: TracebackType | None,
131     ) -> None:
132 
133         self.bash_stdin.write(b"exit 0\n")
134         self.bash_stdin.flush()
135         self.process.wait(timeout=30)
136         self.bash_stdin.close()
137         self.bash_stdout.close()
138 
139         if self.engine == "podman":
140             # This works around what seems to be a race condition in the podman
141             # backend. The full reason is not understood. See PR #966 for a
142             # discussion on possible causes and attempts to remove this line.
143             # For now, this seems to work "well enough".
144             self.process.wait()
145 
146         assert isinstance(self.name, str)
147 
148         subprocess.run(
149             [self.engine, "rm", "--force", "-v", self.name],
150             stdout=subprocess.DEVNULL,
151             check=False,
152         )
153         self.name = None
154 
155     def copy_into(self, from_path: Path, to_path: PurePath) -> None:
156         # `docker cp` causes 'no space left on device' error when
157         # a container is running and the host filesystem is
158         # mounted. https://github.com/moby/moby/issues/38995
159         # Use `docker exec` instead.
160 
161         if from_path.is_dir():
162             self.call(["mkdir", "-p", to_path])
163             subprocess.run(
164                 f"tar cf - . | {self.engine} exec -i {self.name} tar --no-same-owner -xC {shell_quote(to_path)} -f -",
165                 shell=True,
166                 check=True,
167                 cwd=from_path,
168             )
169         else:
170             with subprocess.Popen(
171                 [
172                     self.engine,
173                     "exec",
174                     "-i",
175                     str(self.name),
176                     "sh",
177                     "-c",
178                     f"cat > {shell_quote(to_path)}",
179                 ],
180                 stdin=subprocess.PIPE,
181             ) as exec_process:
182                 exec_process.stdin = cast(IO[bytes], exec_process.stdin)
183 
184                 with open(from_path, "rb") as from_file:
185                     shutil.copyfileobj(from_file, exec_process.stdin)
186 
187                 exec_process.stdin.close()
188                 exec_process.wait()
189 
190                 if exec_process.returncode:
191                     raise subprocess.CalledProcessError(
192                         exec_process.returncode, exec_process.args, None, None
193                     )
194 
195     def copy_out(self, from_path: PurePath, to_path: Path) -> None:
196         # note: we assume from_path is a dir
197         to_path.mkdir(parents=True, exist_ok=True)
198 
199         if self.engine == "podman":
200             subprocess.run(
201                 [
202                     self.engine,
203                     "cp",
204                     f"{self.name}:{from_path}/.",
205                     str(to_path),
206                 ],
207                 check=True,
208                 cwd=to_path,
209             )
210         elif self.engine == "docker":
211             # There is a bug in docker that prevents a simple 'cp' invocation
212             # from working https://github.com/moby/moby/issues/38995
213             command = f"{self.engine} exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -"
214             subprocess.run(
215                 command,
216                 shell=True,
217                 check=True,
218                 cwd=to_path,
219             )
220         else:
221             raise KeyError(self.engine)
222 
223     def glob(self, path: PurePosixPath, pattern: str) -> list[PurePosixPath]:
224         glob_pattern = path.joinpath(pattern)
225 
226         path_strings = json.loads(
227             self.call(
228                 [
229                     self.UTILITY_PYTHON,
230                     "-c",
231                     f"import sys, json, glob; json.dump(glob.glob({str(glob_pattern)!r}), sys.stdout)",
232                 ],
233                 capture_output=True,
234             )
235         )
236 
237         return [PurePosixPath(p) for p in path_strings]
238 
239     def call(
240         self,
241         args: Sequence[PathOrStr],
242         env: dict[str, str] | None = None,
243         capture_output: bool = False,
244         cwd: PathOrStr | None = None,
245     ) -> str:
246 
247         if cwd is None:
248             # Podman does not start the a container in a specific working dir
249             # so we always need to specify it when making calls.
250             cwd = self.cwd
251 
252         chdir = f"cd {cwd}" if cwd else ""
253         env_assignments = (
254             " ".join(f"{shlex.quote(k)}={shlex.quote(v)}" for k, v in env.items())
255             if env is not None
256             else ""
257         )
258         command = " ".join(shlex.quote(str(a)) for a in args)
259         end_of_message = str(uuid.uuid4())
260 
261         # log the command we're executing
262         print(f"    + {command}")
263 
264         # Write a command to the remote shell. First we change the
265         # cwd, if that's required. Then, we use the `env` utility to run
266         # `command` inside the specified environment. We use `env` because it
267         # can cope with spaces and strange characters in the name or value.
268         # Finally, the remote shell is told to write a footer - this will show
269         # up in the output so we know when to stop reading, and will include
270         # the return code of `command`.
271         self.bash_stdin.write(
272             bytes(
273                 f"""(
274             {chdir}
275             env {env_assignments} {command}
276             printf "%04d%s\n" $? {end_of_message}
277         )
278         """,
279                 encoding="utf8",
280                 errors="surrogateescape",
281             )
282         )
283         self.bash_stdin.flush()
284 
285         if capture_output:
286             output_io: IO[bytes] = io.BytesIO()
287         else:
288             output_io = sys.stdout.buffer
289 
290         while True:
291             line = self.bash_stdout.readline()
292 
293             if line.endswith(bytes(end_of_message, encoding="utf8") + b"\n"):
294                 # fmt: off
295                 footer_offset = (
296                     len(line)
297                     - 1  # newline character
298                     - len(end_of_message)  # delimiter
299                     - 4  # 4 return code decimals
300                 )
301                 # fmt: on
302                 return_code_str = line[footer_offset : footer_offset + 4]
303                 return_code = int(return_code_str)
304                 # add the last line to output, without the footer
305                 output_io.write(line[0:footer_offset])
306                 break
307             else:
308                 output_io.write(line)
309 
310         if isinstance(output_io, io.BytesIO):
311             output = str(output_io.getvalue(), encoding="utf8", errors="surrogateescape")
312         else:
313             output = ""
314 
315         if return_code != 0:
316             raise subprocess.CalledProcessError(return_code, args, output)
317 
318         return output
319 
320     def get_environment(self) -> dict[str, str]:
321         env = json.loads(
322             self.call(
323                 [
324                     self.UTILITY_PYTHON,
325                     "-c",
326                     "import sys, json, os; json.dump(os.environ.copy(), sys.stdout)",
327                 ],
328                 capture_output=True,
329             )
330         )
331         return cast(Dict[str, str], env)
332 
333     def environment_executor(self, command: list[str], environment: dict[str, str]) -> str:
334         # used as an EnvironmentExecutor to evaluate commands and capture output
335         return self.call(command, env=environment, capture_output=True)
336 
337     def debug_info(self) -> str:
338         if self.engine == "podman":
339             command = f"{self.engine} info --debug"
340         else:
341             command = f"{self.engine} info"
342         completed = subprocess.run(
343             command,
344             shell=True,
345             check=True,
346             cwd=self.cwd,
347             stdin=subprocess.PIPE,
348             stdout=subprocess.PIPE,
349         )
350         output = str(completed.stdout, encoding="utf8", errors="surrogateescape")
351         return output
352 
353 
354 def shell_quote(path: PurePath) -> str:
355     return shlex.quote(os.fspath(path))
356
```
--- 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/oci_container.py b/cibuildwheel/oci_container.py
--- a/cibuildwheel/oci_container.py
+++ b/cibuildwheel/oci_container.py
@@ -303,9 +303,11 @@
                 return_code = int(return_code_str)
                 # add the last line to output, without the footer
                 output_io.write(line[0:footer_offset])
+                output_io.flush()
                 break
             else:
                 output_io.write(line)
+                output_io.flush()
 
         if isinstance(output_io, io.BytesIO):
             output = str(output_io.getvalue(), encoding="utf8", errors="surrogateescape")

{"golden_diff": "diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py\n--- a/cibuildwheel/oci_container.py\n+++ b/cibuildwheel/oci_container.py\n@@ -303,9 +303,11 @@\n                 return_code = int(return_code_str)\n                 # add the last line to output, without the footer\n                 output_io.write(line[0:footer_offset])\n+                output_io.flush()\n                 break\n             else:\n                 output_io.write(line)\n+                output_io.flush()\n \n         if isinstance(output_io, io.BytesIO):\n             output = str(output_io.getvalue(), encoding=\"utf8\", errors=\"surrogateescape\")\n", "issue": "Stderr on docker run at the top\nCurrently, stdout is captured and placed in the error. But stderr is not, so it's displayed inline. `CalledProcessError` does support stderr too, so should we do the same thing with stderr? (Not sure I'm a big fan of separating stderr and stdout, sometimes they might be best in the original order).\r\n\r\nThoughts?\n", "before_files": [{"content": "from __future__ import annotations\n\nimport io\nimport json\nimport os\nimport platform\nimport shlex\nimport shutil\nimport subprocess\nimport sys\nimport uuid\nfrom pathlib import Path, PurePath, PurePosixPath\nfrom types import TracebackType\nfrom typing import IO, Dict, Sequence, cast\n\nfrom cibuildwheel.util import CIProvider, detect_ci_provider\n\nfrom .typing import Literal, PathOrStr, PopenBytes\n\nContainerEngine = Literal[\"docker\", \"podman\"]\n\n\nclass OCIContainer:\n    \"\"\"\n    An object that represents a running OCI (e.g. Docker) container.\n\n    Intended for use as a context manager e.g.\n    `with OCIContainer(image = 'ubuntu') as docker:`\n\n    A bash shell is running in the remote container. When `call()` is invoked,\n    the command is relayed to the remote shell, and the results are streamed\n    back to cibuildwheel.\n\n    Example:\n        >>> from cibuildwheel.oci_container import *  # NOQA\n        >>> from cibuildwheel.options import _get_pinned_container_images\n        >>> image = _get_pinned_container_images()['x86_64']['manylinux2014']\n        >>> # Test the default container\n        >>> with OCIContainer(image=image) as self:\n        ...     self.call([\"echo\", \"hello world\"])\n        ...     self.call([\"cat\", \"/proc/1/cgroup\"])\n        ...     print(self.get_environment())\n        ...     print(self.debug_info())\n    \"\"\"\n\n    UTILITY_PYTHON = \"/opt/python/cp38-cp38/bin/python\"\n\n    process: PopenBytes\n    bash_stdin: IO[bytes]\n    bash_stdout: IO[bytes]\n\n    def __init__(\n        self,\n        *,\n        image: str,\n        simulate_32_bit: bool = False,\n        cwd: PathOrStr | None = None,\n        engine: ContainerEngine = \"docker\",\n    ):\n        if not image:\n            msg = \"Must have a non-empty image to run.\"\n            raise ValueError(msg)\n\n        self.image = image\n        self.simulate_32_bit = simulate_32_bit\n        self.cwd = cwd\n        self.name: str | None = None\n        self.engine = engine\n\n    def __enter__(self) -> OCIContainer:\n\n        self.name = f\"cibuildwheel-{uuid.uuid4()}\"\n\n        # work-around for Travis-CI PPC64le Docker runs since 2021:\n        # this avoids network splits\n        # https://github.com/pypa/cibuildwheel/issues/904\n        # https://github.com/conda-forge/conda-smithy/pull/1520\n        network_args = []\n        if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == \"ppc64le\":\n            network_args = [\"--network=host\"]\n\n        shell_args = [\"linux32\", \"/bin/bash\"] if self.simulate_32_bit else [\"/bin/bash\"]\n\n        subprocess.run(\n            [\n                self.engine,\n                \"create\",\n                \"--env=CIBUILDWHEEL\",\n                f\"--name={self.name}\",\n                \"--interactive\",\n                \"--volume=/:/host\",  # ignored on CircleCI\n                *network_args,\n                self.image,\n                *shell_args,\n            ],\n            check=True,\n        )\n\n        self.process = subprocess.Popen(\n            [\n                self.engine,\n                \"start\",\n                \"--attach\",\n                \"--interactive\",\n                self.name,\n            ],\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n        )\n\n        assert self.process.stdin and self.process.stdout\n        self.bash_stdin = self.process.stdin\n        self.bash_stdout = self.process.stdout\n\n        # run a noop command to block until the container is responding\n        self.call([\"/bin/true\"], cwd=\"/\")\n\n        if self.cwd:\n            # Although `docker create -w` does create the working dir if it\n            # does not exist, podman does not. There does not seem to be a way\n            # to setup a workdir for a container running in podman.\n            self.call([\"mkdir\", \"-p\", os.fspath(self.cwd)], cwd=\"/\")\n\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> None:\n\n        self.bash_stdin.write(b\"exit 0\\n\")\n        self.bash_stdin.flush()\n        self.process.wait(timeout=30)\n        self.bash_stdin.close()\n        self.bash_stdout.close()\n\n        if self.engine == \"podman\":\n            # This works around what seems to be a race condition in the podman\n            # backend. The full reason is not understood. See PR #966 for a\n            # discussion on possible causes and attempts to remove this line.\n            # For now, this seems to work \"well enough\".\n            self.process.wait()\n\n        assert isinstance(self.name, str)\n\n        subprocess.run(\n            [self.engine, \"rm\", \"--force\", \"-v\", self.name],\n            stdout=subprocess.DEVNULL,\n            check=False,\n        )\n        self.name = None\n\n    def copy_into(self, from_path: Path, to_path: PurePath) -> None:\n        # `docker cp` causes 'no space left on device' error when\n        # a container is running and the host filesystem is\n        # mounted. https://github.com/moby/moby/issues/38995\n        # Use `docker exec` instead.\n\n        if from_path.is_dir():\n            self.call([\"mkdir\", \"-p\", to_path])\n            subprocess.run(\n                f\"tar cf - . | {self.engine} exec -i {self.name} tar --no-same-owner -xC {shell_quote(to_path)} -f -\",\n                shell=True,\n                check=True,\n                cwd=from_path,\n            )\n        else:\n            with subprocess.Popen(\n                [\n                    self.engine,\n                    \"exec\",\n                    \"-i\",\n                    str(self.name),\n                    \"sh\",\n                    \"-c\",\n                    f\"cat > {shell_quote(to_path)}\",\n                ],\n                stdin=subprocess.PIPE,\n            ) as exec_process:\n                exec_process.stdin = cast(IO[bytes], exec_process.stdin)\n\n                with open(from_path, \"rb\") as from_file:\n                    shutil.copyfileobj(from_file, exec_process.stdin)\n\n                exec_process.stdin.close()\n                exec_process.wait()\n\n                if exec_process.returncode:\n                    raise subprocess.CalledProcessError(\n                        exec_process.returncode, exec_process.args, None, None\n                    )\n\n    def copy_out(self, from_path: PurePath, to_path: Path) -> None:\n        # note: we assume from_path is a dir\n        to_path.mkdir(parents=True, exist_ok=True)\n\n        if self.engine == \"podman\":\n            subprocess.run(\n                [\n                    self.engine,\n                    \"cp\",\n                    f\"{self.name}:{from_path}/.\",\n                    str(to_path),\n                ],\n                check=True,\n                cwd=to_path,\n            )\n        elif self.engine == \"docker\":\n            # There is a bug in docker that prevents a simple 'cp' invocation\n            # from working https://github.com/moby/moby/issues/38995\n            command = f\"{self.engine} exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -\"\n            subprocess.run(\n                command,\n                shell=True,\n                check=True,\n                cwd=to_path,\n            )\n        else:\n            raise KeyError(self.engine)\n\n    def glob(self, path: PurePosixPath, pattern: str) -> list[PurePosixPath]:\n        glob_pattern = path.joinpath(pattern)\n\n        path_strings = json.loads(\n            self.call(\n                [\n                    self.UTILITY_PYTHON,\n                    \"-c\",\n                    f\"import sys, json, glob; json.dump(glob.glob({str(glob_pattern)!r}), sys.stdout)\",\n                ],\n                capture_output=True,\n            )\n        )\n\n        return [PurePosixPath(p) for p in path_strings]\n\n    def call(\n        self,\n        args: Sequence[PathOrStr],\n        env: dict[str, str] | None = None,\n        capture_output: bool = False,\n        cwd: PathOrStr | None = None,\n    ) -> str:\n\n        if cwd is None:\n            # Podman does not start the a container in a specific working dir\n            # so we always need to specify it when making calls.\n            cwd = self.cwd\n\n        chdir = f\"cd {cwd}\" if cwd else \"\"\n        env_assignments = (\n            \" \".join(f\"{shlex.quote(k)}={shlex.quote(v)}\" for k, v in env.items())\n            if env is not None\n            else \"\"\n        )\n        command = \" \".join(shlex.quote(str(a)) for a in args)\n        end_of_message = str(uuid.uuid4())\n\n        # log the command we're executing\n        print(f\"    + {command}\")\n\n        # Write a command to the remote shell. First we change the\n        # cwd, if that's required. Then, we use the `env` utility to run\n        # `command` inside the specified environment. We use `env` because it\n        # can cope with spaces and strange characters in the name or value.\n        # Finally, the remote shell is told to write a footer - this will show\n        # up in the output so we know when to stop reading, and will include\n        # the return code of `command`.\n        self.bash_stdin.write(\n            bytes(\n                f\"\"\"(\n            {chdir}\n            env {env_assignments} {command}\n            printf \"%04d%s\\n\" $? {end_of_message}\n        )\n        \"\"\",\n                encoding=\"utf8\",\n                errors=\"surrogateescape\",\n            )\n        )\n        self.bash_stdin.flush()\n\n        if capture_output:\n            output_io: IO[bytes] = io.BytesIO()\n        else:\n            output_io = sys.stdout.buffer\n\n        while True:\n            line = self.bash_stdout.readline()\n\n            if line.endswith(bytes(end_of_message, encoding=\"utf8\") + b\"\\n\"):\n                # fmt: off\n                footer_offset = (\n                    len(line)\n                    - 1  # newline character\n                    - len(end_of_message)  # delimiter\n                    - 4  # 4 return code decimals\n                )\n                # fmt: on\n                return_code_str = line[footer_offset : footer_offset + 4]\n                return_code = int(return_code_str)\n                # add the last line to output, without the footer\n                output_io.write(line[0:footer_offset])\n                break\n            else:\n                output_io.write(line)\n\n        if isinstance(output_io, io.BytesIO):\n            output = str(output_io.getvalue(), encoding=\"utf8\", errors=\"surrogateescape\")\n        else:\n            output = \"\"\n\n        if return_code != 0:\n            raise subprocess.CalledProcessError(return_code, args, output)\n\n        return output\n\n    def get_environment(self) -> dict[str, str]:\n        env = json.loads(\n            self.call(\n                [\n                    self.UTILITY_PYTHON,\n                    \"-c\",\n                    \"import sys, json, os; json.dump(os.environ.copy(), sys.stdout)\",\n                ],\n                capture_output=True,\n            )\n        )\n        return cast(Dict[str, str], env)\n\n    def environment_executor(self, command: list[str], environment: dict[str, str]) -> str:\n        # used as an EnvironmentExecutor to evaluate commands and capture output\n        return self.call(command, env=environment, capture_output=True)\n\n    def debug_info(self) -> str:\n        if self.engine == \"podman\":\n            command = f\"{self.engine} info --debug\"\n        else:\n            command = f\"{self.engine} info\"\n        completed = subprocess.run(\n            command,\n            shell=True,\n            check=True,\n            cwd=self.cwd,\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n        )\n        output = str(completed.stdout, encoding=\"utf8\", errors=\"surrogateescape\")\n        return output\n\n\ndef shell_quote(path: PurePath) -> str:\n    return shlex.quote(os.fspath(path))\n", "path": "cibuildwheel/oci_container.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport io\nimport json\nimport os\nimport platform\nimport shlex\nimport shutil\nimport subprocess\nimport sys\nimport uuid\nfrom pathlib import Path, PurePath, PurePosixPath\nfrom types import TracebackType\nfrom typing import IO, Dict, Sequence, cast\n\nfrom cibuildwheel.util import CIProvider, detect_ci_provider\n\nfrom .typing import Literal, PathOrStr, PopenBytes\n\nContainerEngine = Literal[\"docker\", \"podman\"]\n\n\nclass OCIContainer:\n    \"\"\"\n    An object that represents a running OCI (e.g. Docker) container.\n\n    Intended for use as a context manager e.g.\n    `with OCIContainer(image = 'ubuntu') as docker:`\n\n    A bash shell is running in the remote container. When `call()` is invoked,\n    the command is relayed to the remote shell, and the results are streamed\n    back to cibuildwheel.\n\n    Example:\n        >>> from cibuildwheel.oci_container import *  # NOQA\n        >>> from cibuildwheel.options import _get_pinned_container_images\n        >>> image = _get_pinned_container_images()['x86_64']['manylinux2014']\n        >>> # Test the default container\n        >>> with OCIContainer(image=image) as self:\n        ...     self.call([\"echo\", \"hello world\"])\n        ...     self.call([\"cat\", \"/proc/1/cgroup\"])\n        ...     print(self.get_environment())\n        ...     print(self.debug_info())\n    \"\"\"\n\n    UTILITY_PYTHON = \"/opt/python/cp38-cp38/bin/python\"\n\n    process: PopenBytes\n    bash_stdin: IO[bytes]\n    bash_stdout: IO[bytes]\n\n    def __init__(\n        self,\n        *,\n        image: str,\n        simulate_32_bit: bool = False,\n        cwd: PathOrStr | None = None,\n        engine: ContainerEngine = \"docker\",\n    ):\n        if not image:\n            msg = \"Must have a non-empty image to run.\"\n            raise ValueError(msg)\n\n        self.image = image\n        self.simulate_32_bit = simulate_32_bit\n        self.cwd = cwd\n        self.name: str | None = None\n        self.engine = engine\n\n    def __enter__(self) -> OCIContainer:\n\n        self.name = f\"cibuildwheel-{uuid.uuid4()}\"\n\n        # work-around for Travis-CI PPC64le Docker runs since 2021:\n        # this avoids network splits\n        # https://github.com/pypa/cibuildwheel/issues/904\n        # https://github.com/conda-forge/conda-smithy/pull/1520\n        network_args = []\n        if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == \"ppc64le\":\n            network_args = [\"--network=host\"]\n\n        shell_args = [\"linux32\", \"/bin/bash\"] if self.simulate_32_bit else [\"/bin/bash\"]\n\n        subprocess.run(\n            [\n                self.engine,\n                \"create\",\n                \"--env=CIBUILDWHEEL\",\n                f\"--name={self.name}\",\n                \"--interactive\",\n                \"--volume=/:/host\",  # ignored on CircleCI\n                *network_args,\n                self.image,\n                *shell_args,\n            ],\n            check=True,\n        )\n\n        self.process = subprocess.Popen(\n            [\n                self.engine,\n                \"start\",\n                \"--attach\",\n                \"--interactive\",\n                self.name,\n            ],\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n        )\n\n        assert self.process.stdin and self.process.stdout\n        self.bash_stdin = self.process.stdin\n        self.bash_stdout = self.process.stdout\n\n        # run a noop command to block until the container is responding\n        self.call([\"/bin/true\"], cwd=\"/\")\n\n        if self.cwd:\n            # Although `docker create -w` does create the working dir if it\n            # does not exist, podman does not. There does not seem to be a way\n            # to setup a workdir for a container running in podman.\n            self.call([\"mkdir\", \"-p\", os.fspath(self.cwd)], cwd=\"/\")\n\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> None:\n\n        self.bash_stdin.write(b\"exit 0\\n\")\n        self.bash_stdin.flush()\n        self.process.wait(timeout=30)\n        self.bash_stdin.close()\n        self.bash_stdout.close()\n\n        if self.engine == \"podman\":\n            # This works around what seems to be a race condition in the podman\n            # backend. The full reason is not understood. See PR #966 for a\n            # discussion on possible causes and attempts to remove this line.\n            # For now, this seems to work \"well enough\".\n            self.process.wait()\n\n        assert isinstance(self.name, str)\n\n        subprocess.run(\n            [self.engine, \"rm\", \"--force\", \"-v\", self.name],\n            stdout=subprocess.DEVNULL,\n            check=False,\n        )\n        self.name = None\n\n    def copy_into(self, from_path: Path, to_path: PurePath) -> None:\n        # `docker cp` causes 'no space left on device' error when\n        # a container is running and the host filesystem is\n        # mounted. https://github.com/moby/moby/issues/38995\n        # Use `docker exec` instead.\n\n        if from_path.is_dir():\n            self.call([\"mkdir\", \"-p\", to_path])\n            subprocess.run(\n                f\"tar cf - . | {self.engine} exec -i {self.name} tar --no-same-owner -xC {shell_quote(to_path)} -f -\",\n                shell=True,\n                check=True,\n                cwd=from_path,\n            )\n        else:\n            with subprocess.Popen(\n                [\n                    self.engine,\n                    \"exec\",\n                    \"-i\",\n                    str(self.name),\n                    \"sh\",\n                    \"-c\",\n                    f\"cat > {shell_quote(to_path)}\",\n                ],\n                stdin=subprocess.PIPE,\n            ) as exec_process:\n                exec_process.stdin = cast(IO[bytes], exec_process.stdin)\n\n                with open(from_path, \"rb\") as from_file:\n                    shutil.copyfileobj(from_file, exec_process.stdin)\n\n                exec_process.stdin.close()\n                exec_process.wait()\n\n                if exec_process.returncode:\n                    raise subprocess.CalledProcessError(\n                        exec_process.returncode, exec_process.args, None, None\n                    )\n\n    def copy_out(self, from_path: PurePath, to_path: Path) -> None:\n        # note: we assume from_path is a dir\n        to_path.mkdir(parents=True, exist_ok=True)\n\n        if self.engine == \"podman\":\n            subprocess.run(\n                [\n                    self.engine,\n                    \"cp\",\n                    f\"{self.name}:{from_path}/.\",\n                    str(to_path),\n                ],\n                check=True,\n                cwd=to_path,\n            )\n        elif self.engine == \"docker\":\n            # There is a bug in docker that prevents a simple 'cp' invocation\n            # from working https://github.com/moby/moby/issues/38995\n            command = f\"{self.engine} exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -\"\n            subprocess.run(\n                command,\n                shell=True,\n                check=True,\n                cwd=to_path,\n            )\n        else:\n            raise KeyError(self.engine)\n\n    def glob(self, path: PurePosixPath, pattern: str) -> list[PurePosixPath]:\n        glob_pattern = path.joinpath(pattern)\n\n        path_strings = json.loads(\n            self.call(\n                [\n                    self.UTILITY_PYTHON,\n                    \"-c\",\n                    f\"import sys, json, glob; json.dump(glob.glob({str(glob_pattern)!r}), sys.stdout)\",\n                ],\n                capture_output=True,\n            )\n        )\n\n        return [PurePosixPath(p) for p in path_strings]\n\n    def call(\n        self,\n        args: Sequence[PathOrStr],\n        env: dict[str, str] | None = None,\n        capture_output: bool = False,\n        cwd: PathOrStr | None = None,\n    ) -> str:\n\n        if cwd is None:\n            # Podman does not start the a container in a specific working dir\n            # so we always need to specify it when making calls.\n            cwd = self.cwd\n\n        chdir = f\"cd {cwd}\" if cwd else \"\"\n        env_assignments = (\n            \" \".join(f\"{shlex.quote(k)}={shlex.quote(v)}\" for k, v in env.items())\n            if env is not None\n            else \"\"\n        )\n        command = \" \".join(shlex.quote(str(a)) for a in args)\n        end_of_message = str(uuid.uuid4())\n\n        # log the command we're executing\n        print(f\"    + {command}\")\n\n        # Write a command to the remote shell. First we change the\n        # cwd, if that's required. Then, we use the `env` utility to run\n        # `command` inside the specified environment. We use `env` because it\n        # can cope with spaces and strange characters in the name or value.\n        # Finally, the remote shell is told to write a footer - this will show\n        # up in the output so we know when to stop reading, and will include\n        # the return code of `command`.\n        self.bash_stdin.write(\n            bytes(\n                f\"\"\"(\n            {chdir}\n            env {env_assignments} {command}\n            printf \"%04d%s\\n\" $? {end_of_message}\n        )\n        \"\"\",\n                encoding=\"utf8\",\n                errors=\"surrogateescape\",\n            )\n        )\n        self.bash_stdin.flush()\n\n        if capture_output:\n            output_io: IO[bytes] = io.BytesIO()\n        else:\n            output_io = sys.stdout.buffer\n\n        while True:\n            line = self.bash_stdout.readline()\n\n            if line.endswith(bytes(end_of_message, encoding=\"utf8\") + b\"\\n\"):\n                # fmt: off\n                footer_offset = (\n                    len(line)\n                    - 1  # newline character\n                    - len(end_of_message)  # delimiter\n                    - 4  # 4 return code decimals\n                )\n                # fmt: on\n                return_code_str = line[footer_offset : footer_offset + 4]\n                return_code = int(return_code_str)\n                # add the last line to output, without the footer\n                output_io.write(line[0:footer_offset])\n                output_io.flush()\n                break\n            else:\n                output_io.write(line)\n                output_io.flush()\n\n        if isinstance(output_io, io.BytesIO):\n            output = str(output_io.getvalue(), encoding=\"utf8\", errors=\"surrogateescape\")\n        else:\n            output = \"\"\n\n        if return_code != 0:\n            raise subprocess.CalledProcessError(return_code, args, output)\n\n        return output\n\n    def get_environment(self) -> dict[str, str]:\n        env = json.loads(\n            self.call(\n                [\n                    self.UTILITY_PYTHON,\n                    \"-c\",\n                    \"import sys, json, os; json.dump(os.environ.copy(), sys.stdout)\",\n                ],\n                capture_output=True,\n            )\n        )\n        return cast(Dict[str, str], env)\n\n    def environment_executor(self, command: list[str], environment: dict[str, str]) -> str:\n        # used as an EnvironmentExecutor to evaluate commands and capture output\n        return self.call(command, env=environment, capture_output=True)\n\n    def debug_info(self) -> str:\n        if self.engine == \"podman\":\n            command = f\"{self.engine} info --debug\"\n        else:\n            command = f\"{self.engine} info\"\n        completed = subprocess.run(\n            command,\n            shell=True,\n            check=True,\n            cwd=self.cwd,\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n        )\n        output = str(completed.stdout, encoding=\"utf8\", errors=\"surrogateescape\")\n        return output\n\n\ndef shell_quote(path: PurePath) -> str:\n    return shlex.quote(os.fspath(path))\n", "path": "cibuildwheel/oci_container.py"}]}