Skip to content

Commit 23a6e88

Browse files
authored
Merge pull request #1672 from pypa/disable-host-mount
feat: Adds disable_host_mount suboption to container-engine
2 parents 93542c3 + c18f6cb commit 23a6e88

File tree

10 files changed

+107
-22
lines changed

10 files changed

+107
-22
lines changed

bin/generate_schema.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@
6666
oneOf:
6767
- enum: [docker, podman]
6868
- type: string
69-
pattern: '^docker; ?create_args:'
69+
pattern: '^docker; ?(create_args|disable_host_mount):'
7070
- type: string
71-
pattern: '^podman; ?create_args:'
71+
pattern: '^podman; ?(create_args|disable_host_mount):'
7272
- type: object
7373
additionalProperties: false
7474
required: [name]
@@ -79,6 +79,8 @@
7979
type: array
8080
items:
8181
type: string
82+
disable-host-mount:
83+
type: boolean
8284
dependency-versions:
8385
default: pinned
8486
description: Specify how cibuildwheel controls the versions of the tools it uses

cibuildwheel/oci_container.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,43 @@
3333
class OCIContainerEngineConfig:
3434
name: ContainerEngineName
3535
create_args: Sequence[str] = ()
36+
disable_host_mount: bool = False
3637

3738
@staticmethod
3839
def from_config_string(config_string: str) -> OCIContainerEngineConfig:
3940
config_dict = parse_key_value_string(
40-
config_string, ["name"], ["create_args", "create-args"]
41+
config_string,
42+
["name"],
43+
["create_args", "create-args", "disable_host_mount", "disable-host-mount"],
4144
)
4245
name = " ".join(config_dict["name"])
4346
if name not in {"docker", "podman"}:
4447
msg = f"unknown container engine {name}"
4548
raise ValueError(msg)
4649

4750
name = typing.cast(ContainerEngineName, name)
48-
# some flexibility in the option name to cope with TOML conventions
51+
# some flexibility in the option names to cope with TOML conventions
4952
create_args = config_dict.get("create_args") or config_dict.get("create-args") or []
50-
return OCIContainerEngineConfig(name=name, create_args=create_args)
53+
disable_host_mount_options = (
54+
config_dict.get("disable_host_mount") or config_dict.get("disable-host-mount") or []
55+
)
56+
disable_host_mount = (
57+
strtobool(disable_host_mount_options[-1]) if disable_host_mount_options else False
58+
)
59+
60+
return OCIContainerEngineConfig(
61+
name=name, create_args=create_args, disable_host_mount=disable_host_mount
62+
)
5163

5264
def options_summary(self) -> str | dict[str, str]:
5365
if not self.create_args:
5466
return self.name
5567
else:
56-
return {"name": self.name, "create_args": repr(self.create_args)}
68+
return {
69+
"name": self.name,
70+
"create_args": repr(self.create_args),
71+
"disable_host_mount": str(self.disable_host_mount),
72+
}
5773

5874

5975
DEFAULT_ENGINE = OCIContainerEngineConfig("docker")
@@ -137,7 +153,7 @@ def __enter__(self) -> Self:
137153
"--env=SOURCE_DATE_EPOCH",
138154
f"--name={self.name}",
139155
"--interactive",
140-
"--volume=/:/host", # ignored on CircleCI
156+
*(["--volume=/:/host"] if not self.engine.disable_host_mount else []),
141157
*network_args,
142158
*self.engine.create_args,
143159
self.image,

cibuildwheel/options.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]:
378378
for inner_v in v:
379379
qv = quote_function(inner_v)
380380
yield table["item"].format(k=k, v=qv)
381+
elif isinstance(v, bool):
382+
qv = quote_function(str(v))
383+
yield table["item"].format(k=k, v=qv)
381384
else:
382385
qv = quote_function(v)
383386
yield table["item"].format(k=k, v=qv)

cibuildwheel/resources/cibuildwheel.schema.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@
172172
},
173173
{
174174
"type": "string",
175-
"pattern": "^docker; ?create_args:"
175+
"pattern": "^docker; ?(create_args|disable_host_mount):"
176176
},
177177
{
178178
"type": "string",
179-
"pattern": "^podman; ?create_args:"
179+
"pattern": "^podman; ?(create_args|disable_host_mount):"
180180
},
181181
{
182182
"type": "object",
@@ -196,6 +196,9 @@
196196
"items": {
197197
"type": "string"
198198
}
199+
},
200+
"disable-host-mount": {
201+
"type": "boolean"
199202
}
200203
}
201204
}

