Skip to content

Commit 68bbcec

Browse files
hoodmanehugovk
andcommitted
[3.14] gh-146197: Add Emscripten to CI (GH-146198)
(cherry picked from commit c94048b) Co-authored-by: Hood Chatham <roberthoodchatham@gmail.com> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
1 parent f883bbd commit 68bbcec

File tree

6 files changed

+123
-6
lines changed

6 files changed

+123
-6
lines changed

.github/workflows/build.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ jobs:
386386
- name: Build and test
387387
run: python3 Apple ci iOS --fast-ci --simulator 'iPhone SE (3rd generation),OS=17.5'
388388

389+
build-emscripten:
390+
name: 'Emscripten'
391+
needs: build-context
392+
if: needs.build-context.outputs.run-emscripten == 'true'
393+
uses: ./.github/workflows/reusable-emscripten.yml
394+
389395
build-wasi:
390396
name: 'WASI'
391397
needs: build-context
@@ -664,6 +670,7 @@ jobs:
664670
- build-ubuntu
665671
- build-ubuntu-ssltests
666672
- build-ios
673+
- build-emscripten
667674
- build-wasi
668675
- test-hypothesis
669676
- build-asan
@@ -678,6 +685,7 @@ jobs:
678685
with:
679686
allowed-failures: >-
680687
build-android,
688+
build-emscripten,
681689
build-windows-msi,
682690
build-ubuntu-ssltests,
683691
test-hypothesis,
@@ -714,5 +722,6 @@ jobs:
714722
}}
715723
${{ !fromJSON(needs.build-context.outputs.run-android) && 'build-android,' || '' }}
716724
${{ !fromJSON(needs.build-context.outputs.run-ios) && 'build-ios,' || '' }}
725+
${{ !fromJSON(needs.build-context.outputs.run-emscripten) && 'build-emscripten,' || '' }}
717726
${{ !fromJSON(needs.build-context.outputs.run-wasi) && 'build-wasi,' || '' }}
718727
jobs: ${{ toJSON(needs) }}

.github/workflows/reusable-context.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ on: # yamllint disable-line rule:truthy
4141
run-ubuntu:
4242
description: Whether to run the Ubuntu tests
4343
value: ${{ jobs.compute-changes.outputs.run-ubuntu }} # bool
44+
run-emscripten:
45+
description: Whether to run the Emscripten tests
46+
value: ${{ jobs.compute-changes.outputs.run-emscripten }} # bool
4447
run-wasi:
4548
description: Whether to run the WASI tests
4649
value: ${{ jobs.compute-changes.outputs.run-wasi }} # bool
@@ -65,6 +68,7 @@ jobs:
6568
run-macos: ${{ steps.changes.outputs.run-macos }}
6669
run-tests: ${{ steps.changes.outputs.run-tests }}
6770
run-ubuntu: ${{ steps.changes.outputs.run-ubuntu }}
71+
run-emscripten: ${{ steps.changes.outputs.run-emscripten }}
6872
run-wasi: ${{ steps.changes.outputs.run-wasi }}
6973
run-windows-msi: ${{ steps.changes.outputs.run-windows-msi }}
7074
run-windows-tests: ${{ steps.changes.outputs.run-windows-tests }}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Reusable Emscripten
2+
3+
on:
4+
workflow_call:
5+
6+
env:
7+
FORCE_COLOR: 1
8+
9+
jobs:
10+
build-emscripten-reusable:
11+
name: 'build and test'
12+
runs-on: ubuntu-24.04
13+
timeout-minutes: 60
14+
steps:
15+
- uses: actions/checkout@v6
16+
with:
17+
persist-credentials: false
18+
- name: "Read Emscripten config"
19+
id: emscripten-config
20+
shell: python
21+
run: |
22+
import hashlib
23+
import json
24+
import os
25+
import tomllib
26+
from pathlib import Path
27+
28+
config = tomllib.loads(Path("Platforms/emscripten/config.toml").read_text())
29+
h = hashlib.sha256()
30+
h.update(json.dumps(config["dependencies"], sort_keys=True).encode())
31+
h.update(Path("Platforms/emscripten/make_libffi.sh").read_bytes())
32+
h.update(b'1') # Update to explicitly bust cache
33+
emsdk_cache = Path(os.environ["RUNNER_TEMP"]) / "emsdk-cache"
34+
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
35+
f.write(f"emscripten-version={config['emscripten-version']}\n")
36+
f.write(f"node-version={config['node-version']}\n")
37+
f.write(f"deps-hash={h.hexdigest()}\n")
38+
with open(os.environ["GITHUB_ENV"], "a") as f:
39+
f.write(f"EMSDK_CACHE={emsdk_cache}\n")
40+
- name: "Install Node.js"
41+
uses: actions/setup-node@v6
42+
with:
43+
node-version: ${{ steps.emscripten-config.outputs.node-version }}
44+
- name: "Cache Emscripten SDK"
45+
id: emsdk-cache
46+
uses: actions/cache@v5
47+
with:
48+
path: ${{ env.EMSDK_CACHE }}
49+
key: emsdk-${{ steps.emscripten-config.outputs.emscripten-version }}-${{ steps.emscripten-config.outputs.deps-hash }}
50+
restore-keys: emsdk-${{ steps.emscripten-config.outputs.emscripten-version }}
51+
- name: "Install Python"
52+
uses: actions/setup-python@v6
53+
with:
54+
python-version: '3.x'
55+
- name: "Runner image version"
56+
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
57+
- name: "Install Emscripten"
58+
run: python3 Platforms/emscripten install-emscripten
59+
- name: "Configure build Python"
60+
run: python3 Platforms/emscripten configure-build-python -- --config-cache --with-pydebug
61+
- name: "Make build Python"
62+
run: python3 Platforms/emscripten make-build-python
63+
- name: "Make dependencies"
64+
run: >-
65+
python3 Platforms/emscripten make-dependencies
66+
${{ steps.emsdk-cache.outputs.cache-hit == 'true' && '--check-up-to-date' || '' }}
67+
- name: "Configure host Python"
68+
run: python3 Platforms/emscripten configure-host --host-runner node -- --config-cache
69+
- name: "Make host Python"
70+
run: python3 Platforms/emscripten make-host
71+
- name: "Test"
72+
run: python3 Platforms/emscripten run --test

