Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/reusable-wasi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,4 @@ jobs:
- name: "Display build info of the host/WASI Python"
run: python3 Platforms/WASI pythoninfo-host
- name: "Test"
run: |
CROSS_BUILD_WASI=$(python3 Platforms/WASI path)
make --directory "${CROSS_BUILD_WASI}" test
run: python3 Platforms/WASI test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add a ``test`` command to :file:`Platforms/WASI` with ``--speed`` to select
fast CI or slow Buildbot tests and ``--target`` to test the build, gathered
distribution, or release archive.
31 changes: 30 additions & 1 deletion Platforms/WASI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,36 @@ python3 Platforms/WASI clean

### Testing

To find out where the WASI build directory is, you can run:
After building, you can run the CI tests with:

```shell
python3 Platforms/WASI test
```

The `--speed` flag can be:

- `fast` (the default): `make test`
- `slow`: `make buildbottest`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use "make test" by default (fast), and add --slow or --buildbot option for the "slow" option (make buildbottest)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure that's another way to do it. Are you just bikeshedding on the CLI or is there an explicit reason to do it the way you're suggesting?


The `--target` flag selects the files to test:

- `build` (the default): the WASI build in `cross-build/`.
- `dist`: the distribution files gathered in `dist/`.
- `archive`: the contents of the release archive, unpacked into `dist/`.

To thoroughly test the contents of the release archive, use:

```shell
python3 Platforms/WASI test --speed slow --target archive
```

This requires an archive created by `package` or `release`. It deletes the
selected versioned distribution directory inside `dist/`, then restores it
from the matching `.tar.xz` archive before running the tests.
The archive and other distributions in `dist/` are left untouched.

You can also run tests more directly. To find out where the WASI build directory
is, you can run:

