├── .fossil-settings └── ignore-glob ├── 2.7.10 ├── package-pythonhome.sh ├── patches │ ├── python2-cross_compile.patch │ ├── python2-no-openssl.patch │ ├── python2-no_popen.patch │ ├── python2-static_submodules.patch │ ├── python2-webbrowser.patch │ └── series ├── python.sh └── webprompt.sh ├── 2.7.18 ├── package-pythonhome.sh ├── patches │ ├── python2-cross_compile.patch │ ├── python2-no_popen.patch │ ├── python2-webbrowser.patch │ └── series ├── python.sh └── webprompt.sh ├── 3.8 ├── package-pythonhome.sh ├── patches │ ├── disable-set_inheritable.patch │ ├── python3-cross_compile.patch │ └── series ├── python.sh └── webprompt.sh ├── LICENSE ├── README.md ├── emscripten.pyx ├── emscripten_fetch.pyx ├── git-export.sh ├── mock ├── emscripten.pyx └── emscripten_fetch.pyx ├── static_submodules-test.c ├── webprompt-main.c └── webprompt-shell.html /.fossil-settings/ignore-glob: -------------------------------------------------------------------------------- 1 | */build/ 2 | */crosspython-static/ 3 | */crosspython-dynamic/ 4 | */destdir* 5 | */package/ 6 | */Python-*.tgz 7 | */Python-*.tar.xz 8 | */setuptools-*.zip 9 | */t/ 10 | *.bak 11 | *~ 12 | -------------------------------------------------------------------------------- /2.7.10/package-pythonhome.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Creates a minimal Python file hierarchy at $PACKAGEDIR 4 | 5 | # Copyright (C) 2018, 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | FILE_PACKAGER="python3 $(dirname $(which emcc))/tools/file_packager.py" 13 | 14 | PREFIX=${PREFIX:-$(dirname $(readlink -f $0))/destdir} 15 | PACKAGEDIR=${PACKAGEDIR:-$(dirname $(readlink -f $0))/package} 16 | OUTDIR=${OUTDIR:-.} 17 | CROSSPYTHON=$(dirname $(readlink -f $0))/crosspython-static/bin/python 18 | 19 | # Python home 20 | 21 | # optional lz4 compression, requires '-s LZ4=1' 22 | LZ4= 23 | if [ "$1" == "--lz4" ]; then LZ4="--lz4"; shift; fi 24 | 25 | rm -rf $PACKAGEDIR/ 26 | mkdir -p $PACKAGEDIR 27 | 28 | # Hard-coded modules: for 'print("hello, world.")' 29 | # $@: additional, app-specific modules 30 | for i in site.py os.py posixpath.py stat.py genericpath.py warnings.py linecache.py types.py UserDict.py _abcoll.py abc.py _weakrefset.py copy_reg.py traceback.py sysconfig.py re.py sre_compile.py sre_parse.py sre_constants.py encodings/__init__.py codecs.py encodings/aliases.py encodings/utf_8.py __future__.py ast.py copy.py weakref.py platform.py string.py io.py tempfile.py random.py hashlib.py struct.py dummy_thread.py collections.py keyword.py heapq.py argparse.py textwrap.py gettext.py locale.py functools.py importlib/__init__.py glob.py fnmatch.py pickle.py colorsys.py contextlib.py zipfile.py shutil.py json/__init__.py json/decoder.py json/scanner.py encodings/hex_codec.py json/encoder.py difflib.py inspect.py dis.py opcode.py tokenize.py token.py xml/__init__.py xml/etree/__init__.py xml/etree/ElementTree.py xml/etree/ElementPath.py encodings/zlib_codec.py tarfile.py urlparse.py StringIO.py encodings/latin_1.py encodings/ascii.py \ 31 | "$@"; do 32 | if [ $PREFIX/lib/python2.7/$i -nt $PREFIX/lib/python2.7/${i%.py}.pyo ]; then 33 | (cd $PREFIX && $CROSSPYTHON -OO -m py_compile lib/python2.7/$i) 34 | fi 35 | mkdir -p $PACKAGEDIR/lib/python2.7/$(dirname $i) 36 | cp -au $PREFIX/lib/python2.7/${i%.py}.pyo $PACKAGEDIR/lib/python2.7/${i%.py}.pyo 37 | done 38 | # Large and leaks build paths, clean it: 39 | echo 'build_time_vars = {}' > $PACKAGEDIR/lib/python2.7/_sysconfigdata.py 40 | (cd $PACKAGEDIR && $CROSSPYTHON -OO -m py_compile lib/python2.7/_sysconfigdata.py) 41 | rm -f $PACKAGEDIR/lib/python2.7/_sysconfigdata.py 42 | 43 | # --no-heap-copy: suited for ALLOW_MEMORY_GROWTH=1 44 | PACKAGEDIR_FULLPATH=$(readlink -f $PACKAGEDIR) 45 | ( 46 | cd $OUTDIR; # use relative path in xxx-data.js 47 | $FILE_PACKAGER \ 48 | pythonhome.data --js-output=pythonhome-data.js \ 49 | --preload $PACKAGEDIR_FULLPATH@/ \ 50 | --use-preload-cache --no-heap-copy $LZ4 51 | ) 52 | -------------------------------------------------------------------------------- /2.7.10/patches/python2-cross_compile.patch: -------------------------------------------------------------------------------- 1 | Description: Fix build system for Emscripten cross-compilation. 2 | Also adds ac_cv_func_dlopen=yes to support dynamic linking. 3 | Forwarded: no 4 | Author: Marat Dukhan , Sylvain Beucler 5 | Origin: https://github.com/PeachPy/Python-2.7 6 | Last-Update: 2018-10-28 7 | 8 | commit 5b7a8e46d129e576ad9298055bb4e695aea29cb6 9 | Author: Marat Dukhan 10 | Date: Mon Oct 5 16:58:19 2015 -0400 11 | 12 | Make config.sub recognize asmjs-unknown-emscripten target 13 | 14 | diff --git a/config.sub b/config.sub 15 | index d654d03..0d8236f 100755 16 | --- a/config.sub 17 | +++ b/config.sub 18 | @@ -119,7 +119,8 @@ case $maybe_os in 19 | linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ 20 | knetbsd*-gnu* | netbsd*-gnu* | \ 21 | kopensolaris*-gnu* | \ 22 | - storm-chaos* | os2-emx* | rtmk-nova*) 23 | + storm-chaos* | os2-emx* | rtmk-nova* | \ 24 | + emscripten) 25 | os=-$maybe_os 26 | basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` 27 | ;; 28 | @@ -254,6 +255,7 @@ case $basic_machine in 29 | | am33_2.0 \ 30 | | arc | arceb \ 31 | | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ 32 | + | asmjs \ 33 | | avr | avr32 \ 34 | | be32 | be64 \ 35 | | bfin \ 36 | @@ -1510,6 +1512,8 @@ case $os in 37 | -dicos*) 38 | os=-dicos 39 | ;; 40 | + -emscripten) 41 | + ;; 42 | -nacl*) 43 | ;; 44 | -none) 45 | commit 600f9b7a4b77ff3270e36efac222347d8f175d66 46 | Author: Marat Dukhan 47 | Date: Fri Jan 22 02:04:07 2016 -0500 48 | 49 | Re-generate configure script 50 | 51 | configure updated with `autoreconf` 52 | 53 | diff --git a/configure b/configure 54 | index 8cf777e..263719f 100755 55 | --- a/configure 56 | +++ b/configure 57 | @@ -3202,6 +3202,9 @@ then 58 | *-*-cygwin*) 59 | ac_sys_system=Cygwin 60 | ;; 61 | + asmjs-*-*) 62 | + ac_sys_system=Emscripten 63 | + ;; 64 | *) 65 | # for now, limit cross builds to known configurations 66 | MACHDEP="unknown" 67 | @@ -3248,6 +3251,9 @@ if test "$cross_compiling" = yes; then 68 | *-*-cygwin*) 69 | _host_cpu= 70 | ;; 71 | + asmjs-*-*) 72 | + _host_cpu= 73 | + ;; 74 | *) 75 | # for now, limit cross builds to known configurations 76 | MACHDEP="unknown" 77 | commit 6743219e7de04c33667ecdaa879db51bc68cf7a9 78 | Author: Marat Dukhan 79 | Date: Mon Oct 5 16:58:59 2015 -0400 80 | 81 | Make configure.ac recognize asmjs-*-* target 82 | 83 | diff --git a/configure.ac b/configure.ac 84 | index 78fe3c7..d7665b4 100644 85 | --- a/configure.ac 86 | +++ b/configure.ac 87 | @@ -322,6 +322,9 @@ then 88 | *-*-cygwin*) 89 | ac_sys_system=Cygwin 90 | ;; 91 | + asmjs-*-*) 92 | + ac_sys_system=Emscripten 93 | + ;; 94 | *) 95 | # for now, limit cross builds to known configurations 96 | MACHDEP="unknown" 97 | @@ -368,6 +371,9 @@ if test "$cross_compiling" = yes; then 98 | *-*-cygwin*) 99 | _host_cpu= 100 | ;; 101 | + asmjs-*-*) 102 | + _host_cpu= 103 | + ;; 104 | *) 105 | # for now, limit cross builds to known configurations 106 | MACHDEP="unknown" 107 | commit 2011acd27dbe95c74aca73eb126578e51ee716de 108 | Author: Marat Dukhan 109 | Date: Tue Nov 17 13:46:47 2015 -0500 110 | 111 | Add config.site 112 | 113 | More details in https://bugs.python.org/msg136962 114 | 115 | diff --git a/config.site b/config.site 116 | new file mode 100644 117 | index 0000000..c273024 118 | --- /dev/null 119 | +++ b/config.site 120 | @@ -0,0 +1,3 @@ 121 | +ac_cv_file__dev_ptmx=no 122 | +ac_cv_file__dev_ptc=no 123 | +ac_cv_func_dlopen=yes 124 | -------------------------------------------------------------------------------- /2.7.10/patches/python2-no-openssl.patch: -------------------------------------------------------------------------------- 1 | Ignore OpenSSL detection: Python 2.7.10 uses openssl 1.0.2 but tries 2 | to compile with 1.1 if present, and compilation fails. 3 | 4 | Taken from 5 | https://github.com/renpy/renpy-deps/blob/master/source/python-minimize-openssl.diff 6 | 7 | --- a/setup.py 2015-05-23 12:09:25.000000000 -0400 8 | +++ b/setup.py 2017-11-02 23:20:09.131537016 -0400 9 | @@ -842,7 +842,7 @@ 10 | have_usable_openssl = (have_any_openssl and 11 | openssl_ver >= min_openssl_ver) 12 | 13 | - if have_any_openssl: 14 | + if False and have_any_openssl: 15 | if have_usable_openssl: 16 | # The _hashlib module wraps optimized implementations 17 | # of hash functions from the OpenSSL library. 18 | @@ -854,7 +854,7 @@ 19 | print ("warning: openssl 0x%08x is too old for _hashlib" % 20 | openssl_ver) 21 | missing.append('_hashlib') 22 | - if COMPILED_WITH_PYDEBUG or not have_usable_openssl: 23 | + if True or COMPILED_WITH_PYDEBUG or not have_usable_openssl: 24 | # The _sha module implements the SHA1 hash algorithm. 25 | exts.append( Extension('_sha', ['shamodule.c']) ) 26 | # The _md5 module implements the RSA Data Security, Inc. MD5 27 | @@ -865,7 +865,7 @@ 28 | depends = ['md5.h']) ) 29 | 30 | min_sha2_openssl_ver = 0x00908000 31 | - if COMPILED_WITH_PYDEBUG or openssl_ver < min_sha2_openssl_ver: 32 | + if True or COMPILED_WITH_PYDEBUG or openssl_ver < min_sha2_openssl_ver: 33 | # OpenSSL doesn't do these until 0.9.8 so we'll bring our own hash 34 | exts.append( Extension('_sha256', ['sha256module.c']) ) 35 | exts.append( Extension('_sha512', ['sha512module.c']) ) 36 | -------------------------------------------------------------------------------- /2.7.10/patches/python2-no_popen.patch: -------------------------------------------------------------------------------- 1 | Description: Fix popen-related issues with Emscripten 2 | warning: unresolved symbol: popen [emcc] 3 | missing function: popen [_popen->abort at runtime] 4 | Forwarded: no 5 | Author: Sylvain Beucler 6 | Last-Update: 2018-10-28 7 | 8 | 9 | --- Python-2.7.10/Modules/posixmodule.c-dist 2015-05-23 18:09:20.000000000 +0200 10 | +++ Python-2.7.10/Modules/posixmodule.c 2018-06-24 22:20:45.802694964 +0200 11 | @@ -151,8 +151,10 @@ 12 | #define HAVE_OPENDIR 1 13 | #define HAVE_PIPE 1 14 | #ifndef __rtems__ 15 | +#ifndef __EMSCRIPTEN__ 16 | #define HAVE_POPEN 1 17 | #endif 18 | +#endif 19 | #define HAVE_SYSTEM 1 20 | #define HAVE_WAIT 1 21 | #define HAVE_TTYNAME 1 22 | -------------------------------------------------------------------------------- /2.7.10/patches/python2-static_submodules.patch: -------------------------------------------------------------------------------- 1 | Description: support static submodules (with dots in their path) 2 | Forwarded: not-needed 3 | Author: Gabriel Jacobo, Sylvain Beucler 4 | Origin: https://mdqinc.com/blog/2011/08/statically-linking-python-with-cython-generated-modules-and-packages/ 5 | Last-Update: 2018-10-28 6 | 7 | For a pure-Python approach, see: 8 | https://github.com/renpy/renpyweb/blob/d78e427ddf1f8b8ae8e93d7f1b77aab76036f481/main.c#L230 9 | 10 | --- Python-2.7.10/Python/import.c 2015-05-23 18:09:24.000000000 +0200 11 | +++ Python-2.7.10/Python/import.c-patched 2018-06-22 17:59:32.148285493 +0200 12 | @@ -1362,6 +1362,13 @@ 13 | Py_DECREF(meta_path); 14 | } 15 | 16 | + // printf("PATCH: find_module(%s)\n", fullname); 17 | + if (fullname != NULL && is_builtin(fullname)) { 18 | + // printf("PATCH: submodule %s is built-in!\n", fullname); 19 | + strcpy(buf, fullname); 20 | + return &fd_builtin; 21 | + } 22 | + 23 | if (path != NULL && PyString_Check(path)) { 24 | /* The only type of submodule allowed inside a "frozen" 25 | package are other frozen modules or packages. */ 26 | -------------------------------------------------------------------------------- /2.7.10/patches/python2-webbrowser.patch: -------------------------------------------------------------------------------- 1 | Description: Support opening new browser tabs from Python/Emscripten 2 | Forwarded: no 3 | Author: Sylvain Beucler 4 | Last-Update: 2018-10-28 5 | 6 | --- Python-2.7.10/Lib/webbrowser.py 2018-10-19 21:48:22.701879860 +0200 7 | +++ Python-2.7.10/Lib/webbrowser.py 2018-10-19 21:53:22.680671785 +0200 8 | @@ -6,7 +6,7 @@ 9 | import shlex 10 | import sys 11 | import stat 12 | -import subprocess 13 | +#import subprocess 14 | import time 15 | 16 | __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] 17 | @@ -656,6 +656,16 @@ 18 | GenericBrowser(["start", "netscape", "%s"]), -1) 19 | 20 | 21 | +class EmscriptenBrowser(BaseBrowser): 22 | + def open(self, url, new=0, autoraise=True): 23 | + import emscripten 24 | + emscripten.run_script("""try { window.open('%s', '_blank'); } catch (e) { console.log(e); }""" % url.replace("'", "%27")) 25 | + return True 26 | + 27 | +if sys.platform == 'emscripten': 28 | + register("emscripten", None, EmscriptenBrowser()) 29 | + 30 | + 31 | # OK, now that we know what the default preference orders for each 32 | # platform are, allow user to override them with the BROWSER variable. 33 | if "BROWSER" in os.environ: 34 | -------------------------------------------------------------------------------- /2.7.10/patches/series: -------------------------------------------------------------------------------- 1 | python2-cross_compile.patch 2 | python2-no_popen.patch 3 | #python2-static_submodules.patch 4 | python2-webbrowser.patch 5 | python2-no-openssl.patch 6 | -------------------------------------------------------------------------------- /2.7.10/python.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Compile minimal Python for Emscripten and native local testing 4 | 5 | # Copyright (C) 2018, 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | VERSION=2.7.10 # for end-of-life Python2, support Ren'Py's version only 13 | SCRIPTDIR=$(dirname $(readlink -f $0)) 14 | DESTDIR=${DESTDIR:-$SCRIPTDIR/destdir} 15 | SETUPLOCAL=${SETUPLOCAL:-'/dev/null'} 16 | 17 | CACHEROOT=$SCRIPTDIR 18 | BUILD=$SCRIPTDIR/build 19 | export QUILT_PATCHES=$(dirname $(readlink -f $0))/patches 20 | 21 | WGET=${WGET:-wget} 22 | 23 | unpack () { 24 | $WGET -c https://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz -P $CACHEROOT/ 25 | mkdir -p $BUILD 26 | cd $BUILD/ 27 | rm -rf Python-$VERSION/ 28 | tar xf $CACHEROOT/Python-$VERSION.tgz 29 | cd Python-$VERSION/ 30 | quilt push -a 31 | } 32 | 33 | # use cases: 34 | # - python/pgen/.pyo for emscripten() below 35 | # - common basis for crosspython below 36 | hostpython () { 37 | cd $BUILD/Python-$VERSION/ 38 | mkdir -p native 39 | ( 40 | cd native/ 41 | if [ ! -e config.status ]; then 42 | # --without-pymalloc: prevents segfault during 'make' (sysconfigdata) 43 | ../configure \ 44 | --prefix='' \ 45 | --without-pymalloc 46 | fi 47 | 48 | make -j$(nproc) 49 | make install DESTDIR=$BUILD/hostpython 50 | ) 51 | 52 | # setuptools for 3rd-party module installers 53 | wget -c https://files.pythonhosted.org/packages/11/0a/7f13ef5cd932a107cd4c0f3ebc9d831d9b78e1a0e8c98a098ca17b1d7d97/setuptools-41.6.0.zip -P $CACHEROOT/ 54 | cd $BUILD/hostpython/ 55 | rm -rf setuptools-41.6.0/ 56 | unzip $CACHEROOT/setuptools-41.6.0.zip 57 | cd setuptools-41.6.0/ 58 | ../bin/python setup.py install 59 | } 60 | 61 | emscripten () { 62 | cd $BUILD/Python-$VERSION/ 63 | mkdir -p emscripten 64 | ( 65 | cd emscripten/ 66 | # OPT=-Oz: TODO 67 | # CONFIG_SITE: deals with cross-compilation https://bugs.python.org/msg136962 68 | # not needed as emcc has a single arch: BASECFLAGS=-m32 LDFLAGS=-m32 69 | # PATH: detect our Python, beware of conflict with emcc's python3 70 | # don't use PYTHON_FOR_BUILD which is high-level / lots of options 71 | # --without-threads: pthreads experimental as of 2020-05 72 | # cf. https://emscripten.org/docs/porting/pthreads.html 73 | # --without-signal-module: no process signals in Emscripten 74 | # --without-pymalloc: ? 75 | # --disable-ipv6: browser-side networking 76 | # --disable-shared: compile statically for Emscripten perfs + incomplete PIC support 77 | if [ ! -e config.status ]; then 78 | CONFIG_SITE=../config.site \ 79 | BASECFLAGS='-s USE_ZLIB=1' LDFLAGS='-s USE_ZLIB=1' \ 80 | PATH=$BUILD/Python-$VERSION/native:$PATH \ 81 | emconfigure ../configure \ 82 | --host=asmjs-unknown-emscripten --build=$(../config.guess) \ 83 | --prefix='' \ 84 | --without-threads --without-pymalloc --without-signal-module --disable-ipv6 \ 85 | --disable-shared 86 | fi 87 | 88 | # pgen native setup 89 | # note: need to build 'pgen' once before overwriting it with the native one 90 | # note: PGEN=../native/Parser/pgen doesn't work, make overwrites it 91 | emmake make Parser/pgen 92 | \cp --preserve=mode ../native/Parser/pgen Parser/ 93 | 94 | # Modules/Setup.local 95 | echo '*static*' > Modules/Setup.local 96 | cat $SETUPLOCAL >> Modules/Setup.local 97 | # drop -I/-L/-lz, we USE_ZLIB=1 (keep it in SETUPLOCAL for mock) 98 | sed -i -e 's/^\(zlib zlibmodule.c\).*/\1/' Modules/Setup.local 99 | emmake make Makefile 100 | 101 | ( 102 | export PATH=$BUILD/Python-$VERSION/native:$PATH 103 | emmake make -j$(nproc) 104 | # setup.py install_lib doesn't respect DESTDIR 105 | echo -e 'sharedinstall:\n\ttrue' >> Makefile 106 | # decrease .pyo size by dropping docstrings 107 | sed -i -e '/compileall.py/ s/ -O / -OO /' Makefile 108 | emmake make install DESTDIR=$DESTDIR 109 | ) 110 | 111 | # Basic trimming 112 | # Disabled for now, better cherry-pick the files we need 113 | #emmake make install DESTDIR=$(pwd)/destdir 114 | #find destdir/ -name "*.py" -print0 | xargs -r0 rm 115 | #find destdir/ -name "*.pyo" -print0 | xargs -r0 rm # only keep .pyc, .pyo apparently don't work 116 | #find destdir/ -name "*.so" -print0 | xargs -r0 rm 117 | #rm -rf destdir/usr/local/bin/ 118 | #rm -rf destdir/usr/local/share/man/ 119 | #rm -rf destdir/usr/local/include/ 120 | #rm -rf destdir/usr/local/lib/*.a 121 | #rm -rf destdir/usr/local/lib/pkgconfig/ 122 | #rm -rf destdir/usr/local/lib/python2.7/test/ 123 | # Ditch .so for now, they cause an abort() with dynamic 124 | # linking unless we recompile all of them as SIDE_MODULE-s 125 | #rm -rf $DESTDIR/lib/python2.7/lib-dynload/ 126 | ) 127 | } 128 | 129 | # For mock-ing emscripten environment through static desktop python 130 | mock () { 131 | cd $BUILD/Python-$VERSION/ 132 | mkdir -p native 133 | ( 134 | cd native/ 135 | if [ ! -e config.status ]; then 136 | ../configure \ 137 | --prefix=$BUILD/hostpython/ \ 138 | --without-threads --without-pymalloc --without-signal-module --disable-ipv6 \ 139 | --disable-shared 140 | fi 141 | echo '*static*' > Modules/Setup.local 142 | cat $SETUPLOCAL >> Modules/Setup.local 143 | 144 | make -j$(nproc) Parser/pgen python 145 | 146 | make -j$(nproc) 147 | DESTDIR= make install 148 | ) 149 | } 150 | 151 | # python aimed at compiling third-party Python modules to WASM 152 | # - building static/dynamic wasm modules 153 | # - compiling .pyo files, in emscripten() and package-xxx.sh 154 | # Uses hostpython; detects its PYTHONHOME through ../lib AFAICS, no need to recompile 155 | # Usage: 156 | # .../crosspython-static/bin/python setup.py xxx --root=.../destdir/ --prefix='' 157 | # .../crosspython-dynamic/bin/python setup.py xxx --root=.../destdir/ --prefix='' 158 | # .../crosspython-static/bin/python -OO -m py_compile xxx.py 159 | # Note: maybe create and patch two virtualenv instead? but harder to 160 | # use with dual static/dynamic Makefile; doesn't handle root=destdir 161 | crosspython () { 162 | cd $SCRIPTDIR 163 | # Copy-link hostpython except for include/ and 164 | # lib/python2.7/_sysconfigdata.py 165 | for variant in static dynamic; do 166 | rm -rf crosspython-$variant 167 | mkdir crosspython-$variant 168 | ( 169 | cd crosspython-$variant 170 | for i in $(cd ../build/hostpython && ls -A); do 171 | ln -s ../build/hostpython/$i $i 172 | done 173 | rm include lib 174 | mkdir lib 175 | for i in $(cd ../build/hostpython/lib && ls -A); do 176 | ln -s ../../build/hostpython/lib/$i lib/$i 177 | done 178 | rm lib/python2.7 179 | mkdir lib/python2.7 180 | for i in $(cd ../build/hostpython/lib/python2.7 && ls -A); do 181 | ln -s ../../../build/hostpython/lib/python2.7/$i lib/python2.7/$i 182 | done 183 | 184 | # Use Python.h configured for WASM 185 | ln -s $DESTDIR/include include 186 | 187 | # Use compiler settings configured for WASM 188 | rm lib/python2.7/_sysconfigdata.* 189 | cp -a $DESTDIR/lib/python2.7/_sysconfigdata.* lib/python2.7/ 190 | ) 191 | done 192 | # 'CCSHARED': 'xxx', 193 | sed -i -e "s/'CCSHARED': .*/'CCSHARED': '-fPIC -s SIDE_MODULE=1',/" \ 194 | crosspython-dynamic/lib/python2.7/_sysconfigdata.py 195 | } 196 | 197 | case "$1" in 198 | unpack|hostpython|emscripten|mock|crosspython) 199 | "$1" 200 | ;; 201 | '') 202 | unpack 203 | hostpython 204 | emscripten 205 | crosspython 206 | ;; 207 | *) 208 | echo "Usage: $0 unpack|hostpython|emscripten|mock|crosspython" 209 | exit 1 210 | ;; 211 | esac 212 | -------------------------------------------------------------------------------- /2.7.10/webprompt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Simple Python prompt for the browser, for smoke testing 4 | 5 | # Copyright (C) 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | # Alternatively: use Emscripten's old binary: 13 | # emscripten/tests/python/python.bc -s ERROR_ON_UNDEFINED_SYMBOLS=0 14 | 15 | INSTALLDIR=${INSTALLDIR:-$(dirname $(readlink -f $0))/destdir} 16 | BUILD=t 17 | 18 | mkdir -p $BUILD 19 | 20 | cython -2 ../emscripten.pyx -o $BUILD/emscripten.c 21 | cython -2 ../emscripten_fetch.pyx -o $BUILD/emscripten_fetch.c 22 | # utf_32_be: support Unicode characters e.g. u'é' 23 | PREFIX=$INSTALLDIR OUTDIR=$BUILD ./package-pythonhome.sh \ 24 | encodings/utf_32_be.py 25 | 26 | FLAGS='-O3' 27 | while (( $# )); do 28 | case "$1" in 29 | debug) FLAGS='-s ASSERTIONS=1 -g -s FETCH_DEBUG=1';; 30 | async) ASYNC='-s ASYNCIFY=1 -O3';; 31 | esac 32 | shift 33 | done 34 | emcc -o $BUILD/index.html \ 35 | ../webprompt-main.c $BUILD/emscripten.c $BUILD/emscripten_fetch.c \ 36 | $FLAGS \ 37 | -I$INSTALLDIR/include/python2.7 -L$INSTALLDIR/lib -lpython2.7 \ 38 | -s EMULATE_FUNCTION_POINTER_CASTS=1 \ 39 | -s USE_ZLIB=1 \ 40 | -s FETCH=1 \ 41 | -s ALLOW_MEMORY_GROWTH=1 \ 42 | -s FORCE_FILESYSTEM=1 -s RETAIN_COMPILER_SETTINGS=1 \ 43 | $ASYNC \ 44 | --shell-file ../webprompt-shell.html -s MINIFY_HTML=0 \ 45 | -s EXPORTED_FUNCTIONS='[_main, _Py_Initialize, _PyRun_SimpleString, _pyruni]' \ 46 | -s EXTRA_EXPORTED_RUNTIME_METHODS='[ccall, cwrap]' 47 | 48 | # emrun --serve_after_close t/index.html 49 | 50 | # cython -2 ../mock/emscripten.pyx -o t/mock.c 51 | # cython -2 ../mock/emscripten_fetch.pyx -o t/mock2.c 52 | # gcc -g -I build/hostpython/include/python2.7 -L build/hostpython/lib/ t/mock.c t/mock2.c ../webprompt-main.c -lpython2.7 -ldl -lm -lutil -lz -lpthread 53 | # PYTHONHOME=build/hostpython/ ./a.out 54 | -------------------------------------------------------------------------------- /2.7.18/package-pythonhome.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Creates a minimal Python file hierarchy at $PACKAGEDIR 4 | 5 | # Copyright (C) 2018, 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | FILE_PACKAGER="python3 $(dirname $(which emcc))/tools/file_packager.py" 13 | 14 | PREFIX=${PREFIX:-$(dirname $(readlink -f $0))/destdir} 15 | PACKAGEDIR=${PACKAGEDIR:-$(dirname $(readlink -f $0))/package} 16 | OUTDIR=${OUTDIR:-.} 17 | CROSSPYTHON=$(dirname $(readlink -f $0))/crosspython-static/bin/python 18 | 19 | # Python home 20 | 21 | # optional lz4 compression, requires '-s LZ4=1' 22 | LZ4= 23 | if [ "$1" == "--lz4" ]; then LZ4="--lz4"; shift; fi 24 | 25 | rm -rf $PACKAGEDIR/ 26 | mkdir -p $PACKAGEDIR 27 | 28 | # Hard-coded modules: for 'print("hello, world.")' 29 | # $@: additional, app-specific modules 30 | for i in site.py os.py posixpath.py stat.py genericpath.py warnings.py linecache.py types.py UserDict.py _abcoll.py abc.py _weakrefset.py copy_reg.py traceback.py sysconfig.py re.py sre_compile.py sre_parse.py sre_constants.py encodings/__init__.py codecs.py encodings/aliases.py encodings/utf_8.py __future__.py ast.py copy.py weakref.py platform.py string.py io.py tempfile.py random.py hashlib.py struct.py dummy_thread.py collections.py keyword.py heapq.py argparse.py textwrap.py gettext.py locale.py functools.py importlib/__init__.py glob.py fnmatch.py pickle.py colorsys.py contextlib.py zipfile.py shutil.py json/__init__.py json/decoder.py json/scanner.py encodings/hex_codec.py json/encoder.py difflib.py inspect.py dis.py opcode.py tokenize.py token.py xml/__init__.py xml/etree/__init__.py xml/etree/ElementTree.py xml/etree/ElementPath.py encodings/zlib_codec.py tarfile.py urlparse.py StringIO.py encodings/latin_1.py encodings/ascii.py \ 31 | "$@"; do 32 | if [ $PREFIX/lib/python2.7/$i -nt $PREFIX/lib/python2.7/${i%.py}.pyo ]; then 33 | (cd $PREFIX && $CROSSPYTHON -OO -m py_compile lib/python2.7/$i) 34 | fi 35 | mkdir -p $PACKAGEDIR/lib/python2.7/$(dirname $i) 36 | cp -au $PREFIX/lib/python2.7/${i%.py}.pyo $PACKAGEDIR/lib/python2.7/${i%.py}.pyo 37 | done 38 | # Large and leaks build paths, clean it: 39 | echo 'build_time_vars = {}' > $PACKAGEDIR/lib/python2.7/_sysconfigdata.py 40 | (cd $PACKAGEDIR && $CROSSPYTHON -OO -m py_compile lib/python2.7/_sysconfigdata.py) 41 | rm -f $PACKAGEDIR/lib/python2.7/_sysconfigdata.py 42 | 43 | # --no-heap-copy: suited for ALLOW_MEMORY_GROWTH=1 44 | PACKAGEDIR_FULLPATH=$(readlink -f $PACKAGEDIR) 45 | ( 46 | cd $OUTDIR; # use relative path in xxx-data.js 47 | $FILE_PACKAGER \ 48 | pythonhome.data --js-output=pythonhome-data.js \ 49 | --preload $PACKAGEDIR_FULLPATH@/ \ 50 | --use-preload-cache --no-heap-copy $LZ4 51 | ) 52 | -------------------------------------------------------------------------------- /2.7.18/patches/python2-cross_compile.patch: -------------------------------------------------------------------------------- 1 | Description: Fix build system for Emscripten cross-compilation. 2 | Also adds ac_cv_func_dlopen=yes to support dynamic linking. 3 | Forwarded: no 4 | Author: Marat Dukhan , Sylvain Beucler 5 | Origin: https://github.com/PeachPy/Python-2.7 6 | Last-Update: 2020-07-27 7 | 8 | commit 5b7a8e46d129e576ad9298055bb4e695aea29cb6 9 | Author: Marat Dukhan 10 | Date: Mon Oct 5 16:58:19 2015 -0400 11 | 12 | Make config.sub recognize asmjs-unknown-emscripten target 13 | 14 | Index: Python-2.7.18/config.sub 15 | =================================================================== 16 | --- Python-2.7.18.orig/config.sub 17 | +++ Python-2.7.18/config.sub 18 | @@ -2,7 +2,7 @@ 19 | # Configuration validation subroutine script. 20 | # Copyright 1992-2017 Free Software Foundation, Inc. 21 | 22 | -timestamp='2017-04-02' 23 | +timestamp='2020-07-27' 24 | 25 | # This file is free software; you can redistribute it and/or modify it 26 | # under the terms of the GNU General Public License as published by 27 | @@ -118,7 +118,8 @@ case $maybe_os in 28 | linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ 29 | knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \ 30 | kopensolaris*-gnu* | cloudabi*-eabi* | \ 31 | - storm-chaos* | os2-emx* | rtmk-nova*) 32 | + storm-chaos* | os2-emx* | rtmk-nova* | \ 33 | + emscripten) 34 | os=-$maybe_os 35 | basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` 36 | ;; 37 | @@ -253,6 +254,7 @@ case $basic_machine in 38 | | am33_2.0 \ 39 | | arc | arceb \ 40 | | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ 41 | + | asmjs \ 42 | | avr | avr32 \ 43 | | ba \ 44 | | be32 | be64 \ 45 | @@ -1547,6 +1549,8 @@ case $os in 46 | -dicos*) 47 | os=-dicos 48 | ;; 49 | + -emscripten) 50 | + ;; 51 | -nacl*) 52 | ;; 53 | -ios) 54 | Index: Python-2.7.18/configure 55 | =================================================================== 56 | --- Python-2.7.18.orig/configure 57 | +++ Python-2.7.18/configure 58 | @@ -3287,6 +3287,9 @@ then 59 | *-*-cygwin*) 60 | ac_sys_system=Cygwin 61 | ;; 62 | + asmjs-*-*) 63 | + ac_sys_system=Emscripten 64 | + ;; 65 | *) 66 | # for now, limit cross builds to known configurations 67 | MACHDEP="unknown" 68 | @@ -3333,6 +3336,9 @@ if test "$cross_compiling" = yes; then 69 | *-*-cygwin*) 70 | _host_cpu= 71 | ;; 72 | + asmjs-*-*) 73 | + _host_cpu= 74 | + ;; 75 | *) 76 | # for now, limit cross builds to known configurations 77 | MACHDEP="unknown" 78 | Index: Python-2.7.18/configure.ac 79 | =================================================================== 80 | --- Python-2.7.18.orig/configure.ac 81 | +++ Python-2.7.18/configure.ac 82 | @@ -340,6 +340,9 @@ then 83 | *-*-cygwin*) 84 | ac_sys_system=Cygwin 85 | ;; 86 | + asmjs-*-*) 87 | + ac_sys_system=Emscripten 88 | + ;; 89 | *) 90 | # for now, limit cross builds to known configurations 91 | MACHDEP="unknown" 92 | @@ -386,6 +389,9 @@ if test "$cross_compiling" = yes; then 93 | *-*-cygwin*) 94 | _host_cpu= 95 | ;; 96 | + asmjs-*-*) 97 | + _host_cpu= 98 | + ;; 99 | *) 100 | # for now, limit cross builds to known configurations 101 | MACHDEP="unknown" 102 | Index: Python-2.7.18/config.site 103 | =================================================================== 104 | --- /dev/null 105 | +++ Python-2.7.18/config.site 106 | @@ -0,0 +1,3 @@ 107 | +ac_cv_file__dev_ptmx=no 108 | +ac_cv_file__dev_ptc=no 109 | +ac_cv_func_dlopen=yes 110 | -------------------------------------------------------------------------------- /2.7.18/patches/python2-no_popen.patch: -------------------------------------------------------------------------------- 1 | Description: Fix popen-related issues with Emscripten 2 | warning: unresolved symbol: popen [emcc] 3 | missing function: popen [_popen->abort at runtime] 4 | Forwarded: no 5 | Author: Sylvain Beucler 6 | Last-Update: 2018-10-28 7 | 8 | 9 | Index: Python-2.7.18/Modules/posixmodule.c 10 | =================================================================== 11 | --- Python-2.7.18.orig/Modules/posixmodule.c 12 | +++ Python-2.7.18/Modules/posixmodule.c 13 | @@ -156,8 +156,10 @@ corresponding Unix manual entries for mo 14 | #define HAVE_OPENDIR 1 15 | #define HAVE_PIPE 1 16 | #ifndef __rtems__ 17 | +#ifndef __EMSCRIPTEN__ 18 | #define HAVE_POPEN 1 19 | #endif 20 | +#endif 21 | #define HAVE_SYSTEM 1 22 | #define HAVE_WAIT 1 23 | #define HAVE_TTYNAME 1 24 | -------------------------------------------------------------------------------- /2.7.18/patches/python2-webbrowser.patch: -------------------------------------------------------------------------------- 1 | Description: Support opening new browser tabs from Python/Emscripten 2 | Forwarded: no 3 | Author: Sylvain Beucler 4 | Last-Update: 2018-10-28 5 | 6 | Index: Python-2.7.18/Lib/webbrowser.py 7 | =================================================================== 8 | --- Python-2.7.18.orig/Lib/webbrowser.py 9 | +++ Python-2.7.18/Lib/webbrowser.py 10 | @@ -6,7 +6,7 @@ import os 11 | import shlex 12 | import sys 13 | import stat 14 | -import subprocess 15 | +#import subprocess 16 | import time 17 | 18 | __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] 19 | @@ -656,6 +656,16 @@ if sys.platform[:3] == "os2" and _iscomm 20 | GenericBrowser(["start", "netscape", "%s"]), -1) 21 | 22 | 23 | +class EmscriptenBrowser(BaseBrowser): 24 | + def open(self, url, new=0, autoraise=True): 25 | + import emscripten 26 | + emscripten.run_script("""try { window.open('%s', '_blank'); } catch (e) { console.log(e); }""" % url.replace("'", "%27")) 27 | + return True 28 | + 29 | +if sys.platform == 'emscripten': 30 | + register("emscripten", None, EmscriptenBrowser()) 31 | + 32 | + 33 | # OK, now that we know what the default preference orders for each 34 | # platform are, allow user to override them with the BROWSER variable. 35 | if "BROWSER" in os.environ: 36 | -------------------------------------------------------------------------------- /2.7.18/patches/series: -------------------------------------------------------------------------------- 1 | python2-cross_compile.patch 2 | python2-no_popen.patch 3 | python2-webbrowser.patch 4 | -------------------------------------------------------------------------------- /2.7.18/python.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Compile minimal Python for Emscripten and native local testing 4 | 5 | # Copyright (C) 2018, 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | VERSION=2.7.18 # for end-of-life Python2, support Ren'Py 7.x version only 13 | SCRIPTDIR=$(dirname $(readlink -f $0)) 14 | DESTDIR=${DESTDIR:-$SCRIPTDIR/destdir} 15 | SETUPLOCAL=${SETUPLOCAL:-'/dev/null'} 16 | 17 | CACHEROOT=$SCRIPTDIR 18 | BUILD=$SCRIPTDIR/build 19 | export QUILT_PATCHES=$(dirname $(readlink -f $0))/patches 20 | 21 | WGET=${WGET:-wget} 22 | 23 | unpack () { 24 | $WGET -c https://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz -P $CACHEROOT/ 25 | mkdir -p $BUILD 26 | cd $BUILD/ 27 | rm -rf Python-$VERSION/ 28 | tar xf $CACHEROOT/Python-$VERSION.tgz 29 | cd Python-$VERSION/ 30 | quilt push -a 31 | } 32 | 33 | # use cases: 34 | # - python/.pyo for emscripten() below 35 | # - common basis for crosspython below 36 | hostpython () { 37 | cd $BUILD/Python-$VERSION/ 38 | mkdir -p native 39 | ( 40 | cd native/ 41 | if [ ! -e config.status ]; then 42 | # --without-pymalloc: prevents segfault during 'make' (sysconfigdata) 43 | ../configure \ 44 | --prefix='' \ 45 | --without-pymalloc 46 | fi 47 | 48 | make -j$(nproc) 49 | make install DESTDIR=$BUILD/hostpython 50 | ) 51 | 52 | # setuptools for 3rd-party module installers 53 | wget -c https://files.pythonhosted.org/packages/11/0a/7f13ef5cd932a107cd4c0f3ebc9d831d9b78e1a0e8c98a098ca17b1d7d97/setuptools-41.6.0.zip -P $CACHEROOT/ 54 | cd $BUILD/hostpython/ 55 | rm -rf setuptools-41.6.0/ 56 | unzip $CACHEROOT/setuptools-41.6.0.zip 57 | cd setuptools-41.6.0/ 58 | ../bin/python setup.py install 59 | } 60 | 61 | emscripten () { 62 | cd $BUILD/Python-$VERSION/ 63 | mkdir -p emscripten 64 | ( 65 | cd emscripten/ 66 | # OPT=-Oz: TODO 67 | # CONFIG_SITE: deals with cross-compilation https://bugs.python.org/msg136962 68 | # not needed as emcc has a single arch: BASECFLAGS=-m32 LDFLAGS=-m32 69 | # PATH: detect our Python, beware of conflict with emcc's python3 70 | # don't use PYTHON_FOR_BUILD which is high-level / lots of options 71 | # --without-threads: pthreads experimental as of 2020-05 72 | # cf. https://emscripten.org/docs/porting/pthreads.html 73 | # --without-signal-module: no process signals in Emscripten 74 | # --without-pymalloc: ? 75 | # --disable-ipv6: browser-side networking 76 | # --disable-shared: compile statically for Emscripten perfs + incomplete PIC support 77 | if [ ! -e config.status ]; then 78 | CONFIG_SITE=../config.site \ 79 | BASECFLAGS='-s USE_ZLIB=1' LDFLAGS='-s USE_ZLIB=1' \ 80 | PATH=$BUILD/Python-$VERSION/native:$PATH \ 81 | emconfigure ../configure \ 82 | --host=asmjs-unknown-emscripten --build=$(../config.guess) \ 83 | --prefix='' \ 84 | --without-threads --without-pymalloc --without-signal-module --disable-ipv6 \ 85 | --disable-shared 86 | fi 87 | 88 | # Modules/Setup.local 89 | echo '*static*' > Modules/Setup.local 90 | cat $SETUPLOCAL >> Modules/Setup.local 91 | # drop -I/-L/-lz, we USE_ZLIB=1 (keep it in SETUPLOCAL for mock) 92 | sed -i -e 's/^\(zlib zlibmodule.c\).*/\1/' Modules/Setup.local 93 | emmake make Makefile 94 | # decrease .pyo size by dropping docstrings 95 | sed -i -e '/compileall.py/ s/ -O / -OO /' Makefile 96 | 97 | ( 98 | export PATH=$BUILD/Python-$VERSION/native:$PATH 99 | emmake make -j$(nproc) 100 | # setup.py install_lib doesn't respect DESTDIR 101 | #echo -e 'sharedinstall:\n\ttrue' >> Makefile 102 | emmake make install DESTDIR=$DESTDIR 103 | ) 104 | 105 | # Basic trimming 106 | # Disabled for now, better cherry-pick the files we need 107 | #emmake make install DESTDIR=$(pwd)/destdir 108 | #find destdir/ -name "*.py" -print0 | xargs -r0 rm 109 | #find destdir/ -name "*.pyo" -print0 | xargs -r0 rm # only keep .pyc, .pyo apparently don't work 110 | #find destdir/ -name "*.so" -print0 | xargs -r0 rm 111 | #rm -rf destdir/usr/local/bin/ 112 | #rm -rf destdir/usr/local/share/man/ 113 | #rm -rf destdir/usr/local/include/ 114 | #rm -rf destdir/usr/local/lib/*.a 115 | #rm -rf destdir/usr/local/lib/pkgconfig/ 116 | #rm -rf destdir/usr/local/lib/python2.7/test/ 117 | # Ditch .so for now, they cause an abort() with dynamic 118 | # linking unless we recompile all of them as SIDE_MODULE-s 119 | #rm -rf $DESTDIR/lib/python2.7/lib-dynload/ 120 | ) 121 | } 122 | 123 | # For mock-ing emscripten environment through static desktop python 124 | mock () { 125 | cd $BUILD/Python-$VERSION/ 126 | mkdir -p native 127 | ( 128 | cd native/ 129 | if [ ! -e config.status ]; then 130 | ../configure \ 131 | --prefix=$BUILD/hostpython/ \ 132 | --without-threads --without-pymalloc --without-signal-module --disable-ipv6 \ 133 | --disable-shared 134 | fi 135 | echo '*static*' > Modules/Setup.local 136 | cat $SETUPLOCAL >> Modules/Setup.local 137 | 138 | make -j$(nproc) Parser/pgen python 139 | 140 | make -j$(nproc) 141 | DESTDIR= make install 142 | ) 143 | } 144 | 145 | # python aimed at compiling third-party Python modules to WASM 146 | # - building static/dynamic wasm modules 147 | # - compiling .pyo files, in emscripten() and package-xxx.sh 148 | # Uses hostpython; detects its PYTHONHOME through ../lib AFAICS, no need to recompile 149 | # Usage: 150 | # .../crosspython-static/bin/python setup.py xxx --root=.../destdir/ --prefix='' 151 | # .../crosspython-dynamic/bin/python setup.py xxx --root=.../destdir/ --prefix='' 152 | # .../crosspython-static/bin/python -OO -m py_compile xxx.py 153 | # Note: maybe create and patch two virtualenv instead? but harder to 154 | # use with dual static/dynamic Makefile; doesn't handle root=destdir 155 | crosspython () { 156 | cd $SCRIPTDIR 157 | # Copy-link hostpython except for include/ and 158 | # lib/python2.7/_sysconfigdata.py 159 | for variant in static dynamic; do 160 | rm -rf crosspython-$variant 161 | mkdir crosspython-$variant 162 | ( 163 | cd crosspython-$variant 164 | for i in $(cd ../build/hostpython && ls -A); do 165 | ln -s ../build/hostpython/$i $i 166 | done 167 | rm include lib 168 | mkdir lib 169 | for i in $(cd ../build/hostpython/lib && ls -A); do 170 | ln -s ../../build/hostpython/lib/$i lib/$i 171 | done 172 | rm lib/python2.7 173 | mkdir lib/python2.7 174 | for i in $(cd ../build/hostpython/lib/python2.7 && ls -A); do 175 | ln -s ../../../build/hostpython/lib/python2.7/$i lib/python2.7/$i 176 | done 177 | 178 | # Use Python.h configured for WASM 179 | ln -s $DESTDIR/include include 180 | 181 | # Use compiler settings configured for WASM 182 | rm lib/python2.7/_sysconfigdata.* 183 | cp -a $DESTDIR/lib/python2.7/_sysconfigdata.* lib/python2.7/ 184 | ) 185 | done 186 | # 'CCSHARED': 'xxx', 187 | sed -i -e "s/'CCSHARED': .*/'CCSHARED': '-fPIC -s SIDE_MODULE=1',/" \ 188 | crosspython-dynamic/lib/python2.7/_sysconfigdata.py 189 | } 190 | 191 | case "$1" in 192 | unpack|hostpython|emscripten|mock|crosspython) 193 | "$1" 194 | ;; 195 | '') 196 | unpack 197 | hostpython 198 | emscripten 199 | crosspython 200 | ;; 201 | *) 202 | echo "Usage: $0 unpack|hostpython|emscripten|mock|crosspython" 203 | exit 1 204 | ;; 205 | esac 206 | -------------------------------------------------------------------------------- /2.7.18/webprompt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Simple Python prompt for the browser, for smoke testing 4 | 5 | # Copyright (C) 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | # Alternatively: use Emscripten's old binary: 13 | # emscripten/tests/python/python.bc -s ERROR_ON_UNDEFINED_SYMBOLS=0 14 | 15 | INSTALLDIR=${INSTALLDIR:-$(dirname $(readlink -f $0))/destdir} 16 | BUILD=t 17 | 18 | mkdir -p $BUILD 19 | 20 | cython -2 ../emscripten.pyx -o $BUILD/emscripten.c 21 | cython -2 ../emscripten_fetch.pyx -o $BUILD/emscripten_fetch.c 22 | # utf_32_be: support Unicode characters e.g. u'é' 23 | PREFIX=$INSTALLDIR OUTDIR=$BUILD ./package-pythonhome.sh \ 24 | encodings/utf_32_be.py 25 | 26 | FLAGS='-O3' 27 | while (( $# )); do 28 | case "$1" in 29 | debug) FLAGS='-s ASSERTIONS=1 -g -s FETCH_DEBUG=1';; 30 | async) ASYNC='-s ASYNCIFY=1 -O3';; 31 | esac 32 | shift 33 | done 34 | emcc -o $BUILD/index.html \ 35 | ../webprompt-main.c $BUILD/emscripten.c $BUILD/emscripten_fetch.c \ 36 | $FLAGS \ 37 | -I$INSTALLDIR/include/python2.7 -L$INSTALLDIR/lib -lpython2.7 \ 38 | -s EMULATE_FUNCTION_POINTER_CASTS=1 \ 39 | -s USE_ZLIB=1 \ 40 | -s FETCH=1 \ 41 | -s ALLOW_MEMORY_GROWTH=1 \ 42 | -s FORCE_FILESYSTEM=1 -s RETAIN_COMPILER_SETTINGS=1 \ 43 | $ASYNC \ 44 | --shell-file ../webprompt-shell.html -s MINIFY_HTML=0 \ 45 | -s EXPORTED_FUNCTIONS='[_main, _Py_Initialize, _PyRun_SimpleString, _pyruni]' \ 46 | -s EXTRA_EXPORTED_RUNTIME_METHODS='[ccall, cwrap]' 47 | 48 | # emrun --serve_after_close t/index.html 49 | 50 | # cython -2 ../mock/emscripten.pyx -o t/mock.c 51 | # cython -2 ../mock/emscripten_fetch.pyx -o t/mock2.c 52 | # gcc -g -I build/hostpython/include/python2.7 -L build/hostpython/lib/ t/mock.c t/mock2.c ../webprompt-main.c -lpython2.7 -ldl -lm -lutil -lz -lpthread 53 | # PYTHONHOME=build/hostpython/ ./a.out 54 | -------------------------------------------------------------------------------- /3.8/package-pythonhome.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Creates a minimal Python file hierarchy at $PACKAGEDIR 4 | 5 | # Copyright (C) 2018, 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | FILE_PACKAGER="python3 $(dirname $(which emcc))/tools/file_packager.py" 13 | 14 | PREFIX=${PREFIX:-$(dirname $(readlink -f $0))/destdir} 15 | PACKAGEDIR=${PACKAGEDIR:-$(dirname $(readlink -f $0))/package} 16 | OUTDIR=${OUTDIR:-.} 17 | CROSSPYTHON=$(dirname $(readlink -f $0))/crosspython-static/bin/python3 18 | 19 | # Python home 20 | 21 | # optional lz4 compression, requires '-s LZ4=1' 22 | LZ4= 23 | if [ "$1" == "--lz4" ]; then LZ4="--lz4"; shift; fi 24 | 25 | rm -rf $PACKAGEDIR/ 26 | mkdir -p $PACKAGEDIR 27 | 28 | # Hard-coded modules: for 'print("hello, world.")' 29 | # $@: additional, app-specific modules 30 | (cd $PREFIX/lib/python3.8/ && $CROSSPYTHON -OO -m compileall .) >/dev/null || true 31 | for i in site.py os.py stat.py posixpath.py genericpath.py abc.py encodings/__init__.py codecs.py encodings/aliases.py encodings/utf_8.py io.py _collections_abc.py _sitebuiltins.py encodings/ascii.py encodings/latin_1.py \ 32 | "$@"; do 33 | mkdir -p $PACKAGEDIR/lib/python3.8/$(dirname $i)/__pycache__ 34 | # Install to legacy .pyc location for size-efficient source-less distribution 35 | cp -au $PREFIX/lib/python3.8/$(dirname $i)/__pycache__/$(basename ${i%.py}.cpython-38.opt-2.pyc) \ 36 | $PACKAGEDIR/lib/python3.8/${i%.py}.pyc 37 | done 38 | # Large and leaks build paths, clean it: 39 | #echo 'build_time_vars = {}' > $PACKAGEDIR/lib/python3.8/lib-dynload/_sysconfigdata__linux_x86_64-linux-gnu.py 40 | #(cd $PACKAGEDIR && $CROSSPYTHON -OO -m py_compile lib/python3.8/_sysconfigdata.py) 41 | #rm -f $PACKAGEDIR/lib/python3.8/_sysconfigdata.py 42 | 43 | # --no-heap-copy: suited for ALLOW_MEMORY_GROWTH=1 44 | PACKAGEDIR_FULLPATH=$(readlink -f $PACKAGEDIR) 45 | ( 46 | cd $OUTDIR; # use relative path in xxx-data.js 47 | $FILE_PACKAGER \ 48 | pythonhome.data --js-output=pythonhome-data.js \ 49 | --preload $PACKAGEDIR_FULLPATH@/ \ 50 | --use-preload-cache --no-heap-copy $LZ4 51 | ) 52 | -------------------------------------------------------------------------------- /3.8/patches/disable-set_inheritable.patch: -------------------------------------------------------------------------------- 1 | Description: Disable set_inheritable 2 | Forwarded: no 3 | Author: Sylvain Beucler 4 | Last-Update: 2020-05-08 5 | 6 | 'set_inheritable' requires iotclt FIOCLEX/21585/0x5451 which is not supported: 7 | RuntimeError: abort(bad ioctl syscall 21585) at jsStackTrace@http://localhost:6931/index.js:1955:17 8 | 9 | --- a/Python/fileutils.c 2020-05-08 16:44:14.563517953 +0200 10 | +++ b/Python/fileutils.c 2020-05-08 16:44:48.875389584 +0200 11 | @@ -1091,6 +1091,9 @@ 12 | static int 13 | set_inheritable(int fd, int inheritable, int raise, int *atomic_flag_works) 14 | { 15 | +#ifdef EMSCRIPTEN 16 | +return 0; 17 | +#endif 18 | #ifdef MS_WINDOWS 19 | HANDLE handle; 20 | DWORD flags; 21 | -------------------------------------------------------------------------------- /3.8/patches/python3-cross_compile.patch: -------------------------------------------------------------------------------- 1 | Description: Fix build system for Emscripten cross-compilation. 2 | Forwarded: no 3 | Author: Sylvain Beucler 4 | Last-Update: 2020-05-08 5 | 6 | Index: Python-3.8.3/config.sub 7 | =================================================================== 8 | --- Python-3.8.3.orig/config.sub 9 | +++ Python-3.8.3/config.sub 10 | @@ -1394,7 +1394,7 @@ case $os in 11 | | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ 12 | | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* \ 13 | | -onefs* | -tirtos* | -phoenix* | -fuchsia* | -redox* | -bme* \ 14 | - | -midnightbsd*) 15 | + | -midnightbsd* | -emscripten*) 16 | # Remember, each alternative MUST END IN *, to match a version number. 17 | ;; 18 | -qnx*) 19 | Index: Python-3.8.3/configure 20 | =================================================================== 21 | --- Python-3.8.3.orig/configure 22 | +++ Python-3.8.3/configure 23 | @@ -3278,6 +3278,9 @@ then 24 | *-*-vxworks*) 25 | ac_sys_system=VxWorks 26 | ;; 27 | + *-*-emscripten) 28 | + ac_sys_system=Emscripten 29 | + ;; 30 | *) 31 | # for now, limit cross builds to known configurations 32 | MACHDEP="unknown" 33 | @@ -3328,6 +3331,9 @@ if test "$cross_compiling" = yes; then 34 | *-*-vxworks*) 35 | _host_cpu=$host_cpu 36 | ;; 37 | + *-*-emscripten) 38 | + ac_sys_system=Emscripten 39 | + ;; 40 | *) 41 | # for now, limit cross builds to known configurations 42 | MACHDEP="unknown" 43 | Index: Python-3.8.3/config-emscripten.site 44 | =================================================================== 45 | --- /dev/null 46 | +++ Python-3.8.3/config-emscripten.site 47 | @@ -0,0 +1,5 @@ 48 | +# Required by ./configure 49 | +ac_cv_file__dev_ptmx=no 50 | +ac_cv_file__dev_ptc=no 51 | +# Support dynamic linking 52 | +ac_cv_func_dlopen=yes 53 | --- Python-3.8.3/setup.py 2020-05-13 19:31:54.000000000 +0200 54 | +++ build/Python-3.8.3/setup.py 2020-05-22 10:43:49.189946959 +0200 55 | @@ -651,10 +651,10 @@ 56 | if not CROSS_COMPILING: 57 | add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib') 58 | add_dir_to_list(self.compiler.include_dirs, '/usr/local/include') 59 | + self.add_multiarch_paths() 60 | # only change this for cross builds for 3.3, issues on Mageia 61 | - if CROSS_COMPILING: 62 | - self.add_cross_compiling_paths() 63 | - self.add_multiarch_paths() 64 | + #if CROSS_COMPILING: 65 | + # self.add_cross_compiling_paths() 66 | self.add_ldflags_cppflags() 67 | 68 | def init_inc_lib_dirs(self): 69 | -------------------------------------------------------------------------------- /3.8/patches/series: -------------------------------------------------------------------------------- 1 | python3-cross_compile.patch 2 | disable-set_inheritable.patch 3 | -------------------------------------------------------------------------------- /3.8/python.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Compile minimal Python for Emscripten and native local testing 4 | 5 | # Copyright (C) 2018, 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | VERSION=3.8.3 13 | SCRIPTDIR=$(dirname $(readlink -f $0)) 14 | DESTDIR=${DESTDIR:-$SCRIPTDIR/destdir} 15 | SETUPLOCAL=${SETUPLOCAL:-'/dev/null'} 16 | 17 | CACHEROOT=$SCRIPTDIR 18 | BUILD=$SCRIPTDIR/build 19 | export QUILT_PATCHES=$(dirname $(readlink -f $0))/patches 20 | 21 | WGET=${WGET:-wget} 22 | 23 | unpack () { 24 | $WGET -c https://www.python.org/ftp/python/$VERSION/Python-$VERSION.tar.xz -P $CACHEROOT/ 25 | mkdir -p $BUILD 26 | cd $BUILD/ 27 | rm -rf Python-$VERSION/ 28 | tar xf $CACHEROOT/Python-$VERSION.tar.xz 29 | cd Python-$VERSION/ 30 | quilt push -a 31 | } 32 | 33 | # use cases: 34 | # - python/.pyo for emscripten() below 35 | # - common basis for crosspython below 36 | hostpython () { 37 | cd $BUILD/Python-$VERSION/ 38 | mkdir -p native 39 | ( 40 | cd native/ 41 | if [ ! -e config.status ]; then 42 | ../configure \ 43 | --prefix='' 44 | fi 45 | 46 | make -j$(nproc) 47 | rm -rf $BUILD/hostpython 48 | make install DESTDIR=$BUILD/hostpython 49 | ) 50 | } 51 | 52 | emscripten () { 53 | cd $BUILD/Python-$VERSION/ 54 | mkdir -p emscripten 55 | ( 56 | cd emscripten/ 57 | # OPT=-Oz: TODO 58 | # CONFIG_SITE: deals with cross-compilation https://bugs.python.org/msg136962 59 | # PATH: detect our Python, beware of conflict with emcc's python3 60 | # don't use PYTHON_FOR_BUILD which is high-level / lots of options 61 | # --without-pymalloc: ? 62 | # --disable-ipv6: ? 63 | # --disable-shared: compile statically for Emscripten perfs + incomplete PIC support 64 | if [ ! -e config.status ]; then 65 | CONFIG_SITE=../config-emscripten.site READELF=true \ 66 | BASECFLAGS='-s USE_ZLIB=1' LDFLAGS='-s USE_ZLIB=1' \ 67 | PATH=$BUILD/Python-$VERSION/native:$PATH \ 68 | emconfigure ../configure \ 69 | --host=asmjs-unknown-emscripten --build=$(../config.guess) \ 70 | --prefix='' \ 71 | --without-pymalloc --disable-ipv6 \ 72 | --disable-shared 73 | fi 74 | cat <> pyconfig.h 75 | /* issues with posixmodule.c */ 76 | #undef HAVE_POSIX_SPAWN 77 | #undef HAVE_POSIX_SPAWNP 78 | /* issues with signalmodule.c */ 79 | #undef HAVE_PTHREAD_SIGMASK 80 | EOF 81 | 82 | # Modules/Setup.local 83 | echo '*static*' > Modules/Setup.local 84 | cat $SETUPLOCAL >> Modules/Setup.local 85 | # drop -I/-L/-lz, we USE_ZLIB=1 (keep it in SETUPLOCAL for mock) 86 | sed -i -e 's/^\(zlib zlibmodule.c\).*/\1/' Modules/Setup.local 87 | emmake make Makefile 88 | # decrease .pyo size by dropping docstrings 89 | sed -i -e '/compileall.py/ s/ -O / -OO /' Makefile 90 | 91 | ( 92 | export PATH=$BUILD/Python-$VERSION/native:$PATH 93 | # Trigger setup.py:CROSS_COMPILING (introduced in 3.8) 94 | export _PYTHON_HOST_PLATFORM=emscripten 95 | emmake make -j$(nproc) 96 | emmake make install DESTDIR=$DESTDIR 97 | ) 98 | 99 | # Basic trimming 100 | # Disabled for now, better cherry-pick the files we need 101 | #emmake make install DESTDIR=$(pwd)/destdir 102 | #find destdir/ -name "*.py" -print0 | xargs -r0 rm 103 | #find destdir/ -name "*.pyo" -print0 | xargs -r0 rm # only keep .pyc, .pyo apparently don't work 104 | #find destdir/ -name "*.so" -print0 | xargs -r0 rm 105 | #rm -rf destdir/usr/local/bin/ 106 | #rm -rf destdir/usr/local/share/man/ 107 | #rm -rf destdir/usr/local/include/ 108 | #rm -rf destdir/usr/local/lib/*.a 109 | #rm -rf destdir/usr/local/lib/pkgconfig/ 110 | #rm -rf destdir/usr/local/lib/python2.7/test/ 111 | # Ditch .so for now, they cause an abort() with dynamic 112 | # linking unless we recompile all of them as SIDE_MODULE-s 113 | #rm -rf $DESTDIR/lib/python2.7/lib-dynload/ 114 | ) 115 | } 116 | 117 | # For mock-ing emscripten environment through static desktop python 118 | mock () { 119 | cd $BUILD/Python-$VERSION/ 120 | mkdir -p native 121 | ( 122 | cd native/ 123 | if [ ! -e config.status ]; then 124 | ../configure \ 125 | --prefix=$BUILD/hostpython/ \ 126 | --without-threads --without-pymalloc --without-signal-module --disable-ipv6 \ 127 | --disable-shared 128 | fi 129 | echo '*static*' > Modules/Setup.local 130 | cat $SETUPLOCAL >> Modules/Setup.local 131 | 132 | make -j$(nproc) Parser/pgen python 133 | 134 | make -j$(nproc) 135 | DESTDIR= make install 136 | ) 137 | } 138 | 139 | # python aimed at compiling third-party Python modules to WASM 140 | # - building static/dynamic wasm modules 141 | # - compiling .pyo files, in emscripten() and package-xxx.sh 142 | # Uses hostpython; detects its PYTHONHOME through ../lib AFAICS, no need to recompile 143 | # Usage: 144 | # .../crosspython-static/bin/python setup.py xxx --root=.../destdir/ --prefix='' 145 | # .../crosspython-dynamic/bin/python setup.py xxx --root=.../destdir/ --prefix='' 146 | # .../crosspython-static/bin/python -OO -m py_compile xxx.py 147 | # Note: maybe create and patch two virtualenv instead? but harder to 148 | # use with dual static/dynamic Makefile; doesn't handle root=destdir 149 | crosspython () { 150 | cd $SCRIPTDIR 151 | # Copy-link hostpython except for include/ and 152 | # lib/python3.8/_sysconfigdata.py 153 | for variant in static dynamic; do 154 | rm -rf crosspython-$variant 155 | mkdir crosspython-$variant 156 | ( 157 | cd crosspython-$variant 158 | for i in $(cd ../build/hostpython && ls -A); do 159 | ln -s ../build/hostpython/$i $i 160 | done 161 | rm include lib 162 | mkdir lib 163 | for i in $(cd ../build/hostpython/lib && ls -A); do 164 | ln -s ../../build/hostpython/lib/$i lib/$i 165 | done 166 | rm lib/python3.8 167 | mkdir lib/python3.8 168 | for i in $(cd ../build/hostpython/lib/python3.8 && ls -A); do 169 | ln -s ../../../build/hostpython/lib/python3.8/$i lib/python3.8/$i 170 | done 171 | 172 | # Use Python.h configured for WASM 173 | ln -s $DESTDIR/include include 174 | 175 | # Use compiler settings configured for WASM 176 | cp -a $DESTDIR/lib/python3.8/_sysconfigdata__emscripten_.py \ 177 | lib/python3.8/_sysconfigdata_*.py 178 | ) 179 | done 180 | # 'CCSHARED': 'xxx', 181 | sed -i -e "s/'CCSHARED': .*/'CCSHARED': '-fPIC -s SIDE_MODULE=1',/" \ 182 | crosspython-dynamic/lib/python3.8/_sysconfigdata_*.py 183 | } 184 | 185 | case "$1" in 186 | unpack|hostpython|emscripten|mock|crosspython) 187 | "$1" 188 | ;; 189 | '') 190 | unpack 191 | hostpython 192 | emscripten 193 | crosspython 194 | ;; 195 | *) 196 | echo "Usage: $0 unpack|hostpython|emscripten|mock|crosspython" 197 | exit 1 198 | ;; 199 | esac 200 | -------------------------------------------------------------------------------- /3.8/webprompt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Simple Python prompt for the browser, for smoke testing 4 | 5 | # Copyright (C) 2019, 2020 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | INSTALLDIR=${INSTALLDIR:-$(dirname $(readlink -f $0))/destdir} 13 | BUILD=t 14 | 15 | mkdir -p $BUILD 16 | 17 | cython -3 ../emscripten.pyx -o $BUILD/emscripten.c 18 | cython -3 ../emscripten_fetch.pyx -o $BUILD/emscripten_fetch.c 19 | # utf_32_be: support Unicode characters e.g. u'é' 20 | # __future__.py: for emscripten.pyx 21 | # + some optional but common Python deps 22 | PREFIX=$INSTALLDIR OUTDIR=$BUILD ./package-pythonhome.sh \ 23 | encodings/utf_32_be.py __future__.py \ 24 | struct.py operator.py datetime.py random.py functools.py types.py \ 25 | collections/__init__.py collections/abc.py \ 26 | pickle.py copyreg.py _compat_pickle.py keyword.py heapq.py reprlib.py \ 27 | re.py sre_compile.py sre_parse.py sre_constants.py enum.py 28 | 29 | FLAGS='-O3' 30 | while (( $# )); do 31 | case "$1" in 32 | debug) FLAGS='-s ASSERTIONS=1 -g -s FETCH_DEBUG=1';; 33 | async) ASYNC='-s ASYNCIFY=1 -O3';; 34 | esac 35 | shift 36 | done 37 | emcc -o $BUILD/index.html \ 38 | ../webprompt-main.c $BUILD/emscripten.c $BUILD/emscripten_fetch.c \ 39 | $FLAGS \ 40 | -I$INSTALLDIR/include/python3.8 -L$INSTALLDIR/lib -lpython3.8 \ 41 | -s EMULATE_FUNCTION_POINTER_CASTS=1 \ 42 | -s USE_ZLIB=1 \ 43 | -s FETCH=1 \ 44 | -s ALLOW_MEMORY_GROWTH=1 \ 45 | -s FORCE_FILESYSTEM=1 -s RETAIN_COMPILER_SETTINGS=1 \ 46 | $ASYNC \ 47 | --shell-file ../webprompt-shell.html -s MINIFY_HTML=0 \ 48 | -s EXPORTED_FUNCTIONS='[_main, _Py_Initialize, _PyRun_SimpleString, _pyruni]' \ 49 | -s EXTRA_EXPORTED_RUNTIME_METHODS='[ccall, cwrap]' 50 | 51 | # emrun --serve_after_close t/index.html 52 | 53 | # cython -3 ../mock/emscripten.pyx -o t/mock.c 54 | # cython -3 ../mock/emscripten_fetch.pyx -o t/mock2.c 55 | # gcc -g -I build/hostpython/include/python3.8 -L build/hostpython/lib/ t/mock.c t/mock2.c ../webprompt-main.c -lpython3.8 -ldl -lm -lutil -lz -lpthread 56 | # PYTHONHOME=build/hostpython/ ./a.out 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 Sylvain Beucler 2 | 3 | Copying and distribution of this file, with or without modification, 4 | are permitted in any medium without royalty provided the copyright 5 | notice and this notice are preserved. This file is offered as-is, 6 | without any warranty. 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python compilation scripts and patches to run in the browser. 2 | 3 | 4 | 5 | Build requirements: Emscripten, python3, gcc, make, quilt 6 | 7 | Emscripten: download prebuilt binaries (or [build from source](https://emscripten.org/docs/building_from_source/)) 8 | 9 | git clone https://github.com/emscripten-core/emsdk/ 10 | pushd emsdk/ 11 | ./emsdk install 2.0.2 12 | ./emsdk activate 2.0.2 13 | popd 14 | source emsdk/emsdk_env.sh 15 | 16 | Python for the web browser! 17 | 18 | cd 3.8/ 19 | ./python.sh 20 | ./package-pythonhome.sh repr.py base64.py ... 21 | emcc ... -lpython3.8 -s EMULATE_FUNCTION_POINTER_CASTS=1 22 | 23 | Web demo: 24 | 25 | ./webprompt.sh 26 | emrun --serve_after_close t/index.html 27 | 28 | Real-world showcase: [RenPyWeb](https://github.com/renpy/renpyweb). 29 | 30 | Emscripten evolves regularly with (minor) breaking changes. 31 | If you use a different version compilation may break. 32 | 33 | Mirrors: 34 | 35 | - 36 | - 37 | -------------------------------------------------------------------------------- /emscripten.pyx: -------------------------------------------------------------------------------- 1 | # Python wrapper for emscripten_* C functions 2 | 3 | # Copyright (C) 2018, 2019, 2020 Sylvain Beucler 4 | 5 | # Copying and distribution of this file, with or without modification, 6 | # are permitted in any medium without royalty provided the copyright 7 | # notice and this notice are preserved. This file is offered as-is, 8 | # without any warranty. 9 | 10 | # http://docs.cython.org/en/latest/src/tutorial/strings.html#auto-encoding-and-decoding 11 | # Most of our strings are converted from/to JS through emscripten stringToUTF8/UTF8ToString 12 | # cython: c_string_type=unicode, c_string_encoding=utf8 13 | # Note: Py->C auto-UTF-8 (instead of .encode('UTF-8')) not supported for Py2 14 | # Note: causes issues with Typed Memoryviews 15 | 16 | from __future__ import print_function 17 | 18 | cdef extern from "emscripten.h": 19 | ctypedef void (*em_callback_func)() 20 | ctypedef void (*em_arg_callback_func)(void*) 21 | ctypedef void (*em_async_wget_onload_func)(void*, void*, int) 22 | 23 | void emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop) 24 | void emscripten_set_main_loop_arg(em_arg_callback_func func, void *arg, int fps, int simulate_infinite_loop) 25 | void emscripten_cancel_main_loop() 26 | void emscripten_exit_with_live_runtime() 27 | 28 | void emscripten_run_script(const char *script) 29 | int emscripten_run_script_int(const char *script) 30 | char *emscripten_run_script_string(const char *script) 31 | 32 | #void emscripten_async_wget(const char* url, const char* file, em_str_callback_func onload, em_str_callback_func onerror) 33 | void emscripten_async_wget_data(const char* url, void *arg, em_async_wget_onload_func onload, em_arg_callback_func onerror) 34 | void emscripten_async_call(em_arg_callback_func func, void *arg, int millis) 35 | 36 | void emscripten_sleep(unsigned int ms) 37 | void emscripten_wget(const char* url, const char* file) 38 | void emscripten_wget_data(const char* url, void** pbuffer, int* pnum, int *perror) 39 | 40 | # Emterpreter-only 41 | #void emscripten_sleep_with_yield(unsigned int ms) 42 | 43 | enum: 44 | EM_LOG_CONSOLE 45 | EM_LOG_WARN 46 | EM_LOG_ERROR 47 | EM_LOG_C_STACK 48 | EM_LOG_JS_STACK 49 | EM_LOG_DEMANGLE 50 | EM_LOG_NO_PATHS 51 | EM_LOG_FUNC_PARAMS 52 | 53 | int emscripten_get_compiler_setting(const char *name) 54 | void emscripten_debugger() 55 | void emscripten_log(int flags, const char* format, ...) 56 | int emscripten_get_callstack(int flags, char *out, int maxbytes) 57 | 58 | LOG_CONSOLE = EM_LOG_CONSOLE 59 | LOG_WARN = EM_LOG_WARN 60 | LOG_ERROR = EM_LOG_ERROR 61 | LOG_C_STACK = EM_LOG_C_STACK 62 | LOG_JS_STACK = EM_LOG_JS_STACK 63 | LOG_DEMANGLE = EM_LOG_DEMANGLE 64 | LOG_NO_PATHS = EM_LOG_NO_PATHS 65 | LOG_FUNC_PARAMS = EM_LOG_FUNC_PARAMS 66 | 67 | 68 | # https://cython.readthedocs.io/en/latest/src/tutorial/memory_allocation.html 69 | from libc.stdlib cimport malloc, free 70 | from cpython.mem cimport PyMem_Malloc, PyMem_Free 71 | # https://github.com/cython/cython/wiki/FAQ#what-is-the-difference-between-pyobject-and-object 72 | from cpython.ref cimport PyObject, Py_XINCREF, Py_XDECREF 73 | 74 | from cpython.buffer cimport PyBuffer_FillInfo 75 | 76 | #cdef extern from "stdio.h": 77 | # int puts(const char *s); 78 | 79 | 80 | # C callback - no memory management 81 | # Take a single Python object and calls it 82 | # Kept for documentation 83 | cdef void callpyfunc(void *py_function): 84 | # not necessary as we're using a no-threading Python 85 | #PyEval_InitThreads() 86 | # Call Python function from C using ()() 87 | f = py_function 88 | f() 89 | 90 | 91 | # C callbacks - memory management 92 | cdef struct pycaller: 93 | PyObject* py_function 94 | PyObject* py_arg # can be: set, None or NULL 95 | 96 | cdef pycaller* pycaller_create(PyObject* py_function, PyObject* py_arg): 97 | cdef pycaller* c = PyMem_Malloc(sizeof(pycaller)) 98 | c.py_function = py_function 99 | c.py_arg = py_arg 100 | Py_XINCREF(c.py_function) 101 | if c.py_arg != NULL: 102 | Py_XINCREF(c.py_arg) 103 | return c 104 | 105 | cdef void pycaller_free(pycaller *c): 106 | if c.py_arg != NULL: 107 | Py_XDECREF(c.py_arg) 108 | Py_XDECREF(c.py_function) 109 | PyMem_Free(c) 110 | 111 | # Take a Python object and calls it ONCE on passed argument 112 | # C callback for e.g. emscripten_async_call 113 | cdef void pycaller_callback_once(void* p): 114 | pycaller_callback_recurring(p) 115 | pycaller_free(p) 116 | 117 | # Take a Python object and calls it on passed argument 118 | # C callback for e.g. emscripten_set_main_loop_arg 119 | cdef void pycaller_callback_recurring(void* p): 120 | cdef pycaller* c = p 121 | py_function = (c.py_function) 122 | if c.py_arg != NULL: 123 | py_arg = (c.py_arg) 124 | py_function(py_arg) 125 | else: 126 | py_function() 127 | 128 | 129 | cdef pycaller* main_loop = NULL 130 | 131 | def set_main_loop_arg(py_function, py_arg, fps, simulate_infinite_loop): 132 | set_main_loop_arg_c(py_function, py_arg, 133 | fps, simulate_infinite_loop) 134 | 135 | def set_main_loop(py_function, fps, simulate_infinite_loop): 136 | set_main_loop_arg_c(py_function, NULL, 137 | fps, simulate_infinite_loop) 138 | 139 | # handle py_arg == NULL != None 140 | cdef set_main_loop_arg_c(PyObject* py_function, PyObject* py_arg, 141 | fps, simulate_infinite_loop): 142 | global main_loop 143 | if main_loop == NULL: 144 | main_loop = pycaller_create(py_function, py_arg) 145 | else: 146 | pass # invalid, let emscripten_set_main_loop_arg() abort 147 | emscripten_set_main_loop_arg(pycaller_callback_recurring, main_loop, 148 | fps, simulate_infinite_loop) 149 | 150 | def cancel_main_loop(): 151 | global main_loop 152 | emscripten_cancel_main_loop() 153 | if main_loop != NULL: 154 | pycaller_free(main_loop) 155 | main_loop = NULL 156 | 157 | # import emscripten,sys 158 | # emscripten.set_main_loop(lambda: sys.stdout.write("main_loop\n"), 2, 0) 159 | # emscripten.cancel_main_loop() 160 | # emscripten.set_main_loop(lambda: sys.stdout.write("main_loop\n"), -1, 1) 161 | # emscripten.set_main_loop_arg(lambda a: sys.stdout.write(a), "main_loop_arg\n", 2, 0) 162 | 163 | def async_call(py_function, py_arg, millis): 164 | cdef pycaller* c = pycaller_create(py_function, py_arg) 165 | emscripten_async_call(pycaller_callback_once, c, millis) 166 | 167 | # emscripten.async_call(lambda a: sys.stdout.write(a), "async_call_arg\n", 1000) 168 | 169 | 170 | def exit_with_live_runtime(): 171 | emscripten_exit_with_live_runtime(); 172 | 173 | def sleep(ms): 174 | emscripten_sleep(ms) 175 | 176 | # Emterpreter-only 177 | #def sleep_with_yield(ms): 178 | # emscripten_sleep_with_yield(ms) 179 | 180 | def run_script(script): 181 | emscripten_run_script(script.encode('UTF-8')); 182 | 183 | def run_script_int(script): 184 | return emscripten_run_script_int(script.encode('UTF-8')); 185 | 186 | def run_script_string(script): 187 | return emscripten_run_script_string(script.encode('UTF-8')); 188 | 189 | 190 | # async_wget 191 | # Requires a C function without parameter, while we need to set 192 | # callpyfunc_arg as callback (so we can call a Python function) 193 | # Perhaps doable if we maintain a list of Python callbacks indexed by 'file' (and ignore potential conflict) 194 | # Or dynamically generate C callbacks in WebAssembly but I doubt that's simple. 195 | # Or implement it with async_wget_data + write output file manually (implies an additional copy) 196 | #def async_wget(url, file, onload, onerror): 197 | # pass 198 | 199 | cdef class pycaller_async_wget: 200 | cdef onload 201 | cdef onerror 202 | cdef arg 203 | def __cinit__(self, onload, onerror, arg): 204 | self.onload = onload 205 | self.onerror = onerror 206 | self.arg = arg 207 | #def __dealloc__(self): 208 | # print("dealloc") 209 | 210 | cdef void pycaller_callback_async_wget_onload(void* p, void* buf, int size): 211 | c = p 212 | # https://cython.readthedocs.io/en/latest/src/tutorial/strings.html#passing-byte-strings 213 | py_buf = (buf)[:size] # copy 214 | c.onload(c.arg, py_buf) 215 | Py_XDECREF(p) 216 | # 'buf' freed right after by emscripten 217 | 218 | cdef void pycaller_callback_async_wget_onerror(void* p): 219 | c = p 220 | if c.onerror is not None: 221 | c.onerror(c.arg) 222 | Py_XDECREF(p) 223 | 224 | def async_wget_data(url, arg, onload, onerror=None): 225 | cdef pycaller_async_wget c = pycaller_async_wget(onload, onerror, arg) 226 | cdef PyObject* p = c 227 | Py_XINCREF(p) # survive until callback 228 | emscripten_async_wget_data(url.encode('UTF-8'), p, 229 | pycaller_callback_async_wget_onload, 230 | pycaller_callback_async_wget_onerror) 231 | 232 | # emscripten.async_wget_data('/', {'a':1}, lambda arg,buf: sys.stdout.write(repr(arg)+"\n"+repr(buf)+"\n"), lambda arg: sys.stdout.write(repr(arg)+"\nd/l error\n")) 233 | # emscripten.async_wget_data('https://bank.confidential/', None, None, lambda arg: sys.stdout.write("d/l error\n")) 234 | # emscripten.async_wget_data('https://bank.confidential/', None, None) 235 | 236 | 237 | # requires -s RETAIN_COMPILER_SETTINGS=1 (otherwise Exception) 238 | def get_compiler_setting(name): 239 | cdef void* amb = emscripten_get_compiler_setting(name.encode('UTF-8')) 240 | # can be int or char*, use heuristic 241 | # otherwise we could whitelist all known string parameters, if that's possible 242 | if amb < 1000: 243 | return amb 244 | else: 245 | return amb # c_string_encoding 246 | # emscripten.get_compiler_setting('EMULATE_FUNCTION_POINTER_CASTS') 247 | # 1 248 | # emscripten.get_compiler_setting('OPT_LEVEL') 249 | # 3 250 | # emscripten.get_compiler_setting('EMSCRIPTEN_VERSION') 251 | # u'1.39.0' 252 | # emscripten.get_compiler_setting('non-existent') 253 | # u'invalid compiler setting: non-existent' 254 | # emscripten.get_compiler_setting('EXPORTED_FUNCTIONS') 255 | # u'invalid compiler setting: EXPORTED_FUNCTIONS' # :( 256 | 257 | def debugger(): 258 | emscripten_debugger() 259 | # open the JavaScript console 260 | # emscripten.debugger() 261 | 262 | def log(flags, fmt, *args): 263 | # No variadic function support in Cython? 264 | # No va_arg variant for emscripten_log either. 265 | # Let's offer limited support 266 | cdef char* cstr 267 | cdef char* cformat 268 | pystrfmt = fmt.encode('UTF-8') 269 | cformat = pystrfmt 270 | if len(args) == 0: 271 | emscripten_log(flags, cformat) 272 | elif len(args) > 0: 273 | if len(args) == 1: 274 | arg = args[0] 275 | if type(arg) == int: 276 | emscripten_log(flags, cformat, arg) 277 | elif type(arg) == float: 278 | emscripten_log(flags, cformat, arg) 279 | elif type(arg) in (str, unicode): 280 | pystr = arg.encode('UTF-8') 281 | cstr = pystr 282 | emscripten_log(flags, cformat, cstr) 283 | else: 284 | pystr = ("emscripten.log: unsupported argument " + str(type(arg))).encode('UTF-8') 285 | cstr = pystr 286 | emscripten_log(flags, cstr) 287 | else: 288 | emscripten_log(flags, "emscripten.log: only up to 2 arguments are supported") 289 | # import emscripten; emscripten.log(0, "hello %02d", 1) 290 | # import emscripten; emscripten.log(emscripten.LOG_WARN|emscripten.LOG_CONSOLE|emscripten.LOG_C_STACK, "warning!") 291 | # emscripten_log doesn't to properly support UTF-8 292 | # import emscripten; emscripten.log(0, u"é") 293 | # import emscripten; emscripten.log(0, "%s", u"é") 294 | 295 | def get_callstack(flags): 296 | cdef int size = emscripten_get_callstack(flags, NULL, 0) 297 | # "subsequent calls will carry different line numbers, so it is 298 | # best to allocate a few bytes extra to be safe" 299 | size += 1024 300 | cdef char* buf = PyMem_Malloc(size) 301 | emscripten_get_callstack(flags, buf, size) 302 | cdef object ret = buf # c_string_encoding 303 | PyMem_Free(buf) 304 | return ret 305 | # from emscripten import * 306 | # print(get_callstack(0)) 307 | # print(get_callstack(LOG_C_STACK|LOG_JS_STACK|LOG_DEMANGLE|LOG_NO_PATHS|LOG_FUNC_PARAMS)) 308 | 309 | 310 | # Pseudo-synchronous, requires ASYNCIFY 311 | def wget(url, file): 312 | return emscripten_wget(url.encode('UTF-8'), file.encode('UTF-8')) 313 | # emscripten.wget('/hello', '/hello'); open('/hello','rb').read() 314 | # Notes: 315 | # - FS error if file already exists 316 | # - Download indicator showing up not going away 317 | # - Download progress bar showing up not going away on error 318 | 319 | # Wrap a malloc'd buffer with buffer interface and automatic free() 320 | cdef class MallocBuffer: 321 | cdef char *buf 322 | cdef int size 323 | def __init__(self): 324 | raise Exception("MallocBuffer: constructor not available from Python") 325 | # constructor from non-Python parameters (__cinit__ don't accept them) 326 | @staticmethod 327 | cdef MallocBuffer from_string_and_size(char* buf, int size): 328 | cdef MallocBuffer ret = MallocBuffer.__new__(MallocBuffer) 329 | ret.buf = buf 330 | ret.size = size 331 | return ret 332 | def __dealloc__(self): 333 | free(self.buf) 334 | def __getbuffer__(self, Py_buffer *view, int flags): 335 | is_readonly = 0 336 | PyBuffer_FillInfo(view, self, self.buf, self.size, is_readonly, flags) 337 | def __releasebuffer__(self, Py_buffer *view): 338 | pass 339 | 340 | # Pseudo-synchronous, requires ASYNCIFY 341 | def wget_data(url): 342 | cdef char* buf 343 | cdef int num, error 344 | emscripten_wget_data(url.encode('UTF-8'), &buf, &num, &error) 345 | if error != 0: 346 | return None 347 | pybuf = MallocBuffer.from_string_and_size(buf, num) 348 | return pybuf 349 | # import emscripten,cStringIO; r = emscripten.wget_data('/hello'); cStringIO.StringIO(r).read(); memoryview(r).tobytes() 350 | 351 | 352 | # Non-API utility 353 | 354 | def syncfs(): 355 | emscripten_run_script(r""" 356 | FS.syncfs(false, function(err) { 357 | if (err) { 358 | console.trace(); console.log(err, err.message); 359 | Module.print("Warning: write error: " + err.message + "\n"); 360 | } 361 | }); 362 | """); 363 | -------------------------------------------------------------------------------- /emscripten_fetch.pyx: -------------------------------------------------------------------------------- 1 | # Python wrapper for emscripten_* C functions - Fetch API 2 | 3 | # Copyright (C) 2019 Sylvain Beucler 4 | 5 | # Copying and distribution of this file, with or without modification, 6 | # are permitted in any medium without royalty provided the copyright 7 | # notice and this notice are preserved. This file is offered as-is, 8 | # without any warranty. 9 | 10 | # http://docs.cython.org/en/latest/src/tutorial/strings.html#auto-encoding-and-decoding 11 | # Most of our strings are converted from/to JS through emscripten stringToUTF8/UTF8ToString 12 | # cython: c_string_type=unicode, c_string_encoding=utf8 13 | # Note: Py->C auto-UTF-8 (instead of .encode('UTF-8')) not supported for Py2 14 | # Note: causes issues with Typed Memoryviews 15 | 16 | # TODO: move to emscripten.fetch but requires emscripten/__init__.py, 17 | # patching emscripten_fetch.c, etc. 18 | 19 | from __future__ import print_function 20 | 21 | from cpython.mem cimport PyMem_Malloc, PyMem_Free 22 | from cpython.ref cimport PyObject, Py_XINCREF, Py_XDECREF 23 | 24 | from cpython.buffer cimport PyBuffer_FillInfo 25 | 26 | from libc.string cimport strncpy 27 | 28 | from libc.stdint cimport uint32_t, uint64_t 29 | 30 | cdef extern from "emscripten/html5.h": 31 | ctypedef int EM_BOOL 32 | ctypedef int EMSCRIPTEN_RESULT 33 | enum: EM_TRUE 34 | enum: EM_FALSE 35 | 36 | cdef extern from "emscripten/fetch.h": 37 | ctypedef struct emscripten_fetch_attr_t: 38 | char requestMethod[32] 39 | void *userData 40 | void (*onsuccess)(emscripten_fetch_t *fetch) 41 | void (*onerror)(emscripten_fetch_t *fetch) 42 | void (*onprogress)(emscripten_fetch_t *fetch) 43 | void (*onreadystatechange)(emscripten_fetch_t *fetch) 44 | uint32_t attributes 45 | unsigned long timeoutMSecs 46 | EM_BOOL withCredentials 47 | const char *destinationPath 48 | const char *userName 49 | const char *password 50 | const char * const *requestHeaders 51 | const char *overriddenMimeType 52 | const char *requestData 53 | size_t requestDataSize 54 | 55 | ctypedef struct emscripten_fetch_t: 56 | unsigned int id 57 | void *userData 58 | const char *url 59 | const char *data 60 | uint64_t numBytes 61 | uint64_t dataOffset 62 | uint64_t totalBytes 63 | unsigned short readyState 64 | unsigned short status 65 | char statusText[64] 66 | uint32_t __proxyState 67 | emscripten_fetch_attr_t __attributes 68 | 69 | enum: 70 | EMSCRIPTEN_FETCH_LOAD_TO_MEMORY 71 | EMSCRIPTEN_FETCH_STREAM_DATA 72 | EMSCRIPTEN_FETCH_PERSIST_FILE 73 | EMSCRIPTEN_FETCH_APPEND 74 | EMSCRIPTEN_FETCH_REPLACE 75 | EMSCRIPTEN_FETCH_NO_DOWNLOAD 76 | EMSCRIPTEN_FETCH_SYNCHRONOUS 77 | EMSCRIPTEN_FETCH_WAITABLE 78 | 79 | void emscripten_fetch_attr_init(emscripten_fetch_attr_t *fetch_attr) 80 | emscripten_fetch_t *emscripten_fetch(emscripten_fetch_attr_t *fetch_attr, const char *url) 81 | #EMSCRIPTEN_RESULT emscripten_fetch_wait(emscripten_fetch_t *fetch, double timeoutMSecs) 82 | EMSCRIPTEN_RESULT emscripten_fetch_close(emscripten_fetch_t *fetch) 83 | size_t emscripten_fetch_get_response_headers_length(emscripten_fetch_t *fetch) 84 | size_t emscripten_fetch_get_response_headers(emscripten_fetch_t *fetch, char *dst, size_t dstSizeBytes) 85 | char **emscripten_fetch_unpack_response_headers(const char *headersString) 86 | void emscripten_fetch_free_unpacked_response_headers(char **unpackedHeaders) 87 | 88 | LOAD_TO_MEMORY = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY 89 | STREAM_DATA = EMSCRIPTEN_FETCH_STREAM_DATA 90 | PERSIST_FILE = EMSCRIPTEN_FETCH_PERSIST_FILE 91 | APPEND = EMSCRIPTEN_FETCH_APPEND 92 | REPLACE = EMSCRIPTEN_FETCH_REPLACE 93 | NO_DOWNLOAD = EMSCRIPTEN_FETCH_NO_DOWNLOAD 94 | SYNCHRONOUS = EMSCRIPTEN_FETCH_SYNCHRONOUS 95 | WAITABLE = EMSCRIPTEN_FETCH_WAITABLE 96 | 97 | # Fetch API 98 | # https://emscripten.org/docs/api_reference/fetch.html 99 | 100 | # http://docs.cython.org/en/latest/src/userguide/extension_types.html 101 | cdef class Fetch: 102 | cdef emscripten_fetch_t *fetch 103 | cdef callbacks 104 | 105 | def __cinit__(self, url, requestMethod=None, userData=None, 106 | onsuccess=None, onerror=None, onprogress=None, onreadystatechange=None, 107 | attributes=None, timeoutMSecs=None, withCredentials=None, 108 | destinationPath=None, userName=None, password=None, 109 | requestHeaders=None, overriddenMimeType=None, requestData=None): 110 | 111 | # Keep track of temporary Python strings we pass emscripten_fetch() for copy 112 | py_str_refs = [] 113 | 114 | cdef emscripten_fetch_attr_t attr 115 | emscripten_fetch_attr_init(&attr) 116 | 117 | Py_XINCREF(self) # survive until callback 118 | attr.userData = self 119 | 120 | if requestMethod is not None: 121 | strncpy(attr.requestMethod, 122 | requestMethod.encode('UTF-8'), 123 | sizeof(attr.requestMethod) - 1) 124 | 125 | self.userData = userData 126 | 127 | self.callbacks = {} 128 | attr.onsuccess = callpyfunc_fetch_onsuccess 129 | attr.onerror = callpyfunc_fetch_onerror 130 | if onsuccess is not None: 131 | self.callbacks['onsuccess'] = onsuccess 132 | if onerror is not None: 133 | self.callbacks['onerror'] = onerror 134 | if onprogress is not None: 135 | self.callbacks['onprogress'] = onprogress 136 | attr.onprogress = callpyfunc_fetch_onprogress 137 | if onreadystatechange is not None: 138 | self.callbacks['onreadystatechange'] = onreadystatechange 139 | attr.onreadystatechange = callpyfunc_fetch_onreadystatechange 140 | 141 | if attributes is not None: 142 | attr.attributes = attributes 143 | if timeoutMSecs is not None: 144 | attr.timeoutMSecs = timeoutMSecs 145 | if withCredentials is not None: 146 | attr.withCredentials = withCredentials 147 | if destinationPath is not None: 148 | py_str_refs.append(destinationPath.encode('UTF-8')) 149 | attr.destinationPath = py_str_refs[-1] 150 | if userName is not None: 151 | py_str_refs.append(userName.encode('UTF-8')) 152 | attr.userName = py_str_refs[-1] 153 | if password is not None: 154 | py_str_refs.append(password.encode('UTF-8')) 155 | attr.password = py_str_refs[-1] 156 | 157 | cdef char** headers 158 | if requestHeaders is not None: 159 | size = (2 * len(requestHeaders) + 1) * sizeof(char*) 160 | headers = PyMem_Malloc(size) 161 | i = 0 162 | for name,value in requestHeaders.items(): 163 | py_str_refs.append(name.encode('UTF-8')) 164 | headers[i] = py_str_refs[-1] 165 | i += 1 166 | py_str_refs.append(value.encode('UTF-8')) 167 | headers[i] = py_str_refs[-1] 168 | i += 1 169 | headers[i] = NULL 170 | attr.requestHeaders = headers 171 | 172 | if overriddenMimeType is not None: 173 | py_str_refs.append(overriddenMimeType.encode('UTF-8')) 174 | attr.overriddenMimeType = py_str_refs[-1] 175 | 176 | if requestData is not None: 177 | size = len(requestData) 178 | attr.requestDataSize = size 179 | # direct pointer, no UTF-8 encoding pass: 180 | attr.requestData = requestData 181 | 182 | # Fetch 183 | cdef emscripten_fetch_t *fetch = emscripten_fetch(&attr, url.encode('UTF-8')) 184 | self.fetch = fetch 185 | 186 | # Explicitely deref temporary Python strings. Test for forgotten refs with e.g.: 187 | # print(attr.overriddenMimeType, attr.destinationPath, attr.userName, attr.password) 188 | del py_str_refs 189 | 190 | if requestHeaders is not None: 191 | PyMem_Free(attr.requestHeaders) 192 | 193 | def __dealloc__(self): 194 | emscripten_fetch_close(self.fetch) 195 | 196 | # Currently unsafe: 197 | # https://github.com/emscripten-core/emscripten/issues/8234 198 | #def fetch_close(fetch): 199 | # pass 200 | 201 | # http://docs.cython.org/en/latest/src/userguide/buffer.html 202 | # https://docs.python.org/3/c-api/typeobj.html#c.PyBufferProcs.bf_getbuffer 203 | # https://docs.python.org/3/c-api/buffer.html#c.PyObject_GetBuffer 204 | # https://docs.python.org/3/c-api/buffer.html#c.PyBuffer_FillInfo 205 | def __getbuffer__(self, Py_buffer *view, int flags): 206 | if self.fetch.data != NULL: 207 | is_readonly = 1 208 | PyBuffer_FillInfo(view, self, self.fetch.data, self.fetch.numBytes, is_readonly, flags) 209 | else: 210 | view.obj = None 211 | raise BufferError 212 | def __releasebuffer__(self, Py_buffer *view): 213 | pass 214 | 215 | def __repr__(self): 216 | return u''.format(repr(self.id), repr(self.userData), repr(self.url), self.data and "" or "None", repr(self.numBytes), repr(self.dataOffset), repr(self.totalBytes), repr(self.readyState), repr(self.status), repr(self.statusText)) 217 | 218 | # For testing whether a copy occurred: 219 | #def overwrite(self): 220 | # cdef char* overwrite = (self.fetch.data) 221 | # overwrite[0] = b'O' 222 | 223 | def get_response_headers(self): 224 | cdef char* buf = NULL 225 | # Note: JS crash if applied on a persisted request from IDB cache 226 | # https://github.com/emscripten-core/emscripten/issues/7026#issuecomment-545488132 227 | cdef length = emscripten_fetch_get_response_headers_length(self.fetch) 228 | if length > 0: 229 | headersString = PyMem_Malloc(length) 230 | emscripten_fetch_get_response_headers(self.fetch, headersString, length+1) 231 | ret = headersString[:length] # copy 232 | PyMem_Free(headersString) 233 | return ret 234 | else: 235 | return None 236 | 237 | def get_unpacked_response_headers(self): 238 | cdef char* headersString = NULL 239 | cdef char** unpackedHeaders = NULL 240 | # Note: JS crash if applied on a persisted request from IDB cache 241 | cdef length = emscripten_fetch_get_response_headers_length(self.fetch) 242 | if length > 0: 243 | headersString = PyMem_Malloc(length) 244 | emscripten_fetch_get_response_headers(self.fetch, headersString, length+1) 245 | unpackedHeaders = emscripten_fetch_unpack_response_headers(headersString) 246 | PyMem_Free(headersString) 247 | d = {} 248 | i = 0 249 | while unpackedHeaders[i] != NULL: 250 | k = unpackedHeaders[i] # c_string_encoding 251 | i += 1 252 | v = unpackedHeaders[i] # c_string_encoding 253 | i += 1 254 | d[k] = v 255 | emscripten_fetch_free_unpacked_response_headers(unpackedHeaders) 256 | return d 257 | else: 258 | return None 259 | 260 | @property 261 | def id(self): 262 | return self.fetch.id 263 | cdef readonly userData 264 | @property 265 | def url(self): 266 | #return self.fetch.url.decode('UTF-8') 267 | return self.fetch.url # c_string_encoding 268 | @property 269 | def data(self): 270 | if self.fetch.data != NULL: 271 | return self 272 | else: 273 | return None 274 | @property 275 | def numBytes(self): 276 | return self.fetch.numBytes 277 | @property 278 | def dataOffset(self): 279 | return self.fetch.dataOffset 280 | @property 281 | def totalBytes(self): 282 | return self.fetch.totalBytes # Content-Length 283 | @property 284 | def readyState(self): 285 | return self.fetch.readyState 286 | @property 287 | def status(self): 288 | return self.fetch.status 289 | @property 290 | def statusText(self): 291 | #return self.fetch.statusText.decode('UTF-8') 292 | return self.fetch.statusText # c_string_encoding 293 | 294 | cdef void callpyfunc_fetch_callback(emscripten_fetch_t *fetch, char* callback_name): 295 | cdef Fetch py_fetch = fetch.userData 296 | # for theoretical concurrency, if we're called during emscripten_fetch() 297 | py_fetch.fetch = fetch 298 | # call Python function 299 | if py_fetch.callbacks.get(callback_name, None): 300 | py_fetch.callbacks[callback_name](py_fetch) 301 | 302 | # one of {onsuccess,onerror} is guaranteed to run, deref Fetch there 303 | cdef void callpyfunc_fetch_onsuccess(emscripten_fetch_t *fetch): 304 | callpyfunc_fetch_callback(fetch, 'onsuccess') 305 | Py_XDECREF(fetch.userData) 306 | cdef void callpyfunc_fetch_onerror(emscripten_fetch_t *fetch): 307 | callpyfunc_fetch_callback(fetch, 'onerror') 308 | Py_XDECREF(fetch.userData) 309 | cdef void callpyfunc_fetch_onprogress(emscripten_fetch_t *fetch): 310 | callpyfunc_fetch_callback(fetch, 'onprogress') 311 | cdef void callpyfunc_fetch_onreadystatechange(emscripten_fetch_t *fetch): 312 | callpyfunc_fetch_callback(fetch, 'onreadystatechange') 313 | 314 | 315 | # import emscripten_fetch,sys; f=lambda x:sys.stdout.write(repr(x)+"\n"); 316 | # #Module.cwrap('PyRun_SimpleString', 'number', ['string'])("def g(x):\n global a; a=x") 317 | # emscripten_fetch.Fetch('/', onsuccess=f) 318 | # emscripten_fetch.Fetch(u'/helloé', onsuccess=f) 319 | # emscripten_fetch.Fetch('/hello', attributes=emscripten_fetch.LOAD_TO_MEMORY, onsuccess=f); del f # output 320 | # fetch_attr={'onsuccess':f}; emscripten_fetch.Fetch('/hello', **fetch_attr); del fetch_attr['onsuccess'] # output 321 | # emscripten_fetch.Fetch('/non-existent', onerror=lambda x:sys.stdout.write(repr(x)+"\n")) 322 | # emscripten_fetch.Fetch('https://bank.confidential/', onerror=lambda x:sys.stdout.write(repr(x)+"\n")) # simulated 404 323 | # emscripten_fetch.Fetch('/hello', attributes=emscripten_fetch.LOAD_TO_MEMORY|emscripten_fetch.PERSIST_FILE, onsuccess=f) 324 | # Note: fe.fetch.id changes (in-place) when first caching 325 | # emscripten_fetch.Fetch('/hello', requestMethod='EM_IDB_DELETE', onsuccess=f) 326 | # emscripten_fetch.Fetch('/hello', attributes=emscripten_fetch.LOAD_TO_MEMORY, requestMethod='POST', requestData='AA\xffBB\x00CC', onsuccess=f, onerror=f) 327 | # emscripten_fetch.Fetch('/hello', attributes=emscripten_fetch.LOAD_TO_MEMORY, requestMethod='12345678901234567890123456789012', onerror=f) 328 | # emscripten_fetch.Fetch('/hello', attributes=emscripten_fetch.LOAD_TO_MEMORY, onsuccess=f, userData='userData', overriddenMimeType='text/html', userName='userName', password='password', requestHeaders={'Content-Type':'text/plain','Cache-Control':'no-store'}) 329 | # emscripten_fetch.Fetch('/hello', attributes=emscripten_fetch.LOAD_TO_MEMORY|emscripten_fetch.PERSIST_FILE, onsuccess=f, destinationPath='destinationPath'); emscripten_fetch.Fetch('destinationPath', requestMethod='EM_IDB_DELETE', onsuccess=f) 330 | # fe=emscripten_fetch.Fetch('/hello', attributes=emscripten_fetch.LOAD_TO_MEMORY|emscripten_fetch.PERSIST_FILE, onsuccess=f, destinationPath='destinationPath'); fe2=emscripten_fetch.Fetch('destinationPath', requestMethod='EM_IDB_DELETE', onsuccess=f); print("fe=",fe); print("fe2=",fe2) 331 | # Note: fe2 can occur before fe1 332 | # r=emscripten_fetch.Fetch('/hello', attributes=emscripten_fetch.LOAD_TO_MEMORY) 333 | # open('test.txt','wb').write(r); open('test.txt','rb').read() 334 | # r.data != None 335 | # memoryview(r)[:5].tobytes() 336 | # import cStringIO; cStringIO.StringIO(r).read(5) 337 | -------------------------------------------------------------------------------- /git-export.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Export Fossil repository so it can be pushed to GitLab/GitHub 4 | 5 | # Copyright (C) 2019 Sylvain Beucler 6 | 7 | # Copying and distribution of this file, with or without modification, 8 | # are permitted in any medium without royalty provided the copyright 9 | # notice and this notice are preserved. This file is offered as-is, 10 | # without any warranty. 11 | 12 | FOSSIL_CHECKOUT=$(dirname $(readlink -f $0)) 13 | 14 | # Populate authors list if needed 15 | ( 16 | cd $FOSSIL_CHECKOUT/ 17 | if ! fossil user capabilities Beuc > /dev/null; then 18 | fossil user new Beuc beuc@beuc.net '' 19 | fi 20 | ) 21 | 22 | # Convert commits 23 | (cd $FOSSIL_CHECKOUT/ && fossil export --git) \ 24 | | git fast-import 25 | git reset HEAD . 26 | git checkout . 27 | -------------------------------------------------------------------------------- /mock/emscripten.pyx: -------------------------------------------------------------------------------- 1 | # Fake python wrapper for emscripten_* C functions for native testing 2 | 3 | # Copyright (C) 2018, 2020 Sylvain Beucler 4 | 5 | # Copying and distribution of this file, with or without modification, 6 | # are permitted in any medium without royalty provided the copyright 7 | # notice and this notice are preserved. This file is offered as-is, 8 | # without any warranty. 9 | 10 | import time 11 | import sys 12 | 13 | sys.platform = 'emscripten' 14 | 15 | def set_main_loop(py_function, fps, simulate_infinite_loop): 16 | print("def: set_main_loop", py_function, fps, simulate_infinite_loop) 17 | if not simulate_infinite_loop: 18 | # TODO: simulate browser loop in another Python script? 19 | pass 20 | py_function = (py_function) 21 | if fps <= 0: 22 | fps = 60 # common screen refresh rate 23 | while True: 24 | time.sleep(1.0/fps) 25 | py_function() 26 | 27 | def async_call(func, arg, millis): 28 | py_function = (func) 29 | py_arg = (arg) 30 | py_function(py_arg) 31 | 32 | def exit_with_live_runtime(): 33 | print("exit_with_live_runtime") 34 | 35 | def sleep(ms): 36 | time.sleep(ms/1000.0) 37 | 38 | def sleep_with_yield(ms): 39 | sleep(ms) 40 | 41 | def run_script(script): 42 | print("run_script") 43 | print('\n'.join([" "+l for l in script.splitlines()])) 44 | 45 | def syncfs(): 46 | print("syncfs") 47 | -------------------------------------------------------------------------------- /mock/emscripten_fetch.pyx: -------------------------------------------------------------------------------- 1 | # Fake python wrapper for emscripten_fetch* C functions for native testing 2 | 3 | # Copyright (C) 2020 Sylvain Beucler 4 | 5 | # Copying and distribution of this file, with or without modification, 6 | # are permitted in any medium without royalty provided the copyright 7 | # notice and this notice are preserved. This file is offered as-is, 8 | # without any warranty. 9 | 10 | -------------------------------------------------------------------------------- /static_submodules-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | PyMODINIT_FUNC inittestmod_testsubmod(void); /*proto*/ 5 | 6 | int main(void) { 7 | // Trace modules loading 8 | //setenv("PYTHONVERBOSE", "1", 0); 9 | // Load additional modules installed relative to current directory 10 | setenv("PYTHONPATH", ".", 0); 11 | 12 | Py_InitializeEx(0); 13 | 14 | //inittestmod_testsubmod(); 15 | static struct _inittab builtins[] = { 16 | {"testmod.testsubmod", inittestmod_testsubmod}, 17 | {NULL, NULL} 18 | }; 19 | PyImport_ExtendInittab(builtins); 20 | 21 | PyRun_SimpleString("import testmod.testsubmod; testmod.testsubmod.myputs()"); 22 | Py_Finalize(); 23 | } 24 | 25 | /* 26 | 27 | cat <<'EOF' > testmod/testsubmod.pyx 28 | cdef extern from "stdio.h": 29 | int puts(const char *s); 30 | 31 | def myputs(): 32 | puts("\n\nHELLOOOOOOOOOO\n\n"); 33 | EOF 34 | 35 | cython testmod/testsubmod.pyx 36 | sed -i -e 's|Py_InitModule4("\([^"]\+\)"|Py_InitModule4("testmod.\1"|' \ 37 | -e 's|^__Pyx_PyMODINIT_FUNC init|__Pyx_PyMODINIT_FUNC inittestmod_|' testmod/testsubmod.c 38 | gcc -I../../Include -I.. testmod/testsubmod.c main.c ../libpython2.7.a -lm -ldl -lutil && ./a.out 39 | 40 | See also https://mdqinc.com/blog/2011/08/statically-linking-python-with-cython-generated-modules-and-packages/ 41 | 42 | */ 43 | -------------------------------------------------------------------------------- /webprompt-main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple Python prompt with static emscripten module available 3 | * 4 | * Copyright (C) 2019, 2020 Sylvain Beucler 5 | * 6 | * Copying and distribution of this file, with or without 7 | * modification, are permitted in any medium without royalty provided 8 | * the copyright notice and this notice are preserved. This file is 9 | * offered as-is, without any warranty. 10 | */ 11 | 12 | #ifdef __EMSCRIPTEN__ 13 | #include 14 | #endif 15 | #include 16 | 17 | #if PY_MAJOR_VERSION >= 3 18 | #define MODINIT(name) PyInit_##name 19 | #else 20 | #define MODINIT(name) init##name 21 | #endif 22 | 23 | PyMODINIT_FUNC MODINIT(emscripten) (void); 24 | PyMODINIT_FUNC MODINIT(emscripten_fetch) (void); 25 | 26 | // Run a line *and* display the result 27 | // PyRun_StringFlags only returns a non-None object with Py_eval_input (no 'print' support) 28 | // PyRun_InteractiveOne always reads stdin even with another 'fp', so we redirect stdin 29 | void pyruni() { 30 | freopen("/tmp/input.py", "rb", stdin); 31 | PyRun_InteractiveOne(stdin, ""); 32 | } 33 | 34 | int main(int argc, char**argv) { 35 | Py_OptimizeFlag = 2; // look for .pyo rather than .pyc 36 | Py_FrozenFlag = 1; // drop warnings 37 | Py_VerboseFlag = 1; // trace modules loading 38 | static struct _inittab builtins[] = { 39 | { "emscripten", MODINIT(emscripten) }, 40 | { "emscripten_fetch", MODINIT(emscripten_fetch) }, 41 | {NULL, NULL} 42 | }; 43 | PyImport_ExtendInittab(builtins); 44 | Py_InitializeEx(0); // 0 = get rid of 'Calling stub instead of sigaction()' 45 | #ifdef __EMSCRIPTEN__ 46 | emscripten_exit_with_live_runtime(); 47 | #else 48 | pyruni(); 49 | #endif 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /webprompt-shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Emscripten-Generated Code 8 | 50 | 51 | 52 |
53 |
emscripten
54 |
Downloading...
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
python-emscripten bare-bones demo
64 | 65 |
66 | 67 | 68 | 69 | 74 | 75 |
76 | 77 | 153 | 154 | 155 | 207 | 208 | 209 | {{{ SCRIPT }}} 210 | 211 | 212 | --------------------------------------------------------------------------------