Platforms/emscripten/__main__.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,18 @@ def write_library_config(prefix, name, config, quiet):
350350
def make_emscripten_libffi(context, working_dir):
351351
validate_emsdk_version(context.emsdk_cache)
352352
prefix = context.build_paths["prefix_dir"]
353-
libffi_config = load_config_toml()["libffi"]
353+
libffi_config = load_config_toml()["dependencies"]["libffi"]
354+
with open(EMSCRIPTEN_DIR / "make_libffi.sh", "rb") as f:
355+
libffi_config["make_libffi_shasum"] = hashlib.file_digest(f, "sha256").hexdigest()
354356
if not should_build_library(
355357
prefix, "libffi", libffi_config, context.quiet
356358
):
357359
return
360+
361+
if context.check_up_to_date:
362+
print("libffi out of date, expected to be up to date", file=sys.stderr)
363+
sys.exit(1)
364+
358365
url = libffi_config["url"]
359366
version = libffi_config["version"]
360367
shasum = libffi_config["shasum"]
@@ -378,10 +385,14 @@ def make_emscripten_libffi(context, working_dir):
378385
def make_mpdec(context, working_dir):
379386
validate_emsdk_version(context.emsdk_cache)
380387
prefix = context.build_paths["prefix_dir"]
381-
mpdec_config = load_config_toml()["mpdec"]
388+
mpdec_config = load_config_toml()["dependencies"]["mpdec"]
382389
if not should_build_library(prefix, "mpdec", mpdec_config, context.quiet):
383390
return
384391

392+
if context.check_up_to_date:
393+
print("libmpdec out of date, expected to be up to date", file=sys.stderr)
394+
sys.exit(1)
395+
385396
url = mpdec_config["url"]
386397
version = mpdec_config["version"]
387398
shasum = mpdec_config["shasum"]
@@ -678,6 +689,14 @@ def main():
678689
help="Build all static library dependencies",
679690
)
680691

692+
for cmd in [make_mpdec_cmd, make_libffi_cmd, make_dependencies_cmd]:
693+
cmd.add_argument(
694+
"--check-up-to-date",
695+
action="store_true",
696+
default=False,
697+
help=("If passed, will fail if dependency is out of date"),
698+
)
699+
681700
make_build = subcommands.add_parser(
682701
"make-build-python", help="Run `make` for the build Python"
683702
)
@@ -705,15 +724,15 @@ def main():
705724
help=(
706725
"If passed, will add the default test arguments to the beginning of the command. "
707726
"Default arguments loaded from Platforms/emscripten/config.toml"
708-
)
727+
),
709728
)
710729
run.add_argument(
711730
"args",
712731
nargs=argparse.REMAINDER,
713732
help=(
714733
"Arguments to pass to the emscripten Python "
715734
"(use '--' to separate from run options)",
716-
)
735+
),
717736
)
718737
add_cross_build_dir_option(run)
719738

