diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f642301..209b8a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,17 +6,17 @@ jobs: build: runs-on: ubuntu-22.04 env: - SDK_VERSION: 3.1.47.2bi + SDK_VERSION: 3.1.48.0bi SYS_PYTHON: /usr/bin/python3 PACKAGES: emsdk hpy _ctypes pygame BUILD_STATIC: emsdk _ctypes hpy STATIC: false BUILDS: 3.11 3.12 - CYTHON: Cython-3.0.1-py2.py3-none-any.whl + CYTHON: Cython-3.0.4-py2.py3-none-any.whl LD_VENDOR: -sUSE_GLFW=3 steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.1.0 - name: pygame-wasm-builder prepare run: | pwd diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..bae2924 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include pygbag/support * diff --git a/pygbag/__main__.py b/pygbag/__main__.py index c516613..b8a6137 100644 --- a/pygbag/__main__.py +++ b/pygbag/__main__.py @@ -35,6 +35,8 @@ async def import_site(sourcefile=None, simulator=False, async_input=None, async_ module directory : {mod_dir} platform support : {support} {sys.argv=} + {sourcefile=} + """ ) @@ -87,14 +89,18 @@ def __del__(self): pass def __repr__(self): - return "\nNoOp:%s:" % self.__descr + return "\nNoOp: %s" % self.__descr __str__ = __repr__ # fake host document.window import platform as fakehost + def truc(*argv, **kw): + print("truc", argv, kw) + fakehost.window = NoOp("platform.window") + fakehost.window.get_terminal_console = truc import aio.filelike @@ -167,9 +173,12 @@ def no_op(cls, *argv, **kw): sys.modules["__EMSCRIPTEN__"] = __EMSCRIPTEN__ sys.modules["embed"] = __EMSCRIPTEN__ + print(" =============== pythonrc =================") with open(support / "pythonrc.py", "r") as file: exec(file.read(), globals(), globals()) + print(" =============== /pythonrc =================") + import zipfile import aio.toplevel import ast @@ -219,6 +228,14 @@ async def raw_input(self, prompt=">>> "): return maybe return None + async def async_get_pkg(cls, want, ex, resume): + print( + """ +224:TODO: PEP723 + async def async_get_pkg(cls, want, ex, resume) +""" + ) + # start async top level machinery and add a console. await TopLevel_async_handler.start_toplevel(platform.shell, console=True) ns = vars(__import__(__name__)) diff --git a/pygbag/aio.py b/pygbag/aio.py index a889538..60c56d3 100644 --- a/pygbag/aio.py +++ b/pygbag/aio.py @@ -1,12 +1,13 @@ import sys - +import os import aio # to allow "import pygbag.aio as asyncio" sys.modules["pygbag.aio"] = aio # if not wasm cpu then run caller in simulator and block. -if not __import__("os").uname().machine.startswith("wasm"): + +if hasattr(os, "uname") and not os.uname().machine.startswith("wasm"): import time from pathlib import Path @@ -27,10 +28,10 @@ async def custom_async_input(): aio.loop.create_task( pygbag.__main__.import_site( - sourcefile=sys.argv[-1], + sourcefile=sys.argv[0], simulator=True, async_input=custom_async_input, - async_pkg=aio.pep0723.check_list(filename=sys.argv[-1]), + async_pkg=aio.pep0723.check_list(filename=sys.argv[0]), ) ) diff --git a/pygbag/support/cross/aio/pep0723.py b/pygbag/support/cross/aio/pep0723.py index 34e680e..0e65ea4 100644 --- a/pygbag/support/cross/aio/pep0723.py +++ b/pygbag/support/cross/aio/pep0723.py @@ -19,6 +19,14 @@ from aio.filelike import fopen +import platform + +print(platform) + +import platform_wasm.todo + +PATCHLIST = [] + class Config: READ_722 = False @@ -181,7 +189,8 @@ async def install_pkg(sysconf, wheel_url, wheel_pkg): async def pip_install(pkg, sconf={}): - print("searching", pkg) + print("185: searching TODO: install from repo in found key", pkg) + if not sconf: sconf = __import__("sysconfig").get_paths() @@ -196,21 +205,28 @@ async def pip_install(pkg, sconf={}): else: print("270: ERROR: cannot find package :", pkg) except FileNotFoundError: - print("190: ERROR: cannot find package :", pkg) + print("200: ERROR: cannot find package :", pkg) return except: - print("194: ERROR: cannot find package :", pkg) + print("204: ERROR: cannot find package :", pkg) return - try: - wheel_pkg, wheel_hash = wheel_url.rsplit("/", 1)[-1].split("#", 1) - await install_pkg(sconf, wheel_url, wheel_pkg) - except: - print("INVALID", pkg, "from", wheel_url) + if wheel_url: + try: + wheel_pkg, wheel_hash = wheel_url.rsplit("/", 1)[-1].split("#", 1) + await install_pkg(sconf, wheel_url, wheel_pkg) + except: + print("212: INVALID", pkg, "from", wheel_url) async def parse_code(code, env): + global PATCHLIST + + # pythonrc is calling aio.pep0723.parse_code not check_list + # so do patching here + platform_wasm.todo.patch() + maybe_missing = [] if Config.READ_722: @@ -235,7 +251,12 @@ async def parse_code(code, env): still_missing = [] + import platform + for dep in maybe_missing: + if dep in platform.patches: + PATCHLIST.append(dep) + if not importlib.util.find_spec(dep) and dep not in still_missing: still_missing.append(dep.lower()) else: @@ -244,7 +265,10 @@ async def parse_code(code, env): return still_missing +# parse_code does the patching +# this is not called by pythonrc async def check_list(code=None, filename=None): + global PATCHLIST print() print("-" * 11, "computing required packages", "-" * 10) @@ -267,42 +291,48 @@ async def check_list(code=None, filename=None): still_missing = await parse_code(code, env) - # nothing to do - if not len(still_missing): - return + # is there something to do ? + if len(still_missing): + importlib.invalidate_caches() - importlib.invalidate_caches() + # only do that once and for all. + if not len(Config.repos): + await async_imports_init() + await async_repos() - # only do that once and for all. - if not len(Config.repos): - await async_imports_init() - await async_repos() + # TODO: check for possible upgrade of env/* pkg - # TODO: check for possible upgrade of env/* pkg + maybe_missing = still_missing + still_missing = [] - maybe_missing = still_missing - still_missing = [] + for pkg in maybe_missing: + hit = "" + for repo in Config.pkg_repolist: + wheel_pkg = repo.get(pkg, "") + if wheel_pkg: + wheel_url = repo["-CDN-"] + "/" + wheel_pkg + wheel_pkg = wheel_url.rsplit("/", 1)[-1] + await install_pkg(sconf, wheel_url, wheel_pkg) + hit = pkg - for pkg in maybe_missing: - hit = "" - for repo in Config.pkg_repolist: - wheel_pkg = repo.get(pkg, "") - if wheel_pkg: - wheel_url = repo["-CDN-"] + "/" + wheel_pkg - wheel_pkg = wheel_url.rsplit("/", 1)[-1] - await install_pkg(sconf, wheel_url, wheel_pkg) - hit = pkg - - if len(hit): - print("found on pygbag repo and installed to env :", hit) - else: - still_missing.append(pkg) + if len(hit): + print("found on pygbag repo and installed to env :", hit) + else: + still_missing.append(pkg) - for pkg in still_missing: - if (env / pkg).is_dir(): - print("found in env :", pkg) - continue - await pip_install(pkg) + for pkg in still_missing: + if (env / pkg).is_dir(): + print("found in env :", pkg) + continue + await pip_install(pkg) + + import platform + + # apply any patches + while len(PATCHLIST): + dep = PATCHLIST.pop(0) + print(f"314: patching {dep}") + platform.patches.pop(dep)() print("-" * 40) print() diff --git a/pygbag/support/pythonrc.py b/pygbag/support/pythonrc.py index 689a414..96c72b1 100644 --- a/pygbag/support/pythonrc.py +++ b/pygbag/support/pythonrc.py @@ -642,9 +642,9 @@ async def preload_code(cls, code, callback=None, hint=""): DBG(f"629: aio.pep0723.pip_install {deps=}") for dep in deps: await aio.pep0723.pip_install(dep) - - else: # sim use a local folder venv model - await aio.pep0723.check_list(code=code, filename=None) + # else: + # sim use a local folder venv model + # await aio.pep0723.check_list(code=code, filename=None) is done in pygbag.aio await TopLevel_async_handler.async_imports(callback, *maybe_wanted) # await TopLevel_async_handler.async_imports(callback, *TopLevel_async_handler.list_imports(code, file=None)) @@ -1457,289 +1457,9 @@ async def async_repos(cls): shell.screen_width = 1024 shell.screen_height = 600 - # ====================================================== -def patch(): - global COLS, LINES, CONSOLE - import platform - - if not __UPY__: - # DeprecationWarning: Using or importing the ABCs from 'collections' - # instead of from 'collections.abc' is deprecated since Python 3.3 - # and in 3.10 it will stop working - import collections - from collections.abc import MutableMapping - - collections.MutableMapping = MutableMapping - - # could use that ? - # import _sqlite3 - # sys.modules['sqlite3'] = _sqlite3 - - # - import os - - COLS = platform.window.get_terminal_cols() - CONSOLE = platform.window.get_terminal_console() - LINES = platform.window.get_terminal_lines() - CONSOLE - - os.environ["COLS"] = str(COLS) - os.environ["LINES"] = str(LINES) - - def patch_os_get_terminal_size(fd=0): - cols = os.environ.get("COLS", 80) - lines = os.environ.get("LINES", 25) - try: - res = ( - int(cols), - int(lines), - ) - except: - res( - 80, - 25, - ) - return os.terminal_size(res) - - os.get_terminal_size = patch_os_get_terminal_size - - # fake termios module for some wheel imports - termios = type(sys)("termios") - termios.block2 = [ - b"\x03", - b"\x1c", - b"\x7f", - b"\x15", - b"\x04", - b"\x00", - b"\x01", - b"\x00", - b"\x11", - b"\x13", - b"\x1a", - b"\x00", - b"\x12", - b"\x0f", - b"\x17", - b"\x16", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - b"\x00", - ] - - def patch_termios_getattr(*argv): - return [17664, 5, 191, 35387, 15, 15, termios.block2] - - def patch_termios_set_raw_mode(): - # assume first set is raw mode - embed.warn(f"Term phy COLS : {int(platform.window.get_terminal_cols())}") - embed.warn(f"Term phy LINES : {int(platform.window.get_terminal_lines())}") - embed.warn(f"Term logical : {patch_os_get_terminal_size()}") - # set console scrolling zone - embed.warn(f"Scroll zone start at {LINES=}") - CSI(f"{LINES+1};{LINES+CONSOLE}r", f"{LINES+2};1H>>> ") - platform.window.set_raw_mode(1) - - def patch_termios_setattr(*argv): - if not termios.state: - patch_termios_set_raw_mode() - else: - embed.warn("RESETTING TERMINAL") - - termios.state += 1 - pass - - termios.set_raw_mode = patch_termios_set_raw_mode - termios.state = 0 - termios.tcgetattr = patch_termios_getattr - termios.tcsetattr = patch_termios_setattr - termios.TCSANOW = 0x5402 - termios.TCSAFLUSH = 0x5410 - termios.ECHO = 8 - termios.ICANON = 2 - termios.IEXTEN = 32768 - termios.ISIG = 1 - termios.IXON = 1024 - termios.IXOFF = 4096 - termios.ICRNL = 256 - termios.INLCR = 64 - termios.IGNCR = 128 - termios.VMIN = 6 - - sys.modules["termios"] = termios - - # pyodide emulation - # TODO: implement loadPackage()/pyimport() - def runPython(code): - from textwrap import dedent - - print("1285: runPython N/I") - - platform.runPython = runPython - - # fake Decimal module for some wheel imports - sys.modules["decimal"] = type(sys)("decimal") - - class Decimal: - pass - - sys.modules["decimal"].Decimal = Decimal - - # patch builtins input() - async def async_input(prompt=""): - shell.is_interactive = False - if prompt: - print(prompt, end="") - maybe = "" - while not len(maybe): - maybe = embed.readline() - await asyncio.sleep(0) - - shell.is_interactive = True - return maybe.rstrip("\n") - - import builtins - - builtins.input = async_input - - # - def patch_matplotlib_pyplot(): - import matplotlib - import matplotlib.pyplot - - def patch_matplotlib_pyplot_show(*args, **kwargs): - import pygame - import matplotlib.pyplot - import matplotlib.backends.backend_agg - - figure = matplotlib.pyplot.gcf() - canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(figure) - canvas.draw() - renderer = canvas.get_renderer() - raw_data = renderer.tostring_rgb() - size = canvas.get_width_height() - - screen = shell.pg_init() - surf = pygame.image.fromstring(raw_data, size, "RGB") - screen.blit(surf, (0, 0)) - pygame.display.update() - - matplotlib.pyplot.show = patch_matplotlib_pyplot_show - - matplotlib.pyplot.__pause__ = matplotlib.pyplot.pause - - def patch_matplotlib_pyplot_pause(interval): - matplotlib.pyplot.__pause__(0.0001) - patch_matplotlib_pyplot_show() - return asyncio.sleep(interval) - - matplotlib.pyplot.pause = patch_matplotlib_pyplot_pause - - # - def patch_panda3d_showbase(): - import panda3d - import panda3d.core - from direct.showbase.ShowBase import ShowBase - - print(f"panda3d: apply model path {os.getcwd()} patch") - panda3d.core.get_model_path().append_directory(os.getcwd()) - panda3d.core.loadPrcFileData("", "win-size 1024 600") - panda3d.core.loadPrcFileData("", "support-threads #f") - panda3d.core.loadPrcFileData("", "textures-power-2 down") - panda3d.core.loadPrcFileData("", "textures-square down") - # samples expect that - panda3d.core.loadPrcFileData("", "default-model-extension .egg") - - def run(*argv, **env): - print("ShowBase.run patched to launch asyncio.run(main())") - import direct.task.TaskManagerGlobal - - async def main(): - try: - print("1633: auto resizing") - platform.window.window_resize() - except: - ... - while not asyncio.get_running_loop().is_closed(): - try: - direct.task.TaskManagerGlobal.taskMgr.step() - except SystemExit: - print("87: Panda3D stopped", file=sys.stderr) - break - # go to host - await asyncio.sleep(0) - - asyncio.run(main()) - - print("panda3d: apply ShowBase.run patch") - ShowBase.run = run - - def patch_cwcwidth(): - import cwcwidth - - sys.modules["wcwidth"] = cwcwidth - - def patch_pygame(): - import pygame - import platform_wasm.pygame - import platform_wasm.pygame.vidcap - - sys.modules["pygame.vidcap"] = platform_wasm.pygame.vidcap - - platform.patches = { - "matplotlib": patch_matplotlib_pyplot, - "panda3d": patch_panda3d_showbase, - "wcwidth": patch_cwcwidth, - "pygame.base": patch_pygame, - } - - -patch() -del patch - - -# ====================================================== -# emulate pyodide display() cmd -# TODO: fixme target -async def display(obj, target=None, **kw): - filename = aio.filelike.mktemp(".png") - target = kw.pop("target", None) - x = kw.pop("x", 0) - y = kw.pop("y", 0) - dpi = kw.setdefault("dpi", 72) - if repr(type(obj)).find("matplotlib.figure.Figure") > 0: - # print(f"matplotlib figure {platform.is_browser=}") - if platform.is_browser: - # Agg is not avail, save to svg only option. - obj.canvas.draw() - tmp = f"{filename}.svg" - obj.savefig(tmp, format="svg", **kw) - await platform.jsiter(platform.window.svg.render(tmp, filename)) - else: - # desktop matplotlib can save to any format - obj.canvas.draw() - obj.savefig(filename, format="png", **kw) - - if target in [None, "pygame"]: - import pygame - - screen = shell.pg_init() - screen.fill((0, 0, 0)) - screen.blit(pygame.image.load(filename), (x, y)) - pygame.display.update() +# patching +# import platform_wasm.todo # ====================================================== diff --git a/setup.cfg b/setup.cfg index 392fb0d..bdece1f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,7 +22,9 @@ classifiers = [options] packages = pygbag -zip_safe = True +# package_dir = pygbag +include_package_data = True +zip_safe = False python_requires = >=3.8 install_requires = @@ -33,6 +35,12 @@ install_requires = installer aiohttp +[options.packages.find] +where = pygbag + +[options.package_data] +pygbag = * + [options.entry_points] console_scripts = pygbag = pygbag.app:main