```shell
python3 Platforms/WASI path
Expand Down
31 changes: 29 additions & 2 deletions Platforms/WASI/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"_build",
"_package",
"_shared",
"_test",
]

import argparse
Expand All @@ -14,6 +15,7 @@
import _build
import _package
import _shared
import _test


def main():
Expand Down Expand Up @@ -76,6 +78,20 @@ def main():
release = subcommands.add_parser(
"release", help="Build and package for a release"
)
test = subcommands.add_parser("test", help="Run the tests")
test.add_argument(
"--speed",
choices={"fast", "slow"},
default="fast",
help="Run `make test` (fast, the default) or `make buildbottest` (slow)",
)
test.add_argument(
"--target",
choices={"build", "dist", "archive"},
default="build",
help="Test the build (the default), gathered distribution, or release archive; "
"'archive' replaces the distribution directory with the archive's contents",
)
path = subcommands.add_parser(
"path", help="Print the path to a build or distribution directory"
)
Expand All @@ -100,6 +116,7 @@ def main():
pythoninfo_host,
build_host,
release,
test,
):
subcommand.add_argument(
"--quiet",
Expand Down Expand Up @@ -129,6 +146,13 @@ def main():
dest="clean",
help="Delete any relevant directories before building",
)
for subcommand in build, configure_build, configure_host:
subcommand.add_argument(
"--release",
help="Configure build for release mode",
action="store_true",
default=False,
)
for subcommand in (
build,
configure_build,
Expand All @@ -137,7 +161,7 @@ def main():
build_host,
):
subcommand.add_argument(
"args", nargs="*", help="Extra arguments to pass to `configure`"
"_args", nargs="*", help="Extra arguments to pass to `configure`"
)
for subcommand in build, configure_host, build_host, release:
subcommand.add_argument(
Expand Down Expand Up @@ -167,6 +191,7 @@ def main():
package,
path,
release,
test,
):
subcommand.add_argument(
"--host-triple",
Expand Down Expand Up @@ -219,14 +244,16 @@ def main():
_package.gather(context)
_package.archive(context)
case "release":
context.args = ["--without-static-libpython"]
context.release = True
Comment thread
brettcannon marked this conversation as resolved.
Resolved
_build.clean_contents(context)
_build.configure_build_python(context)
_build.make_build_python(context)
_build.configure_wasi_python(context)
_build.make_wasi_python(context)
_package.gather(context)
_package.archive(context)
case "test":
_test.test(context)
case "path":
paths = {
"build-python": "build_python_path",
Expand Down
157 changes: 19 additions & 138 deletions Platforms/WASI/_build.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
__lazy_modules__ = [
"contextlib",
"functools",
"os",
"pathlib",
"shutil",
"subprocess",
"sys",
"tempfile",
]

import contextlib
import functools
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile

try:
from os import process_cpu_count as cpu_count
Expand All @@ -29,131 +23,19 @@
WASMTIME_HOST_RUNNER_VAR = f"{{{WASMTIME_VAR_NAME}}}"


def separator():
"""Print a separator line across the terminal width."""
try:
tput_output = subprocess.check_output(
["tput", "cols"], encoding="utf-8"
)
except subprocess.CalledProcessError:
terminal_width = 80
else:
terminal_width = int(tput_output.strip())
print("⎯" * terminal_width)


def updated_env(updates={}):
"""Create a new dict representing the environment to use.

The changes made to the execution environment are printed out.
"""
env_defaults = {}
# https://reproducible-builds.org/docs/source-date-epoch/
git_epoch_cmd = ["git", "log", "-1", "--pretty=%ct"]
try:
epoch = subprocess.check_output(
git_epoch_cmd, encoding="utf-8"
).strip()
env_defaults["SOURCE_DATE_EPOCH"] = epoch
except subprocess.CalledProcessError:
pass # Might be building from a tarball.
# This layering lets SOURCE_DATE_EPOCH from os.environ takes precedence.
environment = env_defaults | os.environ | updates

env_diff = {}
for key, value in environment.items():
if os.environ.get(key) != value:
env_diff[key] = value

env_vars = [
f"\n {key}={item}" for key, item in sorted(env_diff.items())
]
_shared.log("🌎", f"Environment changes:{''.join(env_vars)}")

return environment


def subdir(context_attr, *, clean_ok=False):
"""Decorator to change to a working directory."""

def decorator(func):
@functools.wraps(func)
def wrapper(context):
nonlocal context_attr

working_dir = getattr(context, context_attr)
separator()
_shared.log("📁", os.fsdecode(working_dir))
if clean_ok and context.clean and working_dir.exists():
_shared.log("🚮", "Deleting directory (--clean)...")
shutil.rmtree(working_dir)

working_dir.mkdir(parents=True, exist_ok=True)

with contextlib.chdir(working_dir):
return func(context, working_dir)

return wrapper

return decorator


def call(command, *, context=None, quiet=False, **kwargs):
"""Execute a command.

If 'quiet' is true, then redirect stdout and stderr to a temporary file.
"""
if context is not None:
quiet = context.quiet

_shared.log("❯", " ".join(map(str, command)), spacing=" ")
if not quiet:
stdout = None
stderr = None
else:
if (log_path := getattr(context, "log_path", None)) is None:
log_path = pathlib.Path(tempfile.gettempdir())
log_path.mkdir(parents=True, exist_ok=True)
stdout = tempfile.NamedTemporaryFile(
"w",
encoding="utf-8",
delete=False,
dir=log_path,
prefix="cpython-wasi-",
suffix=".log",
)
stderr = subprocess.STDOUT
_shared.log("📝", f"Logging output to {stdout.name} (--quiet)...")

try:
subprocess.check_call(command, **kwargs, stdout=stdout, stderr=stderr)
except subprocess.CalledProcessError as error:
if quiet:
_shared.log("❌", f"Exit code {error.returncode}")
separator()
with open(stdout.name, encoding="utf-8") as file:
lines = file.readlines()
# Inefficient, but the log shouldn't be dramatically large.
print("".join(lines[-10:]), end="")
if not lines[-1].endswith("\n"):
print()
sys.exit(error.returncode)


@subdir("build_python_path", clean_ok=True)
@_shared.subdir("build_python_path", clean_ok=True)
def configure_build_python(context, working_dir):
"""Configure the build/host Python."""
configure = [os.path.relpath(context.checkout / "configure", working_dir)]
if context.args:
configure.extend(context.args)
configure.extend(context.configure_args)

call(configure, context=context)
context.call(configure)


@subdir("build_python_path")
@_shared.subdir("build_python_path")
def make_build_python(context, _working_dir):
"""Make/build the build Python."""
call(["make", "--jobs", str(cpu_count()), "all"], context=context)
context.call(["make", "--jobs", str(cpu_count()), "all"])

binary = context.build_python_interpreter
cmd = [
Expand Down Expand Up @@ -206,7 +88,7 @@ def wasi_sdk_env(context):
return env


@subdir("wasi_build_path", clean_ok=True)
@_shared.subdir("wasi_build_path", clean_ok=True)
def configure_wasi_python(context, working_dir):
"""Configure the WASI/host build."""
config_site = os.fsdecode(context.here / "config.site-wasm32-wasi")
Expand Down Expand Up @@ -247,12 +129,10 @@ def configure_wasi_python(context, working_dir):
]
if context.is_debug:
configure.append("--with-pydebug")
if context.args:
configure.extend(context.args)
call(
configure.extend(context.configure_args)
context.call(
configure,
env=updated_env(env_additions | wasi_sdk_env(context)),
context=context,
env=_shared.updated_env(env_additions | wasi_sdk_env(context)),
)

python_wasm = working_dir / "python.wasm"
Expand All @@ -264,17 +144,18 @@ def configure_wasi_python(context, working_dir):
sys.stdout.flush()


@subdir("wasi_build_path")
@_shared.subdir("wasi_build_path")
def make_wasi_python(context, working_dir):
"""Run `make` for the WASI/host build."""
call(
context.call(
["make", "--jobs", str(cpu_count()), "all"],
env=updated_env(),
context=context,
env=_shared.updated_env(),
)

exec_script = working_dir / "python.sh"
call([exec_script, "-c", "import sys; print(sys.version)"], quiet=False)
context.call(
[exec_script, "-c", "import sys; print(sys.version)"], quiet=False
)
_shared.log(
"🎉",
f"Use `{exec_script.relative_to(pathlib.Path().absolute())}` "
Expand All @@ -291,13 +172,13 @@ def clean_contents(context):
shutil.rmtree(path)


@subdir("build_python_path")
@_shared.subdir("build_python_path")
def pythoninfo_build_python(context, working_dir):
"""Display build info of the build Python."""
call(["make", "pythoninfo"], context=context)
context.call(["make", "pythoninfo"])


@subdir("wasi_build_path")
@_shared.subdir("wasi_build_path")
def pythoninfo_wasi_python(context, working_dir):
"""Display build info of the host/WASI Python."""
call(["make", "pythoninfo"], context=context)
context.call(["make", "pythoninfo"])
Loading
Loading