Platforms/emscripten/config.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ test-args = [
1212
"-W",
1313
]
1414

15-
[libffi]
15+
[dependencies.libffi]
1616
url = "https://github.com/libffi/libffi/releases/download/v{version}/libffi-{version}.tar.gz"
1717
version = "3.4.6"
1818
shasum = "b0dea9df23c863a7a50e825440f3ebffabd65df1497108e5d437747843895a4e"
1919

20-
[mpdec]
20+
[dependencies.mpdec]
2121
url = "https://www.bytereef.org/software/mpdecimal/releases/mpdecimal-{version}.tar.gz"
2222
version = "4.0.1"
2323
shasum = "96d33abb4bb0070c7be0fed4246cd38416188325f820468214471938545b1ac8"

Tools/build/compute-changes.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
SUFFIXES_DOCUMENTATION = frozenset({".rst", ".md"})
4949

5050
ANDROID_DIRS = frozenset({"Android"})
51+
EMSCRIPTEN_DIRS = frozenset({Path("Platforms", "emscripten")})
5152
IOS_DIRS = frozenset({"Apple", "iOS"})
5253
MACOS_DIRS = frozenset({"Mac"})
5354
WASI_DIRS = frozenset({Path("Tools", "wasm")})
@@ -106,6 +107,7 @@ class Outputs:
106107
run_ci_fuzz: bool = False
107108
run_ci_fuzz_stdlib: bool = False
108109
run_docs: bool = False
110+
run_emscripten: bool = False
109111
run_ios: bool = False
110112
run_macos: bool = False
111113
run_tests: bool = False
@@ -125,6 +127,7 @@ def compute_changes() -> None:
125127
# Otherwise, just run the tests
126128
outputs = Outputs(
127129
run_android=True,
130+
run_emscripten=True,
128131
run_ios=True,
129132
run_macos=True,
130133
run_tests=True,
@@ -194,6 +197,8 @@ def get_file_platform(file: Path) -> str | None:
194197
return "ios"
195198
if first_part in ANDROID_DIRS:
196199
return "android"
200+
if len(file.parts) >= 2 and Path(*file.parts[:2]) in EMSCRIPTEN_DIRS:
201+
return "emscripten"
197202
if len(file.parts) >= 2 and Path(*file.parts[:2]) in WASI_DIRS: # Tools/wasm/
198203
return "wasi"
199204
return None
@@ -242,6 +247,10 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
242247
run_tests = True
243248
platforms_changed.add("macos")
244249
continue
250+
if file.name == "reusable-emscripten.yml":
251+
run_tests = True
252+
platforms_changed.add("emscripten")
253+
continue
245254
if file.name == "reusable-wasi.yml":
246255
run_tests = True
247256
platforms_changed.add("wasi")
@@ -282,18 +291,21 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
282291
if run_tests:
283292
if not has_platform_specific_change or not platforms_changed:
284293
run_android = True
294+
run_emscripten = True
285295
run_ios = True
286296
run_macos = True
287297
run_ubuntu = True
288298
run_wasi = True
289299
else:
290300
run_android = "android" in platforms_changed
301+
run_emscripten = "emscripten" in platforms_changed
291302
run_ios = "ios" in platforms_changed
292303
run_macos = "macos" in platforms_changed
293304
run_ubuntu = False
294305
run_wasi = "wasi" in platforms_changed
295306
else:
296307
run_android = False
308+
run_emscripten = False
297309
run_ios = False
298310
run_macos = False
299311
run_ubuntu = False
@@ -304,6 +316,7 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
304316
run_ci_fuzz=run_ci_fuzz,
305317
run_ci_fuzz_stdlib=run_ci_fuzz_stdlib,
306318
run_docs=run_docs,
319+
run_emscripten=run_emscripten,
307320
run_ios=run_ios,
308321
run_macos=run_macos,
309322
run_tests=run_tests,

0 commit comments

Comments
 (0)