Skip to content

Commit b51d4b3

Browse files
authored
Document the commands provided by Platforms/WASI/__main__.py (GH-158976)
Along the way, fix some bugs and add a `path` command to make is easier to programmatically figure out where things are. The README should also be self-contained enough that one could point an LLM at it for working on WASI.
1 parent 15dd735 commit b51d4b3

5 files changed

Lines changed: 288 additions & 23 deletions

File tree

‎Platforms/WASI/README.md‎

Lines changed: 231 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,237 @@ use WASM runtimes such as [wasmtime](https://wasmtime.dev/).
99
**NOTE**: If you are looking for general information about WebAssembly that is
1010
not directly related to CPython, please see https://github.com/psf/webassembly.
1111

12-
## Build
1312

14-
See [the devguide on how to build and run for WASI](https://devguide.python.org/getting-started/setup-building/#wasi).
13+
## Working on the WASI build
14+
15+
This directory provides a CLI for building and packaging WASI builds for
16+
distribution.
17+
18+
Run all commands below from the root of the CPython source checkout.
19+
20+
To see all the available commands, run:
21+
22+
```shell
23+
python3 Platforms/WASI --help
24+
```
25+
26+
The `python3` interpreter used to run this CLI must be a Python version that
27+
is receiving bugfixes. This requirement does not apply to the build Python,
28+
which the CLI builds from the source checkout.
29+
30+
31+
### Prerequisites
32+
33+
There are some tools that must be available to successfully build.
34+
35+
1. C compiler
36+
2. `make`
37+
3. WASI SDK
38+
4. Wasmtime (or some other WASI runtime configured via `--host-runner`)
39+
40+
The default runner requires Wasmtime be on `PATH`.
41+
42+
The WASI SDK must be the same version as specified in config.toml. The search
43+
for the WASI SDK is done via:
44+
45+
1. `--wasi-sdk` CLI option
46+
2. `WASI_SDK_PATH` environment variable
47+
3. `/opt` where the WASI SDK has been unpacked from its tarball
48+
49+
Note that all prerequisites are included and configured appropriately in the
50+
[WASI dev container image](https://github.com/python/cpython-devcontainers/pkgs/container/wasicontainer).
51+
You can download it via:
52+
53+
```shell
54+
podman pull ghcr.io/python/wasicontainer:latest
55+
```
56+
57+
The `latest` image contains the WASI SDK versions required by all supported
58+
CPython branches.
59+
60+
### Development loop
61+
62+
The common way to get started is to first do a full build:
63+
64+
```shell
65+
python3 Platforms/WASI build --quiet --logdir cross-build/logs -- --with-pydebug --config-cache
66+
```
67+
68+
In the end, you will end up with a "build Python" which is a local build of
69+
Python used for cross-builds. You will also have the WASI build.
70+
71+
Once you have the build you can run the test you want.
72+
73+
Bash:
74+
```bash
75+
"$(python3 Platforms/WASI path)/python.sh" -m test test_os
76+
```
77+
78+
Fish:
79+
```fish
80+
set -l wasi_dir (python3 Platforms/WASI path); "$wasi_dir/python.sh" -m test test_os
81+
```
82+
83+
If you are working on C code and need a rebuild:
84+
85+
```shell
86+
python3 Platforms/WASI make-host
87+
```
88+
89+
90+
### Building
91+
92+
In general,
93+
[the devguide covers how to build and run for WASI](https://devguide.python.org/getting-started/setup-building/#wasi),
94+
but we will cover some of the details here.
95+
96+
The simplest way to get a pydebug WASI build is:
97+
98+
```shell
99+
python3 Platforms/WASI build -- --with-pydebug
100+
```
101+
102+
This builds the build Python and the WASI build with `--with-pydebug` passed to
103+
`configure` (as is anything that comes after `--`). The builds are placed in
104+
the `cross-build/` directory of the source checkout, each in a subdirectory
105+
matching the compiler triple for the build.
106+
107+
You can do the two builds separately if you want:
108+
109+
```shell
110+
python3 Platforms/WASI build-python -- --with-pydebug
111+
python3 Platforms/WASI build-host
112+
```
113+
114+
This can be broken down even more to the separate `configure` and `make` steps:
115+
116+
```shell
117+
python3 Platforms/WASI configure-build-python -- --with-pydebug
118+
python3 Platforms/WASI make-build-python
119+
python3 Platforms/WASI configure-host
120+
python3 Platforms/WASI make-host
121+
```
122+
123+
Note that `configure-host` figures out to do a pydebug build by looking at the
124+
build Python.
125+
126+
There is a `--quiet` flag to redirect output from the underlying commands to a
127+
directory. The `--logdir` flag controls where the log files go (which defaults
128+
to `/tmp`).
129+
130+
```shell
131+
python3 Platforms/WASI build --quiet --logdir cross-build/logs
132+
```
133+
134+
135+
### Packaging
136+
137+
The `package` command is used to gather all the files necessary to make a
138+
release and place them in an archive:
139+
140+
```shell
141+
python3 Platforms/WASI package
142+
```
143+
144+
The files are gathered into a versioned directory inside `dist/` in the source
145+
checkout. An archive containing that directory is placed alongside it in
146+
`dist/`. The gathered directory includes a `bin/python3.wasmtime` file to ease
147+
launching the interpreter.
148+
149+
If you just need to gather the files for a release, you can use the `gather`
150+
command:
151+
152+
```shell
153+
python3 Platforms/WASI gather
154+
```
155+
156+
Both `package` and `gather` require a completed build and delete the entire
157+
existing `dist/` directory before gathering files.
158+
159+
There is no command just to archive the gathered files.
160+
161+
162+
### Paths
163+
164+
The `path` command prints the location of the build Python, WASI build, or
165+
gathered distribution files:
166+
167+
```shell
168+
python3 Platforms/WASI path build-python
169+
python3 Platforms/WASI path wasi
170+
python3 Platforms/WASI path dist
171+
```
172+
173+
With no location argument, `path` defaults to `wasi`. The `dist` location
174+
returns the versioned distribution directory inside `dist/`, not the top-level
175+
`dist/` directory. The `build-python` and `wasi` locations can be queried before
176+
building; the `dist` location requires WASI build metadata to determine the
177+
versioned directory name.
178+
179+
180+
### Cleanup
181+
182+
The `clean` command deletes the entire `cross-build/` and `dist/` directories,
183+
including all build files, gathered distribution files, and archives:
184+
185+
```shell
186+
python3 Platforms/WASI clean
187+
```
188+
189+
190+
### Testing
191+
192+
To find out where the WASI build directory is, you can run:
193+
194+
```shell
195+
python3 Platforms/WASI path
196+
```
197+
198+
From there, you can run the test suite.
199+
200+
For Bash-like shells:
201+
202+
```bash
203+
make buildbottest -C "$(python3 Platforms/WASI path)"
204+
```
205+
206+
For fish:
207+
208+
```fish
209+
make buildbottest -C (python3 Platforms/WASI path)
210+
```
211+
212+
There is a `python.sh` file in the WASI build directory, so you can also run
213+
tests that way.
214+
215+
Bash:
216+
217+
```bash
218+
"$(python3 Platforms/WASI path)/python.sh" -m test
219+
```
220+
221+
Fish:
222+
223+
```fish
224+
set -l wasi_dir (python3 Platforms/WASI path); "$wasi_dir/python.sh" -m test
225+
```
226+
227+
If you want to test the files meant for distribution, the directory containing
228+
the files can be found via `python3 Platforms/WASI path dist` and there is a
229+
`bin/python3.wasmtime` shell script.
230+
231+
Bash:
232+
233+
```bash
234+
"$(python3 Platforms/WASI path dist)/bin/python3.wasmtime" -m test
235+
```
236+
237+
Fish:
238+
239+
```fish
240+
set -l dist_dir (python3 Platforms/WASI path dist); "$dist_dir/bin/python3.wasmtime" -m test
241+
```
242+
15243

16244
## Detecting WASI builds
17245

@@ -48,6 +276,7 @@ posix.uname_result(
48276
'wasi'
49277
```
50278

279+
51280
### C code
52281

53282
WASI SDK defines several built-in macros. You can dump a full list of built-ins

‎Platforms/WASI/__main__.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ def main():
6868
package = subcommands.add_parser(
6969
"package", help="Package the host/WASI Python into an archive"
7070
)
71+
gather = subcommands.add_parser(
72+
"gather", help="Gather all the files for distribution"
73+
)
74+
path = subcommands.add_parser(
75+
"path", help="Print the path to a build or distribution directory"
76+
)
77+
path.add_argument(
78+
"location",
79+
nargs="?",
80+
choices=("build-python", "wasi", "dist"),
81+
default="wasi",
82+
help="Directory whose path to print",
83+
)
7184
subcommands.add_parser(
7285
"clean", help="Delete files and directories created by this script"
7386
)
@@ -144,7 +157,9 @@ def main():
144157
make_host,
145158
build_host,
146159
pythoninfo_host,
160+
gather,
147161
package,
162+
path,
148163
):
149164
subcommand.add_argument(
150165
"--host-triple",
@@ -191,9 +206,18 @@ def main():
191206
_build.pythoninfo_wasi_python(context)
192207
case "clean":
193208
_build.clean_contents(context)
209+
case "gather":
210+
_package.gather(context)
194211
case "package":
195212
_package.gather(context)
196213
_package.archive(context)
214+
case "path":
215+
paths = {
216+
"build-python": "build_python_path",
217+
"wasi": "wasi_build_path",
218+
"dist": "archive_dir",
219+
}
220+
print(getattr(context, paths[context.location]))
197221
case None:
198222
parser.print_help()
199223
case _:

‎Platforms/WASI/_build.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ def call(command, *, context=None, quiet=False, **kwargs):
113113
else:
114114
if (log_path := getattr(context, "log_path", None)) is None:
115115
log_path = pathlib.Path(tempfile.gettempdir())
116+
log_path.mkdir(parents=True, exist_ok=True)
116117
stdout = tempfile.NamedTemporaryFile(
117118
"w",
118119
encoding="utf-8",
@@ -279,9 +280,10 @@ def make_wasi_python(context, working_dir):
279280
def clean_contents(context):
280281
"""Delete all files created by this script."""
281282
context.clean = True
282-
if context.cross_build_path.exists():
283-
_shared.log("🧹", f"Deleting {context.cross_build_path} ...")
284-
shutil.rmtree(context.cross_build_path)
283+
for path in [context.cross_build_path, context.dist_path]:
284+
if path.exists():
285+
_shared.log("🧹", f"Deleting {path} ...")
286+
shutil.rmtree(path)
285287

286288

287289
@subdir("build_python_path")

‎Platforms/WASI/_package.py‎

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -264,18 +264,6 @@ def config_symlink(config_path, context):
264264
return [(symlink, config_path) for symlink in symlinks]
265265

266266

267-
def filename_stem(context):
268-
"""Calculate the stem of the archive file name."""
269-
version_info = context.wasi_build_details["language"]["version_info"]
270-
version = f"python-{version_info['major']}.{version_info['minor']}.{version_info['micro']}"
271-
if version_info["releaselevel"] != "final":
272-
version += version_info["releaselevel"][0] + str(
273-
version_info["serial"]
274-
)
275-
276-
return f"{version}-{context.host_triple}"
277-
278-
279267
def copy_files(files, base):
280268
for dest, src in files:
281269
target = base / dest
@@ -296,13 +284,13 @@ def gather(context):
296284
py_version = python_version(context)
297285
py_d_version = python_version(context, debug_ok=True)
298286

299-
dist = context.checkout / "dist"
287+
dist = context.dist_path
300288
if dist.exists():
301289
_shared.log("🧹", f"Deleting {dist} ...")
302290
shutil.rmtree(dist)
303291

304292
indent = " "
305-
base = dist / filename_stem(context)
293+
base = context.archive_dir
306294
_shared.log("📝", f"Copying files to {base} ...")
307295

308296
_shared.log("📁", "bin/", spacing=indent * 2)
@@ -365,12 +353,12 @@ def gather(context):
365353

366354

367355
def archive(context):
368-
file_name = f"{filename_stem(context)}.tar.xz"
369-
file_path = context.checkout / "dist" / file_name
356+
file_name = f"{context.archive_stem}.tar.xz"
357+
file_path = context.dist_path / file_name
370358
if file_path.exists():
371359
_shared.log("🧹", f"Deleting {file_path} ...")
372360
file_path.unlink()
373-
to_compress = context.checkout / "dist" / filename_stem(context)
361+
to_compress = context.archive_dir
374362
_shared.log("🗜️", f"Archiving to {file_path} ...")
375363
mtime_format = "%Y-%m-%dT%H:%M:%SZ"
376364
if source_date_epoch := os.environ.get("SOURCE_DATE_EPOCH"):

0 commit comments

Comments
 (0)