Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/python-wasm
Path: blob/main/python/cpython/Makefile
1386 views
include ../build/Makefile-vars

#
# We build a native version of CPython and a WASM version.
# The native version is ONLY used as part of the cross compilation build process
# and not used for anything else.  TODO: Currently, we can't build native Python using
# only zig on our supported architectures, so we use the native toolchain, which is
# really annoying and makes bootstraping a build a little harder.
#
# See https://www.python.org/downloads/
PYTHON_MAJOR = 3
PYTHON_MINOR = 11
PYTHON_PATCH = 2
PYTHON_BETA =
PYTHON_VERSION = ${PYTHON_MAJOR}.${PYTHON_MINOR}.${PYTHON_PATCH}

URL = https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}${PYTHON_BETA}.tar.xz
TARBALL = ${UPSTREAM}/python-${PYTHON_VERSION}${PYTHON_BETA}.tar.xz

ZLIB_NATIVE = ${PACKAGES}/zlib-native/dist/native
LIBEDIT_NATIVE = ${PACKAGES}/libedit-native/dist/native

POSIX_WASM = $(shell cowasm-package-path @cowasm/posix-wasm)
LIBEDIT_WASM = $(shell cowasm-package-path @cowasm/libedit)
LZMA_WASM = $(shell cowasm-package-path @cowasm/lzma)
NCURSES_WASM = $(shell cowasm-package-path @cowasm/ncurses)
TERMCAP_WASM = $(shell cowasm-package-path @cowasm/termcap)
OPENSSL_WASM = $(shell cowasm-package-path @cowasm/openssl)
SQLITE_WASM = $(shell cowasm-package-path @cowasm/sqlite)
BZIP2_WASM = $(shell cowasm-package-path @cowasm/bzip2)
ZLIB_WASM = $(shell cowasm-package-path @cowasm/zlib)
LIBFFI_WASM = $(shell cowasm-package-path @cowasm/libffi)

PYTHON_WASM = ${BIN}/python-wasm

# Either -Oz or -O3 make sense to me...
#   -O0 = exceeds a limit so can't even load the result with webassembly!
# The cPython default opt is "-DNDEBUG -g -fwrapv -O3 -Wall", which is
# coded in the generated Makefile.  I have noticed some major compiler bugs
# with -O3, so we can revisit this later with newer LLVM/zig releases.
# For example:
#   https://github.com/sagemathinc/cowasm/issues/27
# is a bug with -O3 that goes away with -Oz. The diff in performance
# is less than 5% with -O3 over -Oz.
# NDEBUG I think disable assertions (?).

# Use this instead for better debugging (?).

WASM_OPT=-DNDEBUG -fwrapv -Wall -Oz -g
#WASM_OPT=-DNDEBUG -fwrapv -Wall -Oz

# Default target
all: deps native ${BIN}/python-native wasm ${BIN}/python-wasm

${DIST}/.built: native wasm
	touch ${DIST}/.built

include ../build/Makefile-rules

## NATIVE

${BUILD_NATIVE}/.build:: ${TARBALL}
	cp src/Setup.local-native ${BUILD_NATIVE}/Modules/Setup.local
	# This _scproxy is needed macos *only* for pip; on Linux it breaks the build.
	if [ `uname -s` == "Darwin" ]; then echo "_scproxy _scproxy.c" >> ${BUILD_NATIVE}/Modules/Setup.local; fi

