Skip to content

Commit 75156c0

Browse files
committed
Add WebAssembly build, cockle and terminal deployments, and testing
1 parent e544bc4 commit 75156c0

39 files changed

+805
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ build/
22
__pycache__/
33
.cache/
44
compile_commands.json
5+
serve.log

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,13 @@ The CLI is tested using `python`. From the top-level directory:
2525
```bash
2626
pytest -v
2727
```
28+
29+
# WebAssembly build and deployment
30+
31+
The `wasm` directory contains everything needed to build the local `git2cpp` source code as an
32+
WebAssembly [Emscripten-forge](https://emscripten-forge.org/) package, create local
33+
[cockle](https://github.com/jupyterlite/cockle) and
34+
[JupyterLite terminal](https://github.com/jupyterlite/terminal) deployments that run in a browser,
35+
and test the WebAssembly build.
36+
37+
See the `README.md` in the `wasm` directory for further details.

test/__init__.py

Whitespace-only changes.

test/conftest.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import pytest
44
import subprocess
55

6+
GIT2CPP_TEST_WASM = os.getenv('GIT2CPP_TEST_WASM') == "1"
7+
8+
if GIT2CPP_TEST_WASM:
9+
from .conftest_wasm import *
610

711
# Fixture to run test in current tmp_path
812
@pytest.fixture
@@ -14,7 +18,10 @@ def run_in_tmp_path(tmp_path):
1418

1519
@pytest.fixture(scope='session')
1620
def git2cpp_path():
17-
return Path(__file__).parent.parent / 'build' / 'git2cpp'
21+
if GIT2CPP_TEST_WASM:
22+
return 'git2cpp'
23+
else:
24+
return Path(__file__).parent.parent / 'build' / 'git2cpp'
1825

1926
@pytest.fixture
2027
def xtl_clone(git2cpp_path, tmp_path, run_in_tmp_path):

test/conftest_wasm.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Extra fixtures used for wasm testing.
2+
from functools import partial
3+
from pathlib import Path
4+
from playwright.sync_api import Page
5+
import pytest
6+
import subprocess
7+
import time
8+
9+
@pytest.fixture(scope="session", autouse=True)
10+
def run_web_server():
11+
with open('serve.log', 'w') as f:
12+
cwd = Path(__file__).parent.parent / 'wasm/test'
13+
proc = subprocess.Popen(
14+
['npm', 'run', 'serve'], stdout=f, stderr=f, cwd=cwd
15+
)
16+
# Wait a bit until server ready to receive connections.
17+
time.sleep(0.3)
18+
yield
19+
proc.terminate()
20+
21+
@pytest.fixture(scope="function", autouse=True)
22+
def load_page(page: Page):
23+
# Load web page at start of every test.
24+
page.goto("http://localhost:8000")
25+
page.locator("#loaded").wait_for()
26+
27+
def subprocess_run(
28+
page: Page,
29+
cmd: list[str],
30+
*,
31+
capture_output: bool = False,
32+
cwd: str | None = None,
33+
text: bool | None = None
34+
) -> subprocess.CompletedProcess:
35+
if cwd is not None:
36+
raise RuntimeError('cwd is not yet supported')
37+
38+
proc = page.evaluate("async cmd => window.cockle.shellRun(cmd)", cmd)
39+
# TypeScript object is auto converted to Python dict.
40+
# Want to return subprocess.CompletedProcess, consider namedtuple if this fails in future.
41+
stdout = proc['stdout'] if capture_output else ''
42+
stderr = proc['stderr'] if capture_output else ''
43+
if not text:
44+
stdout = stdout.encode("utf-8")
45+
stderr = stderr.encode("utf-8")
46+
return subprocess.CompletedProcess(
47+
args=cmd,
48+
returncode=proc['returncode'],
49+
stdout=stdout,
50+
stderr=stderr
51+
)
52+
53+
@pytest.fixture(scope="function", autouse=True)
54+
def mock_subprocess_run(page: Page, monkeypatch):
55+
monkeypatch.setattr(subprocess, "run", partial(subprocess_run, page))

wasm/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
serve/cockle/
2+
serve/lite/

wasm/Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
default: build
2+
.PHONY: build clean rebuild serve test
3+
4+
build:
5+
make -C build $@
6+
make -C cockle-deploy $@
7+
make -C lite-deploy $@
8+
make -C test $@
9+
10+
# Rebuild after change in C++ source code.
11+
rebuild:
12+
make -C build $@
13+
make -C cockle-deploy $@
14+
make -C lite-deploy $@
15+
make -C test $@
16+
17+
# Serve both cockle and JupyterLite deployments.
18+
serve:
19+
npx static-handler serve
20+
21+
test:
22+
make -C test $@
23+
24+
clean:
25+
make -C build $@
26+
make -C cockle-deploy $@
27+
make -C lite-deploy $@
28+
make -C test $@

wasm/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Building and testing git2cpp in WebAssembly
2+
3+
This directory contains everything needed to build the local `git2cpp` source code as an
4+
WebAssembly [Emscripten-forge](https://emscripten-forge.org/) package, create local
5+
[cockle](https://github.com/jupyterlite/cockle) and
6+
[JupyterLite terminal](https://github.com/jupyterlite/terminal) deployments that run in a browser,
7+
and test the WebAssembly build.
8+
9+
It works on Linux and macOS but not Windows.
10+
11+
There are 5 sub-directories:
12+
13+
- `build`: build local `git2cpp` source code into an Emscripten-forge package.
14+
- `cockle-deploy`: create a `cockle` deployment in the `serve` directory.
15+
- `lite-deploy`: create a JupyterLite `terminal` deployment in the `serve` directory.
16+
- `serve`: where the two deployments are served from.
17+
- `test`: test the WebAssembly build.
18+
19+
## Build and deploy
20+
21+
The build, deploy and test process uses a separate `micromamba` environment defined in
22+
`wasm-environment.yml`. To set this up use from within this directory:
23+
24+
```bash
25+
micromamba create -f wasm-environment.yml
26+
micromamba activate git2cpp-wasm
27+
```
28+
29+
Then to build the WebAssembly package, both deployments and the testing resources use:
30+
31+
```bash
32+
make
33+
```
34+
35+
The local deployments in the `serve` directory can be manually checked using:
36+
37+
```bash
38+
make serve
39+
```
40+
41+
and open a web browser at http://localhost:8080/. Confirm that the local build of `git2cpp` is being
42+
used in the two deployments by running `cockle-config package` at the command line, the output
43+
should be something like:
44+
45+
<img alt="cockle-config output" src="cockle-config.png">
46+
47+
Note that the `source` for the `git2cpp` package is the local filesystem rather than from
48+
`prefix.dev`. The version number of `git2cpp` in this table is not necessarily correct as it is the
49+
version number of the current Emscripten-forge recipe rather than the version of the local `git2cpp`
50+
source code which can be checked using `git2cpp -v` at the `cockle`/`terminal` command line.
51+
52+
## Test
53+
54+
To test the WebAssembly build use:
55+
56+
```bash
57+
make test
58+
```
59+
60+
This runs (some of) the tests in the top-level `test` directory with various monkey patching so that
61+
`git2cpp` commands are executed in the browser.
62+
63+
## Rebuild
64+
65+
After making changes to the local `git2cpp` source code you can rebuild the WebAssembly package,
66+
both deployments and test code using:
67+
68+
```bash
69+
make rebuild
70+
```

wasm/build/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
em-forge-recipes/

wasm/build/Makefile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
default: build
2+
.PHONY: build clean clean-output-dir modify-recipe rebuild
3+
4+
include ../common.mk
5+
6+
EM_FORGE_RECIPES_REPO = https://github.com/emscripten-forge/recipes
7+
GIT2CPP_RECIPE_DIR = recipes/recipes_emscripten/git2cpp
8+
RATTLER_ARGS = --package-format tar-bz2 --target-platform emscripten-wasm32 -c https://repo.prefix.dev/emscripten-forge-dev -c microsoft -c conda-forge
9+
10+
# Only want the git2cpp recipe from emscripten-forge/recipes repo, not the whole repo.
11+
# Note removing the .git directory otherwise `git clean -fxd` will not remove the directory.
12+
$(EM_FORGE_RECIPES_DIR):
13+
git clone --no-checkout ${EM_FORGE_RECIPES_REPO} --depth 1 $@
14+
cd $@ && git sparse-checkout init
15+
cd $@ && git sparse-checkout set $(GIT2CPP_RECIPE_DIR)
16+
cd $@ && git checkout main
17+
rm -rf $@/.git
18+
19+
modify-recipe: $(EM_FORGE_RECIPES_DIR)
20+
python modify-recipe.py $(EM_FORGE_RECIPES_DIR)/$(GIT2CPP_RECIPE_DIR)
21+
22+
build: modify-recipe
23+
cd $(EM_FORGE_RECIPES_DIR) && rattler-build build $(RATTLER_ARGS) --recipe $(GIT2CPP_RECIPE_DIR)
24+
25+
rebuild: clean-output-dir build
26+
27+
clean:
28+
rm -rf $(EM_FORGE_RECIPES_DIR)
29+
30+
clean-output-dir:
31+
rm -rf $(BUILT_PACKAGE_DIR)

0 commit comments

Comments
 (0)