cibuildwheel/util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ def parse_key_value_string(
738738
# split by semicolon
739739
fields = [list(group) for k, group in itertools.groupby(parts, lambda x: x == ";") if not k]
740740

741-
result: dict[str, list[str]] = defaultdict(list)
741+
result: defaultdict[str, list[str]] = defaultdict(list)
742742
for field_i, field in enumerate(fields):
743743
# check to see if the option name is specified
744744
field_name, sep, first_value = field[0].partition(":")
@@ -759,4 +759,4 @@ def parse_key_value_string(
759759

760760
result[field_name] += values
761761

762-
return result
762+
return dict(result)

docs/extra.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,9 @@ h1, h2, h3, h4, h5, h6 {
315315
.wy-menu-vertical li.current a {
316316

317317
}
318+
319+
/* word wrap in table cells */
320+
.wy-table-responsive table td, .wy-table-responsive table th {
321+
white-space: normal;
322+
line-height: 1.4;
323+
}

docs/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Linux wheels are built in [`manylinux`/`musllinux` containers](https://github.co
1414

1515
`cibuildwheel` supports this by providing the [`CIBW_ENVIRONMENT`](options.md#environment) and [`CIBW_BEFORE_ALL`](options.md#before-all) options to setup the build environment inside the running container.
1616

17-
- The project directory is copied into the container as `/project`, the output directory for the wheels to be copied out is `/output`. In general, this is handled transparently by `cibuildwheel`. For a more finegrained level of control however, the root of the host file system is mounted as `/host`, allowing for example to access shared files, caches, etc. on the host file system. Note that `/host` is not available on CircleCI due to their Docker policies.
17+
- The project directory is copied into the container as `/project`, the output directory for the wheels to be copied out is `/output`. In general, this is handled transparently by `cibuildwheel`. For a more finegrained level of control however, the root of the host file system is mounted as `/host`, allowing for example to access shared files, caches, etc. on the host file system. Note that `/host` is not available on CircleCI and GitLab CI due to their Docker policies.
1818

1919
- Alternative Docker images can be specified with the `CIBW_MANYLINUX_*_IMAGE`/`CIBW_MUSLLINUX_*_IMAGE` options to allow for a custom, preconfigured build environment for the Linux builds. See [options](options.md#linux-image) for more details.
2020

docs/options.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,8 +1071,8 @@ Auditwheel detects the version of the manylinux / musllinux standard in the imag
10711071
10721072
Options:
10731073

1074-
- `docker[;create_args: ...]`
1075-
- `podman[;create_args: ...]`
1074+
- `docker[;create_args: ...][;disable_host_mount: true/false]`
1075+
- `podman[;create_args: ...][;disable_host_mount: true/false]`
10761076

10771077
Default: `docker`
10781078

@@ -1081,11 +1081,13 @@ Set the container engine to use. Docker is the default, or you can switch to
10811081
running and `docker` available on PATH. To use Podman, it needs to be
10821082
installed and `podman` available on PATH.
10831083

1084-
Arguments can be supplied to the container engine. Currently, the only option
1085-
that's customisable is 'create_args'. Parameters to create_args are
1086-
space-separated strings, which are passed to the container engine on the
1087-
command line when it's creating the container. If you want to include spaces
1088-
inside a parameter, use shell-style quoting.
1084+
Options can be supplied after the name.
1085+
1086+
| Option name | Description
1087+
|---|---
1088+
| `create_args` | Space-separated strings, which are passed to the container engine on the command line when it's creating the container. If you want to include spaces inside a parameter, use shell-style quoting.
1089+
| `disable_host_mount` | By default, cibuildwheel will mount the root of the host filesystem as a volume at `/host` in the container. To disable the host mount, pass `true` to this option.
1090+
10891091

10901092
!!! tip
10911093

@@ -1106,6 +1108,9 @@ inside a parameter, use shell-style quoting.
11061108

11071109
# pass command line options to 'docker create'
11081110
CIBW_CONTAINER_ENGINE: "docker; create_args: --gpus all"
1111+
1112+
# disable the /host mount
1113+
CIBW_CONTAINER_ENGINE: "docker; disable_host_mount: true"
11091114
```
11101115

11111116
!!! tab examples "pyproject.toml"
@@ -1117,6 +1122,9 @@ inside a parameter, use shell-style quoting.
11171122

11181123
# pass command line options to 'docker create'
11191124
container-engine = { name = "docker", create-args = ["--gpus", "all"]}
1125+
1126+
# disable the /host mount
1127+
container-engine = { name = "docker", disable-host-mount = true }
11201128
```
11211129

11221130

unit_test/oci_container_test.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from cibuildwheel.environment import EnvironmentAssignmentBash
1616
from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig
17-
from cibuildwheel.util import detect_ci_provider
17+
from cibuildwheel.util import CIProvider, detect_ci_provider
1818

1919
# Test utilities
2020

@@ -415,3 +415,29 @@ def test_enforce_32_bit(container_engine, image, shell_args):
415415
text=True,
416416
).stdout
417417
assert json.loads(container_args) == shell_args
418+
419+
420+
@pytest.mark.parametrize(
421+
("config", "should_have_host_mount"),
422+
[
423+
("{name}", True),
424+
("{name}; disable_host_mount: false", True),
425+
("{name}; disable_host_mount: true", False),
426+
],
427+
)
428+
def test_disable_host_mount(tmp_path: Path, container_engine, config, should_have_host_mount):
429+
if detect_ci_provider() in {CIProvider.circle_ci, CIProvider.gitlab}:
430+
pytest.skip("Skipping test because docker on this platform does not support host mounts")
431+
432+
engine = OCIContainerEngineConfig.from_config_string(config.format(name=container_engine.name))
433+
434+
sentinel_file = tmp_path / "sentinel"
435+
sentinel_file.write_text("12345")
436+
437+
with OCIContainer(engine=engine, image=DEFAULT_IMAGE) as container:
438+
host_mount_path = "/host" + str(sentinel_file)
439+
if should_have_host_mount:
440+
assert container.call(["cat", host_mount_path], capture_output=True) == "12345"
441+
else:
442+
with pytest.raises(subprocess.CalledProcessError):
443+
container.call(["cat", host_mount_path], capture_output=True)

unit_test/options_test.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,41 +201,61 @@ def test_toml_environment_quoting(tmp_path: Path, toml_assignment, result_value)
201201

202202

203203
@pytest.mark.parametrize(
204-
("toml_assignment", "result_name", "result_create_args"),
204+
("toml_assignment", "result_name", "result_create_args", "result_disable_host_mount"),
205205
[
206206
(
207207
'container-engine = "podman"',
208208
"podman",
209209
[],
210+
False,
210211
),
211212
(
212213
'container-engine = {name = "podman"}',
213214
"podman",
214215
[],
216+
False,
215217
),
216218
(
217219
'container-engine = "docker; create_args: --some-option"',
218220
"docker",
219221
["--some-option"],
222+
False,
220223
),
221224
(
222225
'container-engine = {name = "docker", create-args = ["--some-option"]}',
223226
"docker",
224227
["--some-option"],
228+
False,
225229
),
226230
(
227231
'container-engine = {name = "docker", create-args = ["--some-option", "value that contains spaces"]}',
228232
"docker",
229233
["--some-option", "value that contains spaces"],
234+
False,
230235
),
231236
(
232237
'container-engine = {name = "docker", create-args = ["--some-option", "value;that;contains;semicolons"]}',
233238
"docker",
234239
["--some-option", "value;that;contains;semicolons"],
240+
False,
241+
),
242+
(
243+
'container-engine = {name = "docker", disable-host-mount = true}',
244+
"docker",
245+
[],
246+
True,
247+
),
248+
(
249+
'container-engine = {name = "docker", disable_host_mount = true}',
250+
"docker",
251+
[],
252+
True,
235253
),
236254
],
237255
)
238-
def test_container_engine_option(tmp_path: Path, toml_assignment, result_name, result_create_args):
256+
def test_container_engine_option(
257+
tmp_path: Path, toml_assignment, result_name, result_create_args, result_disable_host_mount
258+
):
239259
args = CommandLineArguments.defaults()
240260
args.package_dir = tmp_path
241261

@@ -253,6 +273,7 @@ def test_container_engine_option(tmp_path: Path, toml_assignment, result_name, r
253273

254274
assert parsed_container_engine.name == result_name
255275
assert parsed_container_engine.create_args == result_create_args
276+
assert parsed_container_engine.disable_host_mount == result_disable_host_mount
256277

257278

258279
def test_environment_pass_references():

0 commit comments

Comments
 (0)