${DIST_NATIVE}/.built: ${BUILD_NATIVE}/.build
	cd ../build && make zig
	cd ../lzma-native && make
	cd ../zlib-native && make
	cd ../termcap-native && make
	cd ../libedit-native && make
	cd ${BUILD_NATIVE} \
		&& 	AR="zig ar" \
			CXX="zig c++ ${ZIG_NATIVE_CFLAGS}" \
			CC="zig cc ${ZIG_NATIVE_CFLAGS}" \
			CFLAGS="-I${ZLIB_NATIVE}/include -I${LIBEDIT_NATIVE}/include" \
			LDFLAGS="-L${ZLIB_NATIVE}/lib -L${LIBEDIT_NATIVE}" \
			./configure \
				--prefix=${DIST_NATIVE} \
				--with-ensurepip \
				--disable-shared \
				--with-readline=editline
	# On **some** macOS systems having either HAVE_MKFIFOAT or HAVE_MKNODAT breaks
	# the build due to I guess bugs with zig and __builtin_available.   We don't need
	# these anywhere, since native cpython is just for bootstrapping our WASM build anyways.
	# NOTE: I didn't hit this with 3.11.0.rc2, but did with the actual release.
	echo "#undef HAVE_MKFIFOAT" >> ${BUILD_NATIVE}/pyconfig.h
	echo "#undef HAVE_MKNODAT" >> ${BUILD_NATIVE}/pyconfig.h
	# We **must** explicitly set RUNSHARED on MacOS since it's wrong there (e.g., it misses
	# zlib).  This is to workaround a security feature of MacOS (see
	# https://developer.apple.com/forums/thread/9233). For simplicity we set this on all hosts.
	cd ${BUILD_NATIVE} \
		&&	make -j8 RUNSHARED="DYLD_LIBRARY_PATH=${BUILD_NATIVE}:${DYLD_LIBRARY_PATH} LD_LIBRARY_PATH=${BUILD_NATIVE}:${LD_LIBRARY_PATH}" \
		&&	make RUNSHARED="DYLD_LIBRARY_PATH=${BUILD_NATIVE}:${DYLD_LIBRARY_PATH} LD_LIBRARY_PATH=${BUILD_NATIVE}:${LD_LIBRARY_PATH}" install
	cp ${SRC}/cowasm_bundler.py ${DIST_NATIVE}/lib/python3.11/site-packages
	touch ${DIST_NATIVE}/.built

${BIN}/python-native: bin/python-native ${DIST_NATIVE}/.built
	rm -f ${BIN}/python-native
	ln -s ${CWD}/bin/python-native ${BIN}/python-native
	touch ${BIN}/python-native

# Use "make run-native" to run native python at the command line.
.PHONY: run-native
run-native: ${DIST_NATIVE}/.built ${BIN}/python-native
	${BIN}/python-native


## WASM

${BUILD_WASM}/.patched: ${BUILD_WASM}/.build
	# Create declarations that make everything extension modules need
	# accessible from outside our "python.wasm as a shared library".
	pnpm dlx dylink ${BUILD_WASM} > ${BUILD_WASM}/Programs/libpython.c
	# Scripts to make it easy to change CPython and rebuild iteratively
	ln -sf ${SRC}/rebuild ${BUILD_WASM}/rebuild
	# Copy the config.site, which answers questions needed for
	# cross compiling, without which ./configure won't work.
	cp src/config.site ${BUILD_WASM}
	# Configure how modules are built
	cp src/Setup.local ${BUILD_WASM}/Modules/Setup.local
	# Custom code to run at startup
	cp src/sitecustomize.py ${BUILD_WASM}/Lib
	# Copy over our small cowasm-specific library with functionality for our packages.
	cp ${SRC}/cowasm_bundler.py ${BUILD_WASM}/Lib
	cp ${SRC}/cowasm_importer.py ${BUILD_WASM}/Lib
	# Make empty sys/wait.h so that python's configure will conclude that we have sys/wait.h; we will
	# explicitly add anything that is really used from there to posix-wasm.h
	mkdir ${BUILD_WASM}/sys
	echo '#include "posix-wasm.h"' >  ${BUILD_WASM}/sys/wait.h
	# Apply our patches:
	cd ${BUILD_WASM} && cat ${SRC}/patches/01-main.patch | patch -p1
	cd ${BUILD_WASM} && cat ${SRC}/patches/02-pydoc.patch | patch -p1
	cd ${BUILD_WASM} && cat ${SRC}/patches/03-wasm-assets.patch | patch -p1
	# Apply this patch when we want to take testing to the next level, and fully enable all
	# tests that involve subprocess support.  Our goal is to support subprocesses fully, but
	# it is NOT to support fork, which is just not possible in general with WebAssembly.
	# However, I believe subprocess support is possible in a fully solid robust way.
	cd ${BUILD_WASM} && cat ${SRC}/patches/04-enable-subprocess-tests.patch | patch -p1
	cd ${BUILD_WASM} && cat ${SRC}/patches/05-st_mode.patch | patch -p1
	# Implement 'import platform; platform.architecture()' for wasm.
	cd ${BUILD_WASM} && cat ${SRC}/patches/06-platform.patch | patch -p1
	# Fork and execv on top of posix-node work, but it takes some restrictions to be robust:
	cd ${BUILD_WASM} && cat ${SRC}/patches/07-subprocess.patch | patch -p1
	cd ${BUILD_WASM} && cat ${SRC}/patches/09-set-inheritable.patch | patch -p1
	# Patch 10-setuptools-c++.patch is used, but only *AFTER* ensurepip happens!
	# clang15 is more strict:
	cd ${BUILD_WASM} && cat ${SRC}/patches/12-timemodule-clang15.patch | patch -p1
	# We can't use the modified WASI socket.h headers, since they annoyingly render the
	# data structure useless (why!? - to save a few bytes?).
	# See packages/python-wasm/src/wasm/posix/socket.zig  (which will move soon)
	cd ${BUILD_WASM} && cat ${SRC}/patches/13-socket-unmodified-headers.patch | patch -p1
	# Warn instead of error on attempt to start a thread.
	# cd ${BUILD_WASM} && cat ${SRC}/patches/14-threading-warning.patch | patch -p1
	touch ${BUILD_WASM}/.patched

