We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
CIBW_ENVIRONMENT broken in 1.5.3
Somehow, my line of `export CIBW_ENVIRONMENT='... CCACHE_BASEDIR=`python -c "import tempfile; import os; print(os.path.realpath(tempfile.gettempdir()))"` ...'` (see https://github.com/YannickJadoul/Parselmouth/blob/1f3dd9abc63afdf4a1d26db40d210bb4187118a8/.travis.yml#L143; yes, I know I ought to clean this up) breaks:

```
    + /bin/true
    + mkdir -p /project
    + /opt/python/cp38-cp38/bin/python -c 'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'
    + uname -i
x86_64
    + python -c import 'tempfile;' import 'os;' 'print(os.path.realpath(tempfile.gettempdir()))'
  File "<string>", line 1
    import
```

See https://travis-ci.org/github/YannickJadoul/Parselmouth/jobs/709679114#L392

I'll start investigating, but is this perhaps related to #403, @joerick?

--- 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/bashlex_eval.py`
Content:
```
1 import shlex
2 import subprocess
3 from typing import Callable, Dict, List, NamedTuple, Optional, Sequence
4 
5 import bashlex  # type: ignore
6 
7 # a function that takes a shell command and the environment, and returns the result
8 EnvironmentExecutor = Callable[[str, Dict[str, str]], str]
9 
10 
11 def local_environment_executor(command: str, env: Dict[str, str]) -> str:
12     return subprocess.check_output(shlex.split(command), env=env, universal_newlines=True)
13 
14 
15 class NodeExecutionContext(NamedTuple):
16     environment: Dict[str, str]
17     input: str
18     executor: EnvironmentExecutor
19 
20 
21 def evaluate(value: str, environment: Dict[str, str], executor: Optional[EnvironmentExecutor] = None) -> str:
22     if not value:
23         # empty string evaluates to empty string
24         # (but trips up bashlex)
25         return ''
26 
27     command_node = bashlex.parsesingle(value)
28 
29     if len(command_node.parts) != 1:
30         raise ValueError(f'"{value}" has too many parts')
31 
32     value_word_node = command_node.parts[0]
33 
34     return evaluate_node(
35         value_word_node,
36         context=NodeExecutionContext(environment=environment, input=value, executor=executor or local_environment_executor)
37     )
38 
39 
40 def evaluate_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
41     if node.kind == 'word':
42         return evaluate_word_node(node, context=context)
43     elif node.kind == 'commandsubstitution':
44         node_result = evaluate_command_node(node.command, context=context)
45         # bash removes training newlines in command substitution
46         return node_result.rstrip()
47     elif node.kind == 'parameter':
48         return evaluate_parameter_node(node, context=context)
49     else:
50         raise ValueError(f'Unsupported bash construct: "{node.kind}"')
51 
52 
53 def evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
54     value = node.word
55 
56     for part in node.parts:
57         part_string = context.input[part.pos[0]:part.pos[1]]
58         part_value = evaluate_node(part, context=context)
59 
60         if part_string not in value:
61             raise RuntimeError(
62                 'bash parse failed. part "{}" not found in "{}". Word was "{}". Full input was "{}"'.format(
63                     part_string, value, node.word, context.input,
64                 )
65             )
66 
67         value = value.replace(part_string, part_value, 1)
68 
69     return value
70 
71 
72 def evaluate_command_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
73     if any(n.kind == 'operator' for n in node.parts):
74         return evaluate_nodes_as_compound_command(node.parts, context=context)
75     else:
76         return evaluate_nodes_as_simple_command(node.parts, context=context)
77 
78 
79 def evaluate_nodes_as_compound_command(nodes: Sequence[bashlex.ast.node], context: NodeExecutionContext) -> str:
80     # bashlex doesn't support any operators besides ';' inside command
81     # substitutions, so we only need to handle that case. We do so assuming
82     # that `set -o errexit` is on, because it's easier to code!
83 
84     result = ''
85     for node in nodes:
86         if node.kind == 'command':
87             result += evaluate_command_node(node, context=context)
88         elif node.kind == 'operator':
89             if node.op == ';':
90                 pass
91             else:
92                 raise ValueError(f'Unsupported bash operator: "{node.op}"')
93         else:
94             raise ValueError(f'Unsupported bash node in compound command: "{node.kind}"')
95 
96     return result
97 
98 
99 def evaluate_nodes_as_simple_command(nodes: List[bashlex.ast.node], context: NodeExecutionContext):
100     words = [evaluate_node(part, context=context) for part in nodes]
101     command = ' '.join(words)
102     return context.executor(command, context.environment)
103 
104 
105 def evaluate_parameter_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:
106     return context.environment.get(node.value, '')
107
```

Path: `cibuildwheel/docker_container.py`
Content:
```
1 import io
2 import json
3 import os
4 import shlex
5 import subprocess
6 import sys
7 import uuid
8 from os import PathLike
9 from pathlib import Path, PurePath
10 from typing import IO, Dict, List, Optional, Sequence, Union
11 
12 
13 class DockerContainer:
14     '''
15     An object that represents a running Docker container.
16 
17     Intended for use as a context manager e.g.
18     `with DockerContainer('ubuntu') as docker:`
19 
20     A bash shell is running in the remote container. When `call()` is invoked,
21     the command is relayed to the remote shell, and the results are streamed
22     back to cibuildwheel.
23     '''
24     UTILITY_PYTHON = '/opt/python/cp38-cp38/bin/python'
25 
26     process: subprocess.Popen
27     bash_stdin: IO[bytes]
28     bash_stdout: IO[bytes]
29 
30     def __init__(self, docker_image: str, simulate_32_bit=False):
31         self.docker_image = docker_image
32         self.simulate_32_bit = simulate_32_bit
33 
34     def __enter__(self) -> 'DockerContainer':
35         self.name = f'cibuildwheel-{uuid.uuid4()}'
36         shell_args = ['linux32', '/bin/bash'] if self.simulate_32_bit else ['/bin/bash']
37         subprocess.run(
38             [
39                 'docker', 'create',
40                 '--env', 'CIBUILDWHEEL',
41                 '--name', self.name,
42                 '-i',
43                 '-v', '/:/host',  # ignored on CircleCI
44                 self.docker_image,
45                 *shell_args
46             ],
47             check=True,
48         )
49         self.process = subprocess.Popen(
50             [
51                 'docker', 'start',
52                 '--attach', '--interactive',
53                 self.name,
54             ],
55             stdin=subprocess.PIPE,
56             stdout=subprocess.PIPE,
57         )
58 
59         assert self.process.stdin and self.process.stdout
60         self.bash_stdin = self.process.stdin
61         self.bash_stdout = self.process.stdout
62 
63         # run a noop command to block until the container is responding
64         self.call(['/bin/true'])
65 
66         return self
67 
68     def __exit__(self, exc_type, exc_val, exc_tb):
69         self.bash_stdin.close()
70         self.process.terminate()
71         self.process.wait()
72 
73         subprocess.run(['docker', 'rm', '--force', '-v', self.name])
74         self.name = None
75 
76     def copy_into(self, from_path: Path, to_path: PurePath) -> None:
77         # `docker cp` causes 'no space left on device' error when
78         # a container is running and the host filesystem is
79         # mounted. https://github.com/moby/moby/issues/38995
80         # Use `docker exec` instead.
81 
82         if from_path.is_dir():
83             self.call(['mkdir', '-p', to_path])
84             subprocess.run(
85                 f'tar cf - . | docker exec -i {self.name} tar -xC {shell_quote(to_path)} -f -',
86                 shell=True,
87                 check=True,
88                 cwd=from_path)
89         else:
90             subprocess.run(
91                 f'cat {shell_quote(from_path)} | docker exec -i {self.name} sh -c "cat > {shell_quote(to_path)}"',
92                 shell=True,
93                 check=True)
94 
95     def copy_out(self, from_path: PurePath, to_path: Path) -> None:
96         # note: we assume from_path is a dir
97         to_path.mkdir(parents=True, exist_ok=True)
98 
99         subprocess.run(
100             f'docker exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -',
101             shell=True,
102             check=True,
103             cwd=to_path
104         )
105 
106     def glob(self, path: PurePath, pattern: str) -> List[PurePath]:
107         glob_pattern = os.path.join(str(path), pattern)
108 
109         path_strs = json.loads(self.call([
110             self.UTILITY_PYTHON,
111             '-c',
112             f'import sys, json, glob; json.dump(glob.glob({glob_pattern!r}), sys.stdout)'
113         ], capture_output=True))
114 
115         return [PurePath(p) for p in path_strs]
116 
117     def call(self, args: Sequence[Union[str, PathLike]], env: Dict[str, str] = {},
118              capture_output=False, cwd: Optional[Union[str, PathLike]] = None) -> str:
119         chdir = f'cd {cwd}' if cwd else ''
120         env_assignments = ' '.join(f'{shlex.quote(k)}={shlex.quote(v)}'
121                                    for k, v in env.items())
122         command = ' '.join(shlex.quote(str(a)) for a in args)
123         end_of_message = str(uuid.uuid4())
124 
125         # log the command we're executing
126         print(f'    + {command}')
127 
128         # Write a command to the remote shell. First we change the
129         # cwd, if that's required. Then, we use the `env` utility to run
130         # `command` inside the specified environment. We use `env` because it
131         # can cope with spaces and strange characters in the name or value.
132         # Finally, the remote shell is told to write a footer - this will show
133         # up in the output so we know when to stop reading, and will include
134         # the returncode of `command`.
135         self.bash_stdin.write(bytes(f'''(
136             {chdir}
137             env {env_assignments} {command}
138             printf "%04d%s\n" $? {end_of_message}
139         )
140         ''', encoding='utf8', errors='surrogateescape'))
141         self.bash_stdin.flush()
142 
143         if capture_output:
144             output_io: IO[bytes] = io.BytesIO()
145         else:
146             output_io = sys.stdout.buffer
147 
148         while True:
149             line = self.bash_stdout.readline()
150 
151             if line.endswith(b'%s\n' % (bytes(end_of_message, encoding='utf8'))):
152                 footer_offset = (
153                     len(line)
154                     - 1  # newline character
155                     - len(end_of_message)  # delimiter
156                     - 4  # 4 returncode decimals
157                 )
158                 returncode_str = line[footer_offset:footer_offset+4]
159                 returncode = int(returncode_str)
160                 # add the last line to output, without the footer
161                 output_io.write(line[0:footer_offset])
162                 break
163             else:
164                 output_io.write(line)
165 
166         if isinstance(output_io, io.BytesIO):
167             output = str(output_io.getvalue(), encoding='utf8', errors='surrogateescape')
168         else:
169             output = ''
170 
171         if returncode != 0:
172             raise subprocess.CalledProcessError(returncode, args, output)
173 
174         return output
175 
176     def get_environment(self) -> Dict[str, str]:
177         return json.loads(self.call([
178             self.UTILITY_PYTHON,
179             '-c',
180             'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'
181         ], capture_output=True))
182 
183     def environment_executor(self, command: str, environment: Dict[str, str]) -> str:
184         # used as an EnvironmentExecutor to evaluate commands and capture output
185         return self.call(shlex.split(command), env=environment)
186 
187 
188 def shell_quote(path: PurePath) -> str:
189     return shlex.quote(str(path))
190
```
--- 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/bashlex_eval.py b/cibuildwheel/bashlex_eval.py
--- a/cibuildwheel/bashlex_eval.py
+++ b/cibuildwheel/bashlex_eval.py
@@ -1,15 +1,14 @@
-import shlex
 import subprocess
 from typing import Callable, Dict, List, NamedTuple, Optional, Sequence
 
 import bashlex  # type: ignore
 
-# a function that takes a shell command and the environment, and returns the result
-EnvironmentExecutor = Callable[[str, Dict[str, str]], str]
+# a function that takes a command and the environment, and returns the result
+EnvironmentExecutor = Callable[[List[str], Dict[str, str]], str]
 
 
-def local_environment_executor(command: str, env: Dict[str, str]) -> str:
-    return subprocess.check_output(shlex.split(command), env=env, universal_newlines=True)
+def local_environment_executor(command: List[str], env: Dict[str, str]) -> str:
+    return subprocess.check_output(command, env=env, universal_newlines=True)
 
 
 class NodeExecutionContext(NamedTuple):
@@ -97,8 +96,7 @@
 
 
 def evaluate_nodes_as_simple_command(nodes: List[bashlex.ast.node], context: NodeExecutionContext):
-    words = [evaluate_node(part, context=context) for part in nodes]
-    command = ' '.join(words)
+    command = [evaluate_node(part, context=context) for part in nodes]
     return context.executor(command, context.environment)
 
 
diff --git a/cibuildwheel/docker_container.py b/cibuildwheel/docker_container.py
--- a/cibuildwheel/docker_container.py
+++ b/cibuildwheel/docker_container.py
@@ -180,9 +180,9 @@
             'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'
         ], capture_output=True))
 
-    def environment_executor(self, command: str, environment: Dict[str, str]) -> str:
+    def environment_executor(self, command: List[str], environment: Dict[str, str]) -> str:
         # used as an EnvironmentExecutor to evaluate commands and capture output
-        return self.call(shlex.split(command), env=environment)
+        return self.call(command, env=environment)
 
 
 def shell_quote(path: PurePath) -> str:

{"golden_diff": "diff --git a/cibuildwheel/bashlex_eval.py b/cibuildwheel/bashlex_eval.py\n--- a/cibuildwheel/bashlex_eval.py\n+++ b/cibuildwheel/bashlex_eval.py\n@@ -1,15 +1,14 @@\n-import shlex\n import subprocess\n from typing import Callable, Dict, List, NamedTuple, Optional, Sequence\n \n import bashlex  # type: ignore\n \n-# a function that takes a shell command and the environment, and returns the result\n-EnvironmentExecutor = Callable[[str, Dict[str, str]], str]\n+# a function that takes a command and the environment, and returns the result\n+EnvironmentExecutor = Callable[[List[str], Dict[str, str]], str]\n \n \n-def local_environment_executor(command: str, env: Dict[str, str]) -> str:\n-    return subprocess.check_output(shlex.split(command), env=env, universal_newlines=True)\n+def local_environment_executor(command: List[str], env: Dict[str, str]) -> str:\n+    return subprocess.check_output(command, env=env, universal_newlines=True)\n \n \n class NodeExecutionContext(NamedTuple):\n@@ -97,8 +96,7 @@\n \n \n def evaluate_nodes_as_simple_command(nodes: List[bashlex.ast.node], context: NodeExecutionContext):\n-    words = [evaluate_node(part, context=context) for part in nodes]\n-    command = ' '.join(words)\n+    command = [evaluate_node(part, context=context) for part in nodes]\n     return context.executor(command, context.environment)\n \n \ndiff --git a/cibuildwheel/docker_container.py b/cibuildwheel/docker_container.py\n--- a/cibuildwheel/docker_container.py\n+++ b/cibuildwheel/docker_container.py\n@@ -180,9 +180,9 @@\n             'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'\n         ], capture_output=True))\n \n-    def environment_executor(self, command: str, environment: Dict[str, str]) -> str:\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(shlex.split(command), env=environment)\n+        return self.call(command, env=environment)\n \n \n def shell_quote(path: PurePath) -> str:\n", "issue": "CIBW_ENVIRONMENT broken in 1.5.3\nSomehow, my line of `export CIBW_ENVIRONMENT='... CCACHE_BASEDIR=`python -c \"import tempfile; import os; print(os.path.realpath(tempfile.gettempdir()))\"` ...'` (see https://github.com/YannickJadoul/Parselmouth/blob/1f3dd9abc63afdf4a1d26db40d210bb4187118a8/.travis.yml#L143; yes, I know I ought to clean this up) breaks:\r\n\r\n```\r\n    + /bin/true\r\n    + mkdir -p /project\r\n    + /opt/python/cp38-cp38/bin/python -c 'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'\r\n    + uname -i\r\nx86_64\r\n    + python -c import 'tempfile;' import 'os;' 'print(os.path.realpath(tempfile.gettempdir()))'\r\n  File \"<string>\", line 1\r\n    import\r\n```\r\n\r\nSee https://travis-ci.org/github/YannickJadoul/Parselmouth/jobs/709679114#L392\r\n\r\nI'll start investigating, but is this perhaps related to #403, @joerick?\n", "before_files": [{"content": "import shlex\nimport subprocess\nfrom typing import Callable, Dict, List, NamedTuple, Optional, Sequence\n\nimport bashlex  # type: ignore\n\n# a function that takes a shell command and the environment, and returns the result\nEnvironmentExecutor = Callable[[str, Dict[str, str]], str]\n\n\ndef local_environment_executor(command: str, env: Dict[str, str]) -> str:\n    return subprocess.check_output(shlex.split(command), env=env, universal_newlines=True)\n\n\nclass NodeExecutionContext(NamedTuple):\n    environment: Dict[str, str]\n    input: str\n    executor: EnvironmentExecutor\n\n\ndef evaluate(value: str, environment: Dict[str, str], executor: Optional[EnvironmentExecutor] = None) -> str:\n    if not value:\n        # empty string evaluates to empty string\n        # (but trips up bashlex)\n        return ''\n\n    command_node = bashlex.parsesingle(value)\n\n    if len(command_node.parts) != 1:\n        raise ValueError(f'\"{value}\" has too many parts')\n\n    value_word_node = command_node.parts[0]\n\n    return evaluate_node(\n        value_word_node,\n        context=NodeExecutionContext(environment=environment, input=value, executor=executor or local_environment_executor)\n    )\n\n\ndef evaluate_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:\n    if node.kind == 'word':\n        return evaluate_word_node(node, context=context)\n    elif node.kind == 'commandsubstitution':\n        node_result = evaluate_command_node(node.command, context=context)\n        # bash removes training newlines in command substitution\n        return node_result.rstrip()\n    elif node.kind == 'parameter':\n        return evaluate_parameter_node(node, context=context)\n    else:\n        raise ValueError(f'Unsupported bash construct: \"{node.kind}\"')\n\n\ndef evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:\n    value = node.word\n\n    for part in node.parts:\n        part_string = context.input[part.pos[0]:part.pos[1]]\n        part_value = evaluate_node(part, context=context)\n\n        if part_string not in value:\n            raise RuntimeError(\n                'bash parse failed. part \"{}\" not found in \"{}\". Word was \"{}\". Full input was \"{}\"'.format(\n                    part_string, value, node.word, context.input,\n                )\n            )\n\n        value = value.replace(part_string, part_value, 1)\n\n    return value\n\n\ndef evaluate_command_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:\n    if any(n.kind == 'operator' for n in node.parts):\n        return evaluate_nodes_as_compound_command(node.parts, context=context)\n    else:\n        return evaluate_nodes_as_simple_command(node.parts, context=context)\n\n\ndef evaluate_nodes_as_compound_command(nodes: Sequence[bashlex.ast.node], context: NodeExecutionContext) -> str:\n    # bashlex doesn't support any operators besides ';' inside command\n    # substitutions, so we only need to handle that case. We do so assuming\n    # that `set -o errexit` is on, because it's easier to code!\n\n    result = ''\n    for node in nodes:\n        if node.kind == 'command':\n            result += evaluate_command_node(node, context=context)\n        elif node.kind == 'operator':\n            if node.op == ';':\n                pass\n            else:\n                raise ValueError(f'Unsupported bash operator: \"{node.op}\"')\n        else:\n            raise ValueError(f'Unsupported bash node in compound command: \"{node.kind}\"')\n\n    return result\n\n\ndef evaluate_nodes_as_simple_command(nodes: List[bashlex.ast.node], context: NodeExecutionContext):\n    words = [evaluate_node(part, context=context) for part in nodes]\n    command = ' '.join(words)\n    return context.executor(command, context.environment)\n\n\ndef evaluate_parameter_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:\n    return context.environment.get(node.value, '')\n", "path": "cibuildwheel/bashlex_eval.py"}, {"content": "import io\nimport json\nimport os\nimport shlex\nimport subprocess\nimport sys\nimport uuid\nfrom os import PathLike\nfrom pathlib import Path, PurePath\nfrom typing import IO, Dict, List, Optional, Sequence, Union\n\n\nclass DockerContainer:\n    '''\n    An object that represents a running Docker container.\n\n    Intended for use as a context manager e.g.\n    `with DockerContainer('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    UTILITY_PYTHON = '/opt/python/cp38-cp38/bin/python'\n\n    process: subprocess.Popen\n    bash_stdin: IO[bytes]\n    bash_stdout: IO[bytes]\n\n    def __init__(self, docker_image: str, simulate_32_bit=False):\n        self.docker_image = docker_image\n        self.simulate_32_bit = simulate_32_bit\n\n    def __enter__(self) -> 'DockerContainer':\n        self.name = f'cibuildwheel-{uuid.uuid4()}'\n        shell_args = ['linux32', '/bin/bash'] if self.simulate_32_bit else ['/bin/bash']\n        subprocess.run(\n            [\n                'docker', 'create',\n                '--env', 'CIBUILDWHEEL',\n                '--name', self.name,\n                '-i',\n                '-v', '/:/host',  # ignored on CircleCI\n                self.docker_image,\n                *shell_args\n            ],\n            check=True,\n        )\n        self.process = subprocess.Popen(\n            [\n                'docker', 'start',\n                '--attach', '--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'])\n\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.bash_stdin.close()\n        self.process.terminate()\n        self.process.wait()\n\n        subprocess.run(['docker', 'rm', '--force', '-v', self.name])\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 - . | docker exec -i {self.name} tar -xC {shell_quote(to_path)} -f -',\n                shell=True,\n                check=True,\n                cwd=from_path)\n        else:\n            subprocess.run(\n                f'cat {shell_quote(from_path)} | docker exec -i {self.name} sh -c \"cat > {shell_quote(to_path)}\"',\n                shell=True,\n                check=True)\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        subprocess.run(\n            f'docker exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -',\n            shell=True,\n            check=True,\n            cwd=to_path\n        )\n\n    def glob(self, path: PurePath, pattern: str) -> List[PurePath]:\n        glob_pattern = os.path.join(str(path), pattern)\n\n        path_strs = json.loads(self.call([\n            self.UTILITY_PYTHON,\n            '-c',\n            f'import sys, json, glob; json.dump(glob.glob({glob_pattern!r}), sys.stdout)'\n        ], capture_output=True))\n\n        return [PurePath(p) for p in path_strs]\n\n    def call(self, args: Sequence[Union[str, PathLike]], env: Dict[str, str] = {},\n             capture_output=False, cwd: Optional[Union[str, PathLike]] = None) -> str:\n        chdir = f'cd {cwd}' if cwd else ''\n        env_assignments = ' '.join(f'{shlex.quote(k)}={shlex.quote(v)}'\n                                   for k, v in env.items())\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 returncode of `command`.\n        self.bash_stdin.write(bytes(f'''(\n            {chdir}\n            env {env_assignments} {command}\n            printf \"%04d%s\\n\" $? {end_of_message}\n        )\n        ''', encoding='utf8', errors='surrogateescape'))\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(b'%s\\n' % (bytes(end_of_message, encoding='utf8'))):\n                footer_offset = (\n                    len(line)\n                    - 1  # newline character\n                    - len(end_of_message)  # delimiter\n                    - 4  # 4 returncode decimals\n                )\n                returncode_str = line[footer_offset:footer_offset+4]\n                returncode = int(returncode_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 returncode != 0:\n            raise subprocess.CalledProcessError(returncode, args, output)\n\n        return output\n\n    def get_environment(self) -> Dict[str, str]:\n        return json.loads(self.call([\n            self.UTILITY_PYTHON,\n            '-c',\n            'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'\n        ], capture_output=True))\n\n    def environment_executor(self, command: str, environment: Dict[str, str]) -> str:\n        # used as an EnvironmentExecutor to evaluate commands and capture output\n        return self.call(shlex.split(command), env=environment)\n\n\ndef shell_quote(path: PurePath) -> str:\n    return shlex.quote(str(path))\n", "path": "cibuildwheel/docker_container.py"}], "after_files": [{"content": "import subprocess\nfrom typing import Callable, Dict, List, NamedTuple, Optional, Sequence\n\nimport bashlex  # type: ignore\n\n# a function that takes a command and the environment, and returns the result\nEnvironmentExecutor = Callable[[List[str], Dict[str, str]], str]\n\n\ndef local_environment_executor(command: List[str], env: Dict[str, str]) -> str:\n    return subprocess.check_output(command, env=env, universal_newlines=True)\n\n\nclass NodeExecutionContext(NamedTuple):\n    environment: Dict[str, str]\n    input: str\n    executor: EnvironmentExecutor\n\n\ndef evaluate(value: str, environment: Dict[str, str], executor: Optional[EnvironmentExecutor] = None) -> str:\n    if not value:\n        # empty string evaluates to empty string\n        # (but trips up bashlex)\n        return ''\n\n    command_node = bashlex.parsesingle(value)\n\n    if len(command_node.parts) != 1:\n        raise ValueError(f'\"{value}\" has too many parts')\n\n    value_word_node = command_node.parts[0]\n\n    return evaluate_node(\n        value_word_node,\n        context=NodeExecutionContext(environment=environment, input=value, executor=executor or local_environment_executor)\n    )\n\n\ndef evaluate_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:\n    if node.kind == 'word':\n        return evaluate_word_node(node, context=context)\n    elif node.kind == 'commandsubstitution':\n        node_result = evaluate_command_node(node.command, context=context)\n        # bash removes training newlines in command substitution\n        return node_result.rstrip()\n    elif node.kind == 'parameter':\n        return evaluate_parameter_node(node, context=context)\n    else:\n        raise ValueError(f'Unsupported bash construct: \"{node.kind}\"')\n\n\ndef evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:\n    value = node.word\n\n    for part in node.parts:\n        part_string = context.input[part.pos[0]:part.pos[1]]\n        part_value = evaluate_node(part, context=context)\n\n        if part_string not in value:\n            raise RuntimeError(\n                'bash parse failed. part \"{}\" not found in \"{}\". Word was \"{}\". Full input was \"{}\"'.format(\n                    part_string, value, node.word, context.input,\n                )\n            )\n\n        value = value.replace(part_string, part_value, 1)\n\n    return value\n\n\ndef evaluate_command_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:\n    if any(n.kind == 'operator' for n in node.parts):\n        return evaluate_nodes_as_compound_command(node.parts, context=context)\n    else:\n        return evaluate_nodes_as_simple_command(node.parts, context=context)\n\n\ndef evaluate_nodes_as_compound_command(nodes: Sequence[bashlex.ast.node], context: NodeExecutionContext) -> str:\n    # bashlex doesn't support any operators besides ';' inside command\n    # substitutions, so we only need to handle that case. We do so assuming\n    # that `set -o errexit` is on, because it's easier to code!\n\n    result = ''\n    for node in nodes:\n        if node.kind == 'command':\n            result += evaluate_command_node(node, context=context)\n        elif node.kind == 'operator':\n            if node.op == ';':\n                pass\n            else:\n                raise ValueError(f'Unsupported bash operator: \"{node.op}\"')\n        else:\n            raise ValueError(f'Unsupported bash node in compound command: \"{node.kind}\"')\n\n    return result\n\n\ndef evaluate_nodes_as_simple_command(nodes: List[bashlex.ast.node], context: NodeExecutionContext):\n    command = [evaluate_node(part, context=context) for part in nodes]\n    return context.executor(command, context.environment)\n\n\ndef evaluate_parameter_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str:\n    return context.environment.get(node.value, '')\n", "path": "cibuildwheel/bashlex_eval.py"}, {"content": "import io\nimport json\nimport os\nimport shlex\nimport subprocess\nimport sys\nimport uuid\nfrom os import PathLike\nfrom pathlib import Path, PurePath\nfrom typing import IO, Dict, List, Optional, Sequence, Union\n\n\nclass DockerContainer:\n    '''\n    An object that represents a running Docker container.\n\n    Intended for use as a context manager e.g.\n    `with DockerContainer('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    UTILITY_PYTHON = '/opt/python/cp38-cp38/bin/python'\n\n    process: subprocess.Popen\n    bash_stdin: IO[bytes]\n    bash_stdout: IO[bytes]\n\n    def __init__(self, docker_image: str, simulate_32_bit=False):\n        self.docker_image = docker_image\n        self.simulate_32_bit = simulate_32_bit\n\n    def __enter__(self) -> 'DockerContainer':\n        self.name = f'cibuildwheel-{uuid.uuid4()}'\n        shell_args = ['linux32', '/bin/bash'] if self.simulate_32_bit else ['/bin/bash']\n        subprocess.run(\n            [\n                'docker', 'create',\n                '--env', 'CIBUILDWHEEL',\n                '--name', self.name,\n                '-i',\n                '-v', '/:/host',  # ignored on CircleCI\n                self.docker_image,\n                *shell_args\n            ],\n            check=True,\n        )\n        self.process = subprocess.Popen(\n            [\n                'docker', 'start',\n                '--attach', '--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'])\n\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.bash_stdin.close()\n        self.process.terminate()\n        self.process.wait()\n\n        subprocess.run(['docker', 'rm', '--force', '-v', self.name])\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 - . | docker exec -i {self.name} tar -xC {shell_quote(to_path)} -f -',\n                shell=True,\n                check=True,\n                cwd=from_path)\n        else:\n            subprocess.run(\n                f'cat {shell_quote(from_path)} | docker exec -i {self.name} sh -c \"cat > {shell_quote(to_path)}\"',\n                shell=True,\n                check=True)\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        subprocess.run(\n            f'docker exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -',\n            shell=True,\n            check=True,\n            cwd=to_path\n        )\n\n    def glob(self, path: PurePath, pattern: str) -> List[PurePath]:\n        glob_pattern = os.path.join(str(path), pattern)\n\n        path_strs = json.loads(self.call([\n            self.UTILITY_PYTHON,\n            '-c',\n            f'import sys, json, glob; json.dump(glob.glob({glob_pattern!r}), sys.stdout)'\n        ], capture_output=True))\n\n        return [PurePath(p) for p in path_strs]\n\n    def call(self, args: Sequence[Union[str, PathLike]], env: Dict[str, str] = {},\n             capture_output=False, cwd: Optional[Union[str, PathLike]] = None) -> str:\n        chdir = f'cd {cwd}' if cwd else ''\n        env_assignments = ' '.join(f'{shlex.quote(k)}={shlex.quote(v)}'\n                                   for k, v in env.items())\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 returncode of `command`.\n        self.bash_stdin.write(bytes(f'''(\n            {chdir}\n            env {env_assignments} {command}\n            printf \"%04d%s\\n\" $? {end_of_message}\n        )\n        ''', encoding='utf8', errors='surrogateescape'))\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(b'%s\\n' % (bytes(end_of_message, encoding='utf8'))):\n                footer_offset = (\n                    len(line)\n                    - 1  # newline character\n                    - len(end_of_message)  # delimiter\n                    - 4  # 4 returncode decimals\n                )\n                returncode_str = line[footer_offset:footer_offset+4]\n                returncode = int(returncode_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 returncode != 0:\n            raise subprocess.CalledProcessError(returncode, args, output)\n\n        return output\n\n    def get_environment(self) -> Dict[str, str]:\n        return json.loads(self.call([\n            self.UTILITY_PYTHON,\n            '-c',\n            'import sys, json, os; json.dump(os.environ.copy(), sys.stdout)'\n        ], capture_output=True))\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)\n\n\ndef shell_quote(path: PurePath) -> str:\n    return shlex.quote(str(path))\n", "path": "cibuildwheel/docker_container.py"}]}