DEBUG_MODE = ""
# DEBUG_MODE = "-g"
BLDSHARED = cowasm-cc --experimental-pic -shared ${DEBUG_MODE}

${DIST_WASM}/.built-cpython: node_modules ${BUILD_WASM}/.patched ${DIST_NATIVE}/.built ${BIN}/python-native
	# - Important to set CXX even though it's not in the main build so zig is used for C++ extension modules.
	# - with-pymalloc it actually works and seems faster
	# - We use "zig cc -target wasm32-wasi" instead of "zig cc -target wasm32-wasi-musl" since the broken
	#   Python configure.ac script outputs wasm32-wasi instead of wasm32-wasi-musl as the PLATFORM_TRIPLE.
	#   This might be something to fix and upstream, but for now, just not using a triple works.
	# - We use -I (path to source) in the CC/CXX line in addition to CFLAGS so that we can override some
	#   bad global libraries, e.g., systemwide (zig musl) sys/wait.h with a local version.
	# - In Python source they lower the default recursion limit; however, we don't need that since our
	#   runtime is Chromium, not wasitime and we can handle huge limits (see Include/internal/pycore_ceval.h).
	# - enable-ipv6, since I put a lot of work into this for posix-node!
	# --with-pkg-config=no, this is CRITICAL; otherwise, if pkg-config is installed, then python's build system
	#   will use it and pick up system-wide libraries that are not supported by WASM, which is a huge pain
	#   that leads to subtle bugs.  One example of this is "uuid". (TODO: bug report: should be disabled when cross compiling?)
	# - DPy_DEFAULT_RECURSION_LIMIT=700:
	#       - with -Oz, a limit of 1000 causes test_userdict to fail when testing on
	#         nodejs linux (it works on mac).  test_userlist is even worse.
	#       - with -oZ, test_richcmp.py fails with a limit above 750; a limit of max of 720 works on node on linux.
	#         Changing to -O3 makes no difference.
	# - with-ensurepip: we put that since we need it, but the makefile just runs ensurepip on python-native,
	#   so it doesn't help.  That's fine for our application, since we explicitly run ensurepip later.
	# - explicitly specifying BLDSHARED is needed since otherwise linking shared modules doesn't work.
	#   ALSO, note that wasm-strip for fPIC code must be done at link time and can't be done later, due to
	#   it simply not being implemented yet.  Stripping is automatic with cowasm-cc unless you pass -g.
	cd ${BUILD_WASM} && \
		OPT="${WASM_OPT}" \
		CC="cowasm-cc" \
		CXX="cowasm-c++" \
		AR="zig ar" \
		CFLAGS="${DEBUG_MODE} -I${BUILD_WASM} -I${POSIX_WASM} -DPy_DEFAULT_RECURSION_LIMIT=700" \
		BLDSHARED="${BLDSHARED}" \
		CONFIG_SITE=./config.site \
		READELF=true \
		HOSTRUNNER="${BIN}/cowasm" \
		./configure \
			--config-cache \
			--prefix=${DIST_WASM}  \
			--enable-big-digits=30 \
			--enable-optimizations \
			--enable-ipv6 \
			--disable-shared \
			--with-pkg-config=no \
			--with-readline=editline \
			--with-build-python=${BIN}/python-native \
			--with-pymalloc \
			--with-ensurepip \
			--host=wasm32-unknown-wasi \
			--build=`./config.guess`
	cat src/pyconfig.h >> ${BUILD_WASM}/pyconfig.h
	# NOTE: I have seen "error: unable to build WASI libc CRT file: FileNotFound" when using "make -j8",
	# and had it go away when removing parallel build.
	cd ${BUILD_WASM} && \
		LZMA_WASM="${LZMA_WASM}" LIBEDIT_WASM="${LIBEDIT_WASM}" NCURSES_WASM="${NCURSES_WASM}" TERMCAP_WASM="${TERMCAP_WASM}" OPENSSL_WASM="${OPENSSL_WASM}" SQLITE_WASM="${SQLITE_WASM}" BZIP2_WASM="${BZIP2_WASM}" LIBFFI_WASM="${LIBFFI_WASM}" ZLIB_WASM="${ZLIB_WASM}" make -j8
	mkdir -p ${DIST_WASM} && touch ${DIST_WASM}/.built-cpython

${DIST_WASM}/.install-cpython: node_modules ${DIST_WASM}/.built-cpython
	# CRITICAL!  We have to make changes to build/lib.wasi-wasm32-3.11/_sysconfigdata__wasi_wasm32-wasi.py so it
	# matches the configuration we just used.  TODO: Find a better way to fix this, e.g., by configuration or
	# patching something earlier?   This definitely feels like a hack.
	# This is what based on testing actually works:
	#      'LDSHARED': '' --> 'LDSHARED': '${BLDSHARED}'
	# This is idempotent.
	cd ${BUILD_WASM}/build/lib.wasi-wasm32-3.11/ \
		&& sed -i'.bak' "s/'LDSHARED': ''/'LDSHARED': '${BLDSHARED}'/g" _sysconfigdata__wasi_wasm32-wasi.py

	# Install as usual.  Output is huge so we do not show it all.
	cd ${BUILD_WASM} && \
		LZMA_WASM="${LZMA_WASM}" LIBEDIT_WASM="${LIBEDIT_WASM}" NCURSES_WASM="${NCURSES_WASM}" TERMCAP_WASM="${TERMCAP_WASM}" OPENSSL_WASM="${OPENSSL_WASM}"  SQLITE_WASM="${SQLITE_WASM}" BZIP2_WASM="${BZIP2_WASM}" LIBFFI_WASM="${LIBFFI_WASM}" ZLIB_WASM="${ZLIB_WASM}" make install | tail -n 10

	# For some reason (upstream bug?) test-tomllib isn't copied over, but it's nice to have for testing.
	cp -rv ${BUILD_WASM}/Lib/test/test_tomllib ${DIST_WASM}/lib/python3.11/test/
	# Used for extension modules to efficiently access what Python exports.
	cp ${BUILD_WASM}/Programs/libpython.c ${DIST_WASM}/lib
	# Done!
	touch ${DIST_WASM}/.install-cpython

.PHONY:
wasm-cpython: ${DIST_WASM}/.install-cpython

# These are the minimal data files needed to start cPython, which we found via "DEBUG=wasi:open pw-d"!
MINIMAL_FILES = encodings/__init__.pyc encodings/aliases.pyc encodings/utf_8.pyc
MINIMAL_READLINE = termcap lib-dynload/readline.cpython-311-wasm32-wasi.so  rlcompleter.pyc inspect.pyc ast.pyc contextlib.pyc collections/__init__.pyc keyword.pyc operator.pyc reprlib.pyc functools.pyc types.pyc enum.pyc dis.pyc opcode.pyc collections/abc.pyc importlib/__init__.pyc warnings.pyc linecache.pyc tokenize.pyc re/__init__.pyc re/_compiler.pyc re/_parser.pyc re/_constants.pyc re/_casefix.pyc copyreg.pyc token.pyc

# Below we create two zip files:
#
#  ${DIST_WASM}/lib/dist/python-minimal.zip - (size=10K) -- a tiny file with the minimum needed to start python
#  ${DIST_WASM}/lib/dist/python-stdlib.zip - (size= ~ 5MB) -- the full standard libary
#
# We WANT to run wasm_assets.py under web assembly rather than use the native cpython!
# This is just because we can and because it's a *good test*.  It does not work yet.
# To try this, replace
#    && python-native ./Tools/wasm/wasm_assets.py
# below by
#    && python-wasm ./Tools/wasm/wasm_assets.py

${DIST_WASM}/.built:  node_modules ${DIST_WASM}/.install-cpython ${BIN}/python-native
	# Build wasm asset bundle (the pyc files, etc.).
	cd ${BUILD_WASM} \
		&& mkdir -p usr/local/lib/python3.11/lib-dynload/  \
		&& ${BIN}/python-native ./Tools/wasm/wasm_assets.py \
		&& rm -rf ${DIST_WASM}/lib/dist \
		&& mv usr/local/lib ${DIST_WASM}/lib/dist
	# Add termcap for xterm to our zip asset bundle, so readline actually works:
	cp ${SRC}/termcap ${DIST_WASM}/lib/dist/termcap && cd ${DIST_WASM}/lib/dist/ && zip -u python311.zip termcap
	# **TODO:** It is very silly for these to be in the same zip file, obviously, because that means they have to be downloaded,
	# so we should just put them in the main binary.  We'll change that when things are working.
	mkdir -p ${DIST_WASM}/lib/dist/lib-dynload/
	cp -v ${DIST_WASM}/lib/python3.11/lib-dynload/*.so ${DIST_WASM}/lib/dist/lib-dynload/

	# This would run wasm-opt on the so extension modules, but it is not
	# necessarily "worth it", since it only reduces the size by less than 3%.
	#${BIN}/cowasm-opt ${DIST_WASM}/lib/dist/lib-dynload/

	# Unfortunately, wasm-strip removes the critical custom section that makes the so file useful, since NotImplementedError
	#      https://reviews.llvm.org/D73820
	#find ${DIST_WASM}/lib/dist/lib-dynload -type f -name "*.so" | xargs -n1 ${CWD}/node_modules/.bin/wasm-strip
	# Instead, we use the -s option to wasm-ld above.
	# Also mv zip to a more meaningful name.
	cd ${DIST_WASM}/lib/dist/ \
		&& zip -u python311.zip lib-dynload/* \
		&& mv python311.zip python-stdlib.zip

	# Next make the MINIMAL python0.zip, which is all that's needed to start python (e.g., for a
	# jupyter kernel or to back
	# computations, but not a REPL). We also make a version with the readline so, so you
	# can start a terminal, before more data needs to be loaded.
	cd ${DIST_WASM}/lib/dist/ \
		&& mkdir tmp && cd tmp && unzip ../python-stdlib.zip && cp ../termcap .\
		&& zip ../python-minimal.zip ${MINIMAL_FILES} \
		&& zip ../python-readline.zip ${MINIMAL_FILES} ${MINIMAL_READLINE} \
		&& cd .. && rm -rf tmp

	# Add some code at the end of the _sysconfigdata so that it is sufficiently relocatable so that
	# it's possible to use setuptools and build C extensions.  I don't understand why python doesn't
	# already do this -- maybe there is some subtle edge case that makes it impossible to do
	# in general?  Or maybe people don't relocate python installs then build.  I don't know.
	cat ${SRC}/patches/17-fix-sysconfigdata.py >> ${DIST_WASM}/lib/python3.11/_sysconfigdata__wasi_wasm32-wasi.py

	# We also copy the posix-wasm.h that was used for the build, so that building C extensions isn't
	# complicated by trying to find this.
	cp -v ${POSIX_WASM}/*.h ${DIST_WASM}/include/python3.11/

	touch ${DIST_WASM}/.built


###########
# Installing pip and setuptools
# We can only do this after ../packages/python-wasm is built, which depends on dylink
# (which depends on cpython) and many other things.  So this target is invoked later.
# NOTE: If you do "make clean-wasm", then "make", be sure to also do "make pip".
###########
${DIST_WASM}/lib/python3.11/site-packages/setuptools/.built: ${DIST_WASM}/.built
	rm -rf ${DIST_WASM}/lib/python*/site-packages/*pip* ${DIST_WASM}/lib/python*/site-packages/*setuptools*
	${CWD}/bin/python-wasm -m ensurepip
	# We apply one patch to support building C++ extensions.  Without this, the numpy
	# build fails pretty quickly. This is needed due to our hacky approach to -fPIC.
	cd ${DIST_WASM} && cat ${SRC}/patches/10-setuptools-c++.patch | patch -p1
	# It would be much nicer to implement a better mmap.  But for now, let's just patch it out:
	cd ${DIST_WASM} && cat ${SRC}/patches/15-pip-no-mmap.patch | patch -p1
	# It would be better to have a better fallback for threads that works in some cases so we
	# don't need this. But for now, just remove it:
	cd ${DIST_WASM} && cat ${SRC}/patches/16-pip-no-auto-refresh-progress.patch  | patch -p1
	touch ${DIST_WASM}/lib/python3.11/site-packages/setuptools/.built

pip: ${DIST_WASM}/lib/python3.11/site-packages/setuptools/.built
.PHONY: pip


# Run the full official Python test suite on the wasm build.  We can't just do
# "make test" in build/wasm, since that only support wasmtime and emscripten.
# Also, we can't use our WASM python to orchestrate this (yet), since the
# test suite runner uses threads (and anyways, we like the speedup from multithreading).
# However, you set line 543 to
#         self.ns.use_mp = 0
# of dist/wasm/lib/python3.11/test/libregrtest/main.py then it works, but of course
# with a single process, which is very slow. So we stick with python-native for testing.
RUN_TESTS = _PYTHON_PROJECT_BASE=${BUILD_WASM} \
	_PYTHON_HOST_PLATFORM=wasi-wasm32 \
	PYTHONPATH=${DIST_WASM}/lib/python3.11 \
	_PYTHON_SYSCONFIGDATA_NAME=_sysconfigdata__wasi_wasm32-wasi \
	${DIST_NATIVE}/bin/python3 \
	./Tools/scripts/run_tests.py \
	 --python=${PYTHON_WASM}

# We use a restricted PATH for running tests.  On Linux I had a lot of trouble
# with some random binaries in random places causing hangs, and this provides
# a cleaner environment.
PNPM = $(shell which pnpm)
NODE = $(shell which node)
TEST_PATH=${BIN}:/bin:/usr/bin:$(shell dirname ${PNPM}):$(shell dirname ${NODE})

test-all: wasm native
	cd ${BUILD_WASM} && PATH=${TEST_PATH} ${RUN_TESTS}
.PHONY: test-all


# I got this list using this on the output:
#     grep passed out | awk '{print $(NF - 1)}' | tr '\n' ' '
SUPPORTED_TEST_DESC = "the **supported-by-python-wasm** cpython test suites"
SUPPORTED_TESTS = test_atexit test_base64 test_calendar test_cgitb  test_compile test_eof test_fstring test_graphlib test_gzip test_hash test_import test_inspect  test_module test_ntpath test_popen test_posixpath test_py_compile test_pydoc  test_quopri  test_script_helper test_source_encoding test_struct test_tabnanny test_tarfile test_trace test_unicodedata test_weakref test___all__ test___future__ test__locale test__opcode test__osx_support test_abc test_abstract_numbers test_aifc test_argparse test_array test_ast test_audioop test_augassign test_baseexception test_bdb test_bigaddrspace test_bigmem test_binascii test_binop test_bisect test_bool test_buffer test_bufio test_bz2 test_call test_cgi test_charmapcodec test_class test_cmath test_cmd test_code_module test_codeccallbacks test_codecencodings_cn test_codecencodings_hk test_codecencodings_iso2022 test_codecencodings_jp test_codecencodings_kr test_codecencodings_tw test_codecmaps_cn test_codecmaps_hk test_codecmaps_jp test_codecmaps_kr test_codecmaps_tw test_codeop test_collections test_colorsys test_compare test_complex test_configparser test_contains test_context test_contextlib test_copy test_copyreg test_crashers test_crypt test_csv test_dataclasses test_datetime test_dbm test_dbm_dumb test_decimal test_decorators test_defaultdict test_deque test_descr test_descrtut test_dict test_dict_version test_dictcomps test_dictviews test_difflib test_dis test_doctest2 test_dynamic test_dynamicclassattribute test_eintr test_email test_ensurepip test_enum test_enumerate test_errno test_except_star test_exception_group test_exception_hierarchy test_exception_variations test_extcall test_file test_filecmp test_fileinput test_fileutils test_finalization test_float test_flufl test_fnmatch test_format test_fractions test_frame test_frozen test_funcattrs test_functools test_future test_future3 test_future4 test_future5 test_generator_stop test_genericalias test_genericclass test_genericpath test_genexps test_getopt test_getpath test_gettext test_global test_grammar test_hashlib test_heapq test_hmac test_html test_htmlparser test_http_cookiejar test_http_cookies test_imghdr test_index test_int test_int_literal test_ipaddress test_isinstance test_iter test_iterlen test_itertools test_keyword test_keywordonlyarg  test_lib2to3 test_linecache test_list test_listcomps test_lltrace test_locale test_long test_longexp test_lzma test_mailcap test_math test_memoryio test_memoryview test_metaclass test_mimetypes test_minidom test_modulefinder test_multibytecodec test_named_expressions test_netrc  test_numeric_tower test_opcache test_opcodes test_operator test_optparse test_ordered_dict test_osx_env test_patma test_peepholer test_peg_generator test_pep646_syntax test_pickle test_picklebuffer test_pickletools test_pipes test_pkg test_pkgutil test_plistlib test_positional_only_arg test_pow test_pprint test_print test_property test_pulldom test_pyclbr test_pyexpat test_raise test_random test_range test_re test_reprlib test_richcmp test_rlcompleter test_sax test_sched test_scope test_secrets test_set test_setcomps test_shelve test_shlex test_slice test_sndhdr test_sort  test_statistics test_strftime test_string test_string_literals test_stringprep test_strptime test_strtod test_structseq test_subclassinit test_sunau test_sundry test_super test_symtable test_syntax test_textwrap test_threadsignals test_timeit  test_tokenize test_tomllib test_tuple test_type_annotations test_type_cache test_type_comments test_typechecks test_types test_ucn test_unary test_unicode_file test_unicode_file_functions test_unicode_identifiers test_univnewlines test_unpack test_unpack_ex test_unparse test_urlparse test_userdict test_userlist test_userstring test_utf8source test_uu test_uuid test_wave test_weakset test_with test_xdrlib test_xml_dom_minicompat test_xml_etree_c test_xxtestfuzz test_yield_from test_zipimport test_zlib test_zoneinfo  test_pstats test_tempfile test_sys_setprofile


${BIN}/python-wasm: ${DIST_WASM}/.built ${CWD}/bin/python-wasm
	ln -sf ${CWD}/bin/python-wasm ${BIN}/python-wasm

.PHONY: python-wasm
python-wasm: ${BIN}/python-wasm

test-pip: pip
	echo "Testing PIP"
	${CWD}/bin/python-wasm -m pip |grep Usage
	echo "Success"
.PHONY: test-pip


# Some tests can be pretty slow, e.g., test_tarfile takes about a minute or more on a FAST computer,
# and doing 4 in parallel on a slow computer.
test: wasm native ${BIN}/python-wasm test-pip
	echo "Running ${SUPPORTED_TEST_DESC}"
	cd ${BUILD_WASM} && PATH=${TEST_PATH} ${RUN_TESTS}  -j4 --timeout=600 ${SUPPORTED_TESTS}
	echo "Ran ${SUPPORTED_TEST_DESC}"
.PHONY: test

PASSED_WITHOUT_SUBPROCESS_DESC = "These tests all passed without subprocess or socket support enabled, but fail with it."
PASSED_WITHOUT_SUBPROCESS = test_timeout test_stat test_robotparser test_nntplib test_largefile test_logging test_audit test_bytes test_c_locale_coercion test_cmd_line_script test_compileall test_coroutines test_exceptions test_gc test_json test_marshal test_runpy test_site  test_traceback test_unicode test_utf8_mode test_warnings

test-passed-without-subprocess: wasm native
	echo "Running ${PASSED_WITHOUT_SUBPROCESS_DESC}"
	cd ${BUILD_WASM} && PATH=${TEST_PATH} ${RUN_TESTS}  --timeout=180 -j4 ${PASSED_WITHOUT_SUBPROCESS}
	echo "Ran ${PASSED_WITHOUT_SUBPROCESS_DESC}"


# One test fails here due to the fact that the PYTHONHOME is different in webworker mode (since the files are in the zip archive).
test-worker: wasm native
	echo "Running using webworker -- ${SUPPORTED_TEST_DESC}"
	cd ${BUILD_WASM} && PATH=${TEST_PATH} PYTHONHOME=${DIST_WASM} ${RUN_TESTS}  -j4 --timeout=180 ${SUPPORTED_TESTS}
	echo "Ran using webworker -- ${SUPPORTED_TEST_DESC}"

# NOTES:
#  test_pydoc - sometimes randomly hangs, mainly on linux.
# test_sys and test_platform -- I think crashes caused by installing too much and running native subprocesses; eventually we won't run anything native... (these work on most platform but I have an ubuntu docker container where they fail)
#
# test_code test_cppext  fail because of libffi not being done.
#
FAILED_TEST_DESC = "All **unsuppported** and known failing cpython test suites: this is less than 10% of the non-skipped cpython test suite.  We want to fix all of these."
FAILED_TESTS = test_code test_cppext  test_readline test_builtin  test_sys test_platform  test_getpass test_codecs   test_distutils test_fileio test_generators test_glob test_imp test_importlib  test_io    test_openpty test_os test_pathlib   test_posix          test_shutil test_signal  test_sqlite3  test_support  test_sysconfig    test_time  test_tracemalloc test_typing   test_unittest  test_xml_etree test_zipapp test_zipfile


test-failed:  wasm native
	echo "Running ${FAILED_TEST_DESC}"
	cd ${BUILD_WASM} && PATH=${TEST_PATH} ${RUN_TESTS} --timeout=180 -j4 ${FAILED_TESTS}
	echo "Ran ${FAILED_TEST_DESC}"


TEST=test_long
test-one:   wasm native
	echo "Use 'make TEST=test_long test-one' to run a specific test, e.g., test_long in this case."
	cd ${BUILD_WASM} && PATH=${TEST_PATH} ${RUN_TESTS} --timeout=60 -v ${TEST}
.PHONY: test-one

test-one-worker: wasm native
	echo "Use 'make TEST=test_long test-one' to run a specific test, e.g., test_long in this case."
	cd ${BUILD_WASM} && PATH=${TEST_PATH} ${RUN_TESTS} -j1 --timeout=60 -v ${TEST}
.PHONY: test-one


SKIPPED_TEST_DESC = "all **skipped** cpython test suites.  Extend what cpython wasm can do to include more of these."
SKIPPED_TESTS = test_asdl_parser test__xxsubinterpreters test_asyncgen test_asynchat test_asyncio test_asyncore test_check_c_globals test_clinic test_cmd_line test_concurrent_futures test_contextlib_async test_ctypes test_curses test_dbm_gnu test_dbm_ndbm test_devpoll test_doctest test_docxmlrpc test_dtrace test_embed test_epoll test_faulthandler test_fcntl test_file_eintr test_fork1 test_ftplib test_gdb test_grp test_httplib test_httpservers test_idle test_imaplib test_interpreters test_ioctl test_kqueue test_launcher test_mailbox test_mmap test_msilib test_multiprocessing_fork test_multiprocessing_forkserver test_multiprocessing_main_handling test_multiprocessing_spawn test_nis test_ossaudiodev test_pdb test_poll test_poplib test_pty test_pwd test_queue test_regrtest test_repl test_resource test_select test_selectors test_smtpd test_smtplib test_smtpnet test_socket test_socketserver test_spwd test_ssl test_stable_abi_ctypes test_startfile test_subprocess test_sys_settrace test_syslog test_tcl test_telnetlib test_thread test_threadedtempfile test_threading test_threading_local test_tix test_tk test_tools test_ttk_guionly test_ttk_textonly test_turtle test_urllib test_urllib2 test_urllib2_localnet test_urllib2net test_urllib_response test_urllibnet test_venv test_wait3 test_wait4 test_webbrowser test_winconsoleio test_winreg test_winsound test_wsgiref test_xmlrpc test_xmlrpc_net test_xxlimited test_zipfile64 test_zipimport_support  test_cprofile test_profile

test-skipped:  wasm native
	echo "Running ${SKIPPED_TEST_DESC}"
	cd ${BUILD_WASM} && PATH=${TEST_PATH} ${RUN_TESTS}  --timeout=60 ${SKIPPED_TESTS}
	echo "Ran ${SKIPPED_TEST_DESC}"


# This is sort of like the 'assets' thing that is part of cpython, but it's
# much more useful (but less small).
${DIST_WASM}/.publishable: wasm
	# Do not need the tests in the published version -- they're huge
	rm -rf ${DIST_WASM}/lib/python3.11/test

	# Graphics -- Remove idle which we are nowhere near supporting (and is big):
	rm -rf ${DIST_WASM}/lib/python3.11/idlelib
	rm -rf ${DIST_WASM}/lib/python3.11/*tkinter
	rm -rf ${DIST_WASM}/lib/python3.11/turtle*

	# Temporarily make it into a package so that we can bundle it
	touch ${DIST_WASM}/lib/python3.11/__init__.py
	# Bundle it -- using python-native since it's more compressed:
	cd ${DIST_WASM}/lib/ && ${CWD}/bin/python-wasm -m cowasm_bundler python3.11
	# Move it out of the way
	mv ${DIST_WASM}/lib/python3.11 ${DIST_WASM}/lib/python3.11.orig
	# Extract bundle
	cd ${DIST_WASM}/lib/ && tar xf python3.11.tar.xz
	# Remove __init__.pyc
	rm -f ${DIST_WASM}/lib/python3.11/__init__.pyc
	# Also site-packages
	mkdir ${DIST_WASM}/lib/python3.11/site-packages
	# Move compiled so files over
	mv ${DIST_WASM}/lib/python3.11.orig/lib-dynload ${DIST_WASM}/lib/python3.11/
	# Compress libpython3.11 to save 20MB decompressed.  Note that this obviously
	# complicates the python-wasm package which has to explicitly copy this out and decompress it.
	xz ${DIST_WASM}/lib/libpython3.11.a
	# Remove stuff
	rm -rf ${DIST_WASM}/lib/python3.11.tar.xz ${DIST_WASM}/lib/python3.11.orig
	touch ${DIST_WASM}/.publishable

.PHONY: publishable
publishable: ${DIST_WASM}/.publishable