From e36a2083292f115ab5a5db525a6badee1b4323b0 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Fri, 23 Feb 2024 16:11:41 -0500 Subject: [PATCH 001/109] Dust off and update to V8 12.2 --- .dockerignore | 15 - .flake8 | 10 - .github/issue_template.md | 3 + .github/workflows/build.yml | 137 ---- .github/workflows/docs.yml | 24 + .github/workflows/pypi-build.yml | 354 +++++++++ .gitignore | 20 +- .isort.cfg | 14 - .mdformat.toml | 1 + .pre-commit-config.yaml | 22 +- ARCHITECTURE.md | 196 +++++ AUTHORS.md | 15 + AUTHORS.rst | 18 - CONTRIBUTING.md | 184 +++++ CONTRIBUTING.rst | 112 --- HISTORY.md | 142 ++++ HISTORY.rst | 125 ---- MANIFEST.in | 15 - Makefile | 89 --- README.md | 138 ++++ README.rst | 212 ------ data/py_mini_racer.png | Bin 78857 -> 20 bytes docs/ARCHITECTURE.md | 1 + docs/CONTRIBUTING.md | 1 + docs/Makefile | 177 ----- docs/api.md | 3 + docs/api.rst | 5 - docs/architecture.md | 1 + docs/authors.md | 1 + docs/authors.rst | 1 - docs/conf.py | 283 -------- docs/contributing.md | 1 + docs/contributing.rst | 1 - docs/history.md | 1 + docs/history.rst | 1 - docs/index.md | 1 + docs/index.rst | 19 - docs/make.bat | 242 ------ docs/readme.rst | 3 - hatch_build.py | 39 + .../extension => helpers}/__init__.py | 0 helpers/babel.py | 20 +- helpers/build_package.py | 56 -- helpers/no-aarch64-linux-gnu-target.patch | 13 + .../split-threshold-for-reg-with-hint.patch | 11 + helpers/v8_build.py | 686 +++++++++++------- helpers/wheel_pymalloc.py | 56 -- mkdocs.yml | 33 + py_mini_racer.png | Bin 0 -> 78857 bytes py_mini_racer/__about__.py | 3 - py_mini_racer/__init__.py | 5 - py_mini_racer/extension/.editorconfig | 9 - py_mini_racer/extension/.gitignore | 5 - py_mini_racer/extension/.gn | 1 - py_mini_racer/extension/BUILD.gn | 36 - py_mini_racer/py_mini_racer.py | 442 ----------- pyproject.toml | 129 ++++ setup.py | 65 -- src/py_mini_racer/__about__.py | 3 + src/py_mini_racer/__init__.py | 14 + src/py_mini_racer/py_mini_racer.py | 496 +++++++++++++ src/v8_py_frontend/BUILD.gn | 14 + src/v8_py_frontend/README.md | 2 + .../v8_py_frontend/mini_racer.cc | 67 +- tests/__init__.py | 0 tests/test_babel.py | 57 +- tests/test_call.py | 66 +- tests/test_eval.py | 266 ++++--- tests/test_heap.py | 20 +- tests/test_strict.py | 52 +- tests/test_types.py | 297 ++++---- tests/test_wasm.py | 72 +- tox.ini | 7 - 73 files changed, 2707 insertions(+), 2923 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .flake8 delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/pypi-build.yml delete mode 100644 .isort.cfg create mode 100644 .mdformat.toml create mode 100644 ARCHITECTURE.md create mode 100644 AUTHORS.md delete mode 100644 AUTHORS.rst create mode 100644 CONTRIBUTING.md delete mode 100644 CONTRIBUTING.rst create mode 100644 HISTORY.md delete mode 100644 HISTORY.rst delete mode 100644 MANIFEST.in delete mode 100644 Makefile create mode 100644 README.md delete mode 100644 README.rst mode change 100644 => 120000 data/py_mini_racer.png create mode 120000 docs/ARCHITECTURE.md create mode 120000 docs/CONTRIBUTING.md delete mode 100644 docs/Makefile create mode 100644 docs/api.md delete mode 100644 docs/api.rst create mode 120000 docs/architecture.md create mode 120000 docs/authors.md delete mode 100644 docs/authors.rst delete mode 100755 docs/conf.py create mode 120000 docs/contributing.md delete mode 100644 docs/contributing.rst create mode 120000 docs/history.md delete mode 100644 docs/history.rst create mode 120000 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/make.bat delete mode 100644 docs/readme.rst create mode 100644 hatch_build.py rename {py_mini_racer/extension => helpers}/__init__.py (100%) delete mode 100644 helpers/build_package.py create mode 100644 helpers/no-aarch64-linux-gnu-target.patch create mode 100644 helpers/split-threshold-for-reg-with-hint.patch delete mode 100644 helpers/wheel_pymalloc.py create mode 100644 mkdocs.yml create mode 100644 py_mini_racer.png delete mode 100644 py_mini_racer/__about__.py delete mode 100755 py_mini_racer/__init__.py delete mode 100644 py_mini_racer/extension/.editorconfig delete mode 100644 py_mini_racer/extension/.gitignore delete mode 100644 py_mini_racer/extension/.gn delete mode 100644 py_mini_racer/extension/BUILD.gn delete mode 100644 py_mini_racer/py_mini_racer.py create mode 100644 pyproject.toml delete mode 100755 setup.py create mode 100644 src/py_mini_racer/__about__.py create mode 100644 src/py_mini_racer/__init__.py create mode 100644 src/py_mini_racer/py_mini_racer.py create mode 100644 src/v8_py_frontend/BUILD.gn create mode 100644 src/v8_py_frontend/README.md rename py_mini_racer/extension/mini_racer_extension.cc => src/v8_py_frontend/mini_racer.cc (94%) create mode 100644 tests/__init__.py delete mode 100644 tox.ini diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index d71f5dc6..00000000 --- a/.dockerignore +++ /dev/null @@ -1,15 +0,0 @@ -*.pyc -py_mini_racer/extension/v8/ -vendor/depot_tools/ -Dockerfile -Makefile -wheelhouse -dist -build -docker -Dockerfile.build -docker-compose.yml -Makefile -*.whl -**/*.so -.git diff --git a/.flake8 b/.flake8 deleted file mode 100644 index d909fe62..00000000 --- a/.flake8 +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -ignore = - E203, - E501, - W503 -exclude = - .tox, - docs/conf.py, - build, - py_mini_racer/extension diff --git a/.github/issue_template.md b/.github/issue_template.md index 78126cf8..68c8ef8d 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,12 +1,15 @@ ### Steps to reproduce ### Expected behavior + Tell us what should happen ### Actual behavior + Tell us what happens instead ### System configuration + **PyMiniRacer version**: **Python version**: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 9a17357a..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: Build -on: - push: - branches: [master] - pull_request: - branches: [master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - name: ${{ matrix.config.plat_name }} - strategy: - matrix: - config: - - os: ubuntu-16.04 - plat_name: manylinux1_x86_64 - - os: macos-10.15 - plat_name: macosx_10_10_x86_64 - - os: windows-2019 - plat_name: win_amd64 - fail-fast: true - runs-on: ${{ matrix.config.os }} - timeout-minutes: 180 - steps: - - name: Configure git - run: git config --global core.symlinks true - - - name: Clone repository - uses: actions/checkout@v1 - - - name: Install python 3.6 - uses: actions/setup-python@v1 - with: - python-version: 3.6.x - architecture: x64 - - - name: Install python 2.7 - uses: actions/setup-python@v1 - with: - python-version: 2.7.x - architecture: x64 - - - name: Build wheelhouse - run: | - python3 -m pip install setuptools wheel - mkdir wheelhouse - python3 helpers/build_package.py wheel wheelhouse - shell: bash - - - name: Archive wheelhouse - uses: actions/upload-artifact@v2 - with: - name: package-${{ matrix.config.plat_name }} - path: wheelhouse - - - name: Check the wheel - if: matrix.config.plat_name == 'manylinux1_x86_64' - run: | - python3 -m pip install auditwheel twine readme_renderer[md] - auditwheel show wheelhouse/*.whl - twine check wheelhouse/*.whl - - - name: Test - run: | - python3 -m pip install pytest wheelhouse/*.whl - pytest tests - shell: bash - - build-on-alpine: - name: alpine_x86_64 - runs-on: ubuntu-latest - container: - image: nicolassqreen/azure-pipelines-container-alpine-python:3.12 - timeout-minutes: 180 - steps: - - name: Clone repository - uses: actions/checkout@v1 - - - name: Download V8 sources - uses: docker://python:2 - with: - args: python helpers/v8_build.py --no-build --no-sysroot - - - name: Prepare Aline Linux build environment - run: | - sudo apk -U add samurai llvm lld linux-headers binutils-gold - cp -f /usr/local/bin/gn py_mini_racer/extension/v8/buildtools/linux64/gn - rm -f py_mini_racer/extension/depot_tools/ninja - - - name: Build the extension - run: | - python helpers/v8_build.py --no-update --no-sysroot --target py_mini_racer_shared_lib - cp py_mini_racer/extension/out/libmini_racer.so py_mini_racer/libmini_racer.muslc.so - - - name: Build the wheelhouse - run: | - sudo apk add py3-pip py3-wheel - mkdir wheelhouse - python3 setup.py sdist --dist-dir wheelhouse - - - name: Archive wheelhouse - uses: actions/upload-artifact@v2 - with: - name: package-alpine_x86_64 - path: wheelhouse - - - name: Test - run: | - python3 -m pip install pytest wheelhouse/*.tar.gz - pytest tests - - release: - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') - runs-on: ubuntu-latest - needs: [build-on-alpine, build] - steps: - - name: Download packages - uses: actions/download-artifact@v2 - with: - path: tmp - - - name: Move packages to the wheelhouse - run: | - mkdir wheelhouse - find tmp -name '*.whl' -exec mv {} wheelhouse \; - find tmp -name '*.tar.gz' -exec mv {} wheelhouse \; - shell: bash - - - name: Publish 📦 to PyPI - uses: pypa/gh-action-pypi-publish@v1.4.2 - with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} - packages_dir: wheelhouse/ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..3fa0ed18 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,24 @@ +name: docs +on: + push: + tags: + - v* + branches: + - main +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install hatch hatch-mkdocs + - run: hatch -vv run docs:gh-deploy --force diff --git a/.github/workflows/pypi-build.yml b/.github/workflows/pypi-build.yml new file mode 100644 index 00000000..a68adc0e --- /dev/null +++ b/.github/workflows/pypi-build.yml @@ -0,0 +1,354 @@ +name: pypi-build + +on: + push: + branches: + - main + +concurrency: + group: build-${{ github.head_ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + sdist: + name: Build sdist + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + # Fetch all tags + fetch-depth: 0 + + - name: Install deps + run: python -m pip install --upgrade build + + - name: Build + run: python -m build --sdist + + - uses: actions/upload-artifact@v3 + with: + name: sdist + path: dist/* + if-no-files-found: error + + # We build for Linux using uraimo/run-on-arch-action@v2, which runs a container under + # the runner in order to reach different platforms (notably Alpine with its musl) and + # architectures (notably aarch64) via emulation. uraimo/run-on-arch-action@v2 doesn't + # support Mac or Windows, so we run a separate job for those. + linux-wheels: + name: Build wheel for ${{ matrix.config.image }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + config: + # To maximize compatibility of generated wheels, we should theoreticaly build + # on the *oldest* supported distros. + # But V8 ships its own bullseye sysroot and links against that, so we may as + # well run on bullseye (even though buster would provide an older supported + # build distro): + - image: debian:bullseye + - image: arm64v8/debian:bullseye + # And for Alpine we mostly measure compatibility in musl minor versions, and + # the oldest supported Alpine as of this writing (3.16) has the same minor + # version as 3.19, so use 3.19. (This helps bring a newer system clang, which + # works better with the V8 build.) + - image: alpine:3.19 + - image: arm64v8/alpine:3.19 + + steps: + - name: Configure git + run: git config --global core.symlinks true + + - uses: actions/checkout@v3 + with: + # Fetch all tags + fetch-depth: 0 + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + + - uses: uraimo/run-on-arch-action@v2 + name: Build wheel + with: + arch: none + distro: none + base_image: ${{ matrix.config.image }} + + setup: | + mkdir -p "${PWD}/wheels" + + dockerRunArgs: | + --volume "${PWD}/wheels:/wheels" + + shell: /bin/sh + + install: | + case "${{ matrix.config.image }}" in + *debian*) + # Let's download some system packages! + # Note that the precise list of packages we need is intertwined not just + # with V8, but with helpers/build_v8.py, which makes various decisions + # about using system versus V8-provided tools and dependencies. + PACKAGES="" + # First, some basic wheel-building dependencies: + PACKAGES="${PACKAGES} python3" + PACKAGES="${PACKAGES} pip" + PACKAGES="${PACKAGES} python3-venv" + # helpers/v8_build.py uses git to download depot_tools, and + # depot_tools/gclient uses it to grab V8: + PACKAGES="${PACKAGES} git" + # depot_tools/cipd uses curl to download various things including its own + # managed python: + PACKAGES="${PACKAGES} curl" + # the V8 build generation system (GN) uses pkg-config: + PACKAGES="${PACKAGES} pkg-config" + # the clang build script still uses the system lld: + PACKAGES="${PACKAGES} lld" + # we use grep below + PACKAGES="${PACKAGES} grep" + apt-get update -q -y + apt-get install -q -y ${PACKAGES} + + # the V8 build runs on clang by default. On mainstream platforms, V8 + # bundles a working clang, but not on arm64. So we need to bring our own + # clang and llvm and lld. + # bullseye ships an ancient llvm, clang, and lld. Add the latest stable: + LLVM_VERSION=18 + curl https://apt.llvm.org/llvm-snapshot.gpg.key > /etc/apt/trusted.gpg.d/apt.llvm.org.asc + echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-${LLVM_VERSION} main" \ + > /etc/apt/sources.list.d/llvm.list + apt-get update -q -y + PACKAGES="clang-${LLVM_VERSION} llvm-${LLVM_VERSION} lld-${LLVM_VERSION}" + apt-get install -q -y ${PACKAGES} + # set up non-versioned symlinks to the latest stable clang: + for pkg in ${PACKAGES}; do + for f in $(dpkg -L "${pkg}" | grep "/usr/bin/.*-${LLVM_VERSION}"); do + no18="${f%-${LLVM_VERSION}}" + rm -f "${no18}" + ln -s "${f}" "${no18}" + done + done + ;; + *alpine*) + # Let's download some system packages! + # Note that the precise list of packages we need is intertwined not just + # with V8, but with helpers/build_v8.py, which makes various decisions + # about using system versus V8-provided tools and dependencies. + PACKAGES="" + # First, some basic wheel-building dependencies: + PACKAGES="${PACKAGES} python3" + PACKAGES="${PACKAGES} py3-pip" + PACKAGES="${PACKAGES} py3-virtualenv" + # helpers/v8_build.py uses git to download depot_tools, and + # depot_tools/gclient uses it to grab V8: + PACKAGES="${PACKAGES} git" + # depot_tools/cipd uses curl to download various things including its own + # managed python: + PACKAGES="${PACKAGES} curl" + # depot_tools/vpython3 runs on bash: + PACKAGES="${PACKAGES} bash" + # pip needs the following to build some dependencies on the fly on + # aarch64: + PACKAGES="${PACKAGES} gcc" + PACKAGES="${PACKAGES} python3-dev" + PACKAGES="${PACKAGES} musl-dev" + PACKAGES="${PACKAGES} libffi-dev" + # the V8 build system uses gn to generate build files and on Alpine the + # one it bundles uses glibc (not musl) and thus does not work: + PACKAGES="${PACKAGES} gn" + # We need patch to apply patches to V8: + PACKAGES="${PACKAGES} patch" + # the V8 build runs on clang by default. On mainstream platforms, V8 + # bundles a working clang, but not on Alpine. So get our own clang, llvm, + # and lld: + PACKAGES="${PACKAGES} clang" + PACKAGES="${PACKAGES} llvm" + PACKAGES="${PACKAGES} lld" + # the V8 build system includes a sysroot (/usr/include, /usr/lib, etc) on + # many platforms, but not Alpine. Thus we have to provide extra build-time + # deps: + PACKAGES="${PACKAGES} glib-dev" + # the V8 build system has its own libstdc++, but it doesn't seem to work + # on Alpine, so we get and use the system one: + PACKAGES="${PACKAGES} libc++-dev" + apk update + apk add ${PACKAGES} + ;; + esac + + # Set up sccache. + # We can't directly use the binary fetched by Mozilla's GitHub SCCache + # Action, because we're doing a cross-arch build (the job is on x86_64 but + # the container may be on aarch64). So we reproduce the logic from the + # action: + # https://github.com/Mozilla-Actions/sccache-action/blob/main/src/setup.ts + SCCACHE_VERSION="v0.7.7" + SCCACHE_ARCH="$(uname -m)" + SCCACHE_BASE_URL="https://github.com/mozilla/sccache/releases/download" + # Note that the sccache binary is statically linked and thus we can use the + # musl binary on Debian (this is indeed what Mozilla's Github Action does): + SCCACHE_PLATFORM="unknown-linux-musl" + SCCACHE_URL="${SCCACHE_BASE_URL}/${SCCACHE_VERSION}/sccache-${SCCACHE_VERSION}-${SCCACHE_ARCH}-${SCCACHE_PLATFORM}.tar.gz" + + mkdir /sccache + cd /sccache + curl --location "${SCCACHE_URL}" > sccache.tar.gz + tar --strip-components 1 -xvzf sccache.tar.gz + + run: | + set -e + + export SCCACHE_GHA_ENABLED="true" + export SCCACHE_PATH=/sccache/sccache + export ACTIONS_CACHE_URL="${{ env.ACTIONS_CACHE_URL }}" + export ACTIONS_RUNTIME_TOKEN="${{ env.ACTIONS_RUNTIME_TOKEN }}" + + python3 -m venv /venv + . /venv/bin/activate + python3 -m pip install --upgrade build + python3 -m build --wheel + cp dist/*.whl /wheels/ + chmod a+rwx /wheels/*.whl + + - uses: actions/upload-artifact@v3 + with: + name: wheel + path: ./wheels/* + if-no-files-found: error + + - name: Run sccache stat for check + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - uses: uraimo/run-on-arch-action@v2 + name: Test wheel + with: + arch: none + distro: none + base_image: ${{ matrix.config.image }} + + setup: | + mkdir -p "${PWD}/wheels" + + dockerRunArgs: | + --volume "${PWD}/wheels:/wheels" + + shell: /bin/sh + + install: | + case "${{ matrix.config.image }}" in + *debian*) + PACKAGES="" + PACKAGES="${PACKAGES} python3" + PACKAGES="${PACKAGES} pip" + PACKAGES="${PACKAGES} python3-venv" + apt-get update -q -y + apt-get install -q -y ${PACKAGES} + ;; + *alpine*) + PACKAGES="" + PACKAGES="${PACKAGES} python3" + PACKAGES="${PACKAGES} py3-pip" + PACKAGES="${PACKAGES} py3-virtualenv" + # Needed by libmini_racer.so: + PACKAGES="${PACKAGES} libatomic" + apk update + apk add ${PACKAGES} + ;; + esac + + run: | + set -e + + python3 -m venv /venv + . /venv/bin/activate + case "${{ matrix.config.image }}" in + *debian*) + python3 -m pip install --upgrade hatch hatch-fancy-pypi-readme hatch-mkdocs + hatch run testinstalled:install /wheels/*.whl + hatch run testinstalled:run + ;; + *alpine*) + # Due to https://github.com/indygreg/python-build-standalone/issues/86 + # Hatch's matrix testing doesn't work on Alpine. Just run pytest directly + # on the system Python version: + python3 -m pip install --upgrade pytest + python3 -m pip install /wheels/*.whl + pytest tests + ;; + esac + + # We build for Mac and Windows directly on the supported GitHub-hosted runners. + non-linux-wheels: + name: Build wheel for ${{ matrix.config.os }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + # Note that to maximize compatibility of generated wheels, we build on the + # *oldest* supported GitHub-hosted runners, per + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners + - os: windows-2019 # x86_64 + - os: macos-11 # this is the earliest x86_64 runner + - os: macos-14 # this is the earliest arm64 runner + + steps: + - name: Configure git + run: git config --global core.symlinks true + + - uses: actions/checkout@v3 + with: + # Fetch all tags + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + # depot_tools still uses distutils which is gone in 3.12: + python-version: '3.11' + + - name: Build wheel + run: | + set -e + python3 -m pip install --upgrade build + python3 -m build --wheel + + - uses: actions/upload-artifact@v3 + with: + name: wheel + path: dist/* + if-no-files-found: error + + - name: Test wheel + run: | + set -e + python3 -m pip install --upgrade hatch hatch-fancy-pypi-readme hatch-mkdocs + hatch run testinstalled:install dist/*.whl + hatch run testinstalled:run + + publish: + name: Upload release to PyPI + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: + - linux-wheels + - non-linux-wheels + - sdist + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/ + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: + - uses: actions/download-artifact@v3 + with: + path: dist + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index 61da2b06..1405308c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,10 @@ __pycache__/ *.dylib *.dll +# generated V8 build artifacts +icudtl.dat +snapshot_blob.bin + # Distribution / packaging .Python env/ @@ -26,6 +30,7 @@ var/ *.egg-info/ .installed.cfg *.egg +site/ # PyInstaller # Usually these files are written by a python script from a template @@ -39,7 +44,6 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ -.tox/ .coverage .coverage.* .cache @@ -61,17 +65,7 @@ docs/_build/ # PyBuilder target/ -# V8 vendor -py_mini_racer/extension/.cipd -py_mini_racer/extension/.gclient* -py_mini_racer/extension/v8 -py_mini_racer/extension/out -py_mini_racer/extension/depot_tools -py_mini_racer/extension/build -py_mini_racer/extension/build_overrides -py_mini_racer/extension/buildtools -py_mini_racer/extension/testing -py_mini_racer/extension/third_party -py_mini_racer/extension/tools +# V8 build +v8_workspace *.iml diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index fb55a6ce..00000000 --- a/.isort.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[settings] -combine_as_imports = True -default_section = THIRDPARTY -force_grid_wrap = 0 -include_trailing_comma = True -known_first_party = py_mini_racer -multi_line_output = 3 -skip_glob = - .venv.* -skip = - .asv, - .tox, - docs, - py_mini_racer/extension diff --git a/.mdformat.toml b/.mdformat.toml new file mode 100644 index 00000000..05229142 --- /dev/null +++ b/.mdformat.toml @@ -0,0 +1 @@ +wrap = 88 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5f16754e..da6b0135 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,25 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: -- repo: https://github.com/PyCQA/flake8 - rev: 3.7.9 - hooks: - - id: flake8 -- repo: https://github.com/timothycrosley/isort - rev: 5.6.4 +- repo: local hooks: - - id: isort + - id: hatch-fmt + name: Hatch format and lint + entry: hatch fmt # runs Ruff, which includes both linting and formatting. + language: system + types: [python] + pass_filenames: false + - id: hatch-docs-format + name: Hatch format docs + entry: hatch run docs:fmt # runs mdformat + language: system - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.4.0 hooks: - id: trailing-whitespace - exclude: \.patch$ + exclude: \.(patch|md)$ - id: end-of-file-fixer - exclude: \.patch$ + exclude: \.(patch|md)$ - id: check-yaml - id: check-added-large-files - repo: local diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..55206bb0 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,196 @@ +# Architecture + +This document contains some notes about the design of PyMiniRacer. + +## Brief catalog of key components + +### `docs/` + +This is the [`mkdocs` ](https://www.mkdocs.org/) site for PyMiniRacer. To maximize +compatibility with standard open-source repository layout, this directory is just a +bunch of stubs which include files from the package root. + +### `hatch_build.py` + +This is a Hatch build hook which builds Python wheels, by calling `helpers/v8_build.py`. + +### `helpers/v8_build.py` + +This is the PyMiniRacer V8 build wrapper. Building V8 for many platforms (Windows, Mac, +glibc Linux, musl Linux) *and* architectures (x86_64, aarch64) is hard, especially since +V8 is primarily intended to be built by Google engineers on a somewhat different set of +of platforms (i.e., those Chrome runs on), and typically via cross-compilation from +relatively curated build hosts. So this file is complicated and full of `if` statements. + +### `src/v8_py_frontend/` + +This is a small frontend for V8, written in C++. It manages initialization, context, +marshals and unmarshals inputs and outputs through V8's type system, etc. The front-end +exposes simple functions and types which are friendly to the Python `ctypes` system. +These simple C++ functions in turn call the C++ V8 APIs. + +As noted below, `v8_py_frontend` is *not* a Python extension (it does *not* include +`Python.h` or link `libpython`, and it does not touch Python types). + +### (Compiled) `src/py_mini_racer/libmini_racer.so`, `src/py_mini_racer/mini_racer.dll`, `src/py_mini_racer/libmini_racer.dylib` + +These files (*which one* depends on the platform) contain the compiled V8 build, +complete with the frontend from `src/v8_py_frontend`. + +### (Compiled) `src/py_mini_racer/icudtl.dat` + +This is a build-time-generated internationalization artifact, used +[at runtime by V8](https://v8.dev/docs/i18n) and thus shipped with PyMiniRacer. + +### (Compiled) `src/py_mini_racer/snapshot_blob.bin` + +This is a build-time-generated +[startup snapshot](https://v8.dev/blog/custom-startup-snapshots), used at runtime by V8 +and thus shipped with PyMiniRacer. This is a snapshot of the JavaScript heap including +JavaScript built-ins, which accelerates JS engine startup. + +### `src/py_mini_racer/` + +This is the pure-Python implementation of PyMiniRacer. This loads the +(Python-independent) PyMiniRacer dynamic-link library (`.dll` on windows, `.so` on +Linux, `.dylib` on MacOS) and uses the Python `ctypes` system to call methods within it, +to manage V8 context and actually evaluate JavaScript code. + +### `.github/workflows/pypi-build.yml` + +This is the primary build script for PyMiniRacer, implemented as a GitHub Actions +workflow. + +## Design decisions + +These are listed in a topological sort, from most-fundamental to most-derived decisions. + +In theory, answers to questions in the vein of "Why is it done this way?" belong in this +document. + +### Minimize the interface with V8 + +V8 is extremely complex and is under continual, heavy development. Such development can +result in interface changes, which may in turn break PyMiniRacer. + +To mitigate the risk of breakage with new V8 builds, we seek to minimize the "API +surface area" between PyMiniRacer and V8. This means we seek to limit "advanced" use of +both: + +1. The V8 C++ API, and +1. The V8 build system (GN) and build options. + +Our success at minimizing the interface with the V8 build system can be measured by the +length of `helpers/v8.build.py` (444 lines as of this writing!). Making V8 build on +multiple platforms takes a lot of trickery... + +### Minimize the interface with the CPython API (don't make an extension) + +For similar reasons (the CPython API is complex and always in flux, *although not as +much as V8*), *combined with* the proliferation of Python versions (many versions of +CPython, PyPy, etc), we'd rather avoid directly interfacing with the CPython API. Thus, +*instead of* an extension module (which includes `Python.h` and links against +`libpython`), we build an ordinary Python-independent C++ library, and use `ctypes` to +access it. + +### Build V8 from source + +The V8 project does not produce stable binary distributions, i.e., static or dynamic +libraries. (In Linux terms, this would probably look like `libv8` and `libv8-dev`.) +Instead, any project (like NodeJS, Chromium, or... PyMiniRacer!) which wants to +integrate V8 must first build it. + +### Build V8 *with* our frontend (`v8_py_frontend`) as a snuck-in component + +We could *just* get a static library (i.e., `libv8.a`) from the V8 build, and link that +into a dynamic-link library \[i.e., `libmini_racer.so`\]) ourselves. + +However: + +1. We do have *one more* C++ file to compile (the C++ code in `v8_py_frontend`) +1. Because we're not making a true Python extension module (see above), we aren't using + Python's `setuptools` `Extension` infrastructure to perform a build. + +This *does*, however, leave us needing *some* platform-independent C++ toolchain. + +V8 already has such a toolchain, based on Ninja and Generated Ninja files (GN). + +Rather than bringing in another toolchain, we sneak `v8_py_frontend` (which is, after +all, just one C++ file) into the V8 tree itself, as a "custom dep". We then instruct GN +to build it as if it were an ordinary part of V8. + +The result is a dynamic-link library which contains an ordinary release build of V8, +plus our Python `ctypes`-friendly frontend. + +### Build PyPI wheels + +Because V8 takes so long to build (about 2-3 hours at present on the free GitHub Actions +runners, and >12 hours when emulating `aarch64` on them), we want to build wheels for +PyPI. We don't want folks to have to build V8 when they `pip install py-mini-racer`!. + +We build wheels for many operating systems and architectures based on popular demand via +GitHib issues. Currently the list is +`{x86_64, aarch64} × {Debian Linux, Alpine Linux, Mac, Windows}` (but skipping Windows +`aarch64` for now since there is not yet either a GitHub Actions runner, or emulation +layer for it). + +### Use the free GitHub Actions hosted runners + +PyMiniRacer is not a funded project, so we run on the free GitHub Actions hosted +runners. These currently let us build for many key platforms (including via emulation). + +This also lets contributors easily run the same build automation by simply forking the +PyMiniRacer repo and running the workflows (for free!) within their own forks. + +### Use `sccache` to patch around build timeouts + +As of this writing, the Linux `aarch64` builds run on emulation GitHub because has no +free hosted `aarch64` runners for Linux. This makes them so slow, they struggle to +complete at all. They take about 24 hours to run. The GitHub Actions +[job timeout is only 6 hours](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits), +so we have to restart the jobs multiple times. We rely on +[`sccache`](https://github.com/mozilla/sccache) to catch the build up to prior progress. + +It would in theory be less ugly to segment the build into small interlinked jobs of less +than 6 hours each so they each succeed, but for now it's simpler to just manually +restart the failed jobs, each time loading from the build cache and making progress, +until they finally succeed. Hopefully at some point GitHub will provide native `aarch64` +Linux runners, which will alleviate this problem. + +### Use `uraimo/run-on-arch-action` (and not `cibuildwheel`) + +So, we need to build wheels for multiple architectures. For Windows and Mac (`x86_64` on +Windows, and both `x86_64` and `aarch64` on Mac) we can can use GitHub hosted runners. +For Linux builds (Debian and Alpine, and `x86_64` and `aarch64`), we use the fantastic +GitHub Action workflow step +[`uraimo/run-on-arch-action`](https://github.com/uraimo/run-on-arch-action), which lets +us build a docker container on the fly and run it on QEMU. + +Many modern Python projects which need to build wheels with native code use +[the `cibuildwheel` project](https://github.com/pypa/cBbuildwheel) to manange their +builds. However, `cibuildwheel` isn't a perfect fit here. Because we are building +Python-independent dynamic-link libraries instead of Python extension modules modules, +we aren't linking with any particular Python ABI. Thus we need *only* +`(operating systems × architectures)` builds, whereas `cibuildwheel` generates +`(operating systems × architecture × Python flavors × Python versions)` wheels. +[That's a ton of wheels](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip)! +Given that it takes hours to *days* to build PyMiniRacer for *one* target OS and +architecture, doing redundant builds is undesirable. + +It might be possible to use `cibuildwheel` with PyMiniRacer by segmenting the build of +the dynamic-link library (i.e., `libmini_racer.so`) from the actual wheel build. That +is, we could have the following separate components: + +1. Create a separate Github Actions workflow to build the `libmini_racer.so` binary + (i.e., the hard part). Publish that as a release, using the GitHub release artifact + management system as a distribution mechanism. +1. The wheel build step could then simply download a pre-built binary from the latest + GitHub release. We could use `cibuildwheel` to manage this step. This would + generate many redundant wheels (because the wheels we'd generate for, say, CPython + 3.9 and 3.10 would be identical), but it wouldn't matter because it would be cheap + and automatic. + +To sum up, to use `cibuildwheel`, we would still need our own *separate* +multi-architecture build workflow for V8, *ahead of* the `cibuildwheel` step. So +`cibuildwheel` could potentially simplify the actual wheel distribution for us, but it +wouldn't simplify the overall workflow management. diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 00000000..6633603f --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,15 @@ +# Credits + +## Authors + +- Jean-Baptiste AVIAT +- Boris FELD +- Selim MENOUAR +- Nicolas VIVET + +## Contributors + +- messense +- Ben Creech + +_Why not add your name to the list?_ diff --git a/AUTHORS.rst b/AUTHORS.rst deleted file mode 100644 index e9b31730..00000000 --- a/AUTHORS.rst +++ /dev/null @@ -1,18 +0,0 @@ -======= -Credits -======= - -Authors -------- - -* Jean-Baptiste AVIAT -* Boris FELD -* Selim MENOUAR -* Nicolas VIVET - -Contributors ------------- - -* messense https://github.com/messense - -Why not add your name to the list? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e70f175c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,184 @@ +# Contributing + +Contributions are welcome, and they are greatly appreciated! Every little bit helps, and +credit will always be given. + +## Types of Contributions + +You can contribute in many ways: + +### Report Bugs + +Report bugs at . + +If you are reporting a bug, please include: + +- Your operating system name and version. +- Any details about your local setup that might be helpful in troubleshooting. +- Detailed steps to reproduce the bug. + +### Fix Bugs + +Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever +wants to implement it. + +## Implement Features + +Look through the GitHub issues for features. Anything tagged with "feature" is open to +whoever wants to implement it. + +## Write Documentation + +Python Mini Racer could always use more documentation, whether as part of the official +Python Mini Racer docs, in docstrings, or even on the web in blog posts, articles, and +such. + +## Submit Feedback + +The best way to send feedback is to file an issue at +. + +If you are proposing a feature: + +- Explain in detail how it would work. +- Keep the scope as narrow as possible, to make it easier to implement. +- Remember that this is a volunteer-driven project, and that contributions are welcome + :) + +## Get Started! + +Ready to contribute? Here's how to set up `PyMiniRacer` for local development. + +!!! warning + Building this package from source takes several GB of disk space and takes 1-2 hours. + +1. Do a quick scan through [the architecture guide](ARCHITECTURE.md) before diving in. + +1. Fork the `PyMiniRacer` repo on GitHub. + +1. Run some of the following: + + ```sh + # Set up a virtualenv: + $ python -m venv ~/my_venv + $ . ~/my_venv/bin/activate + $ pip install pre-commit hatch hatch-mkdocs + + # Set up a local clone of your fork: + $ git clone git@github.com:your_name_here/PyMiniRacer.git + $ cd PyMiniRacer/ + $ pre-commit install # install our pre-commit hooks + + # Build and test stuff: + $ hatch run docs:serve # build the docs you're reading now! + $ hatch build # this may take 1-2 hours! + $ hatch run test:run + ``` + + You can also play with your build locally, as follows: + + ```sh + $ hatch shell + $ python + >>> from py_mini_racer import MiniRacer + >>> mr = MiniRacer() + >>> mr.eval('6*7') + 42 + >>> exit() + $ exit + ``` + +1. Create a branch for local development:: + + ```sh + $ git checkout -b name-of-your-bugfix-or-feature + ``` + + Now you can make your changes locally. + +1. When you're done making changes, check that your changes pass the linter and the + tests, including testing other Python versions: + + ```sh + $ pre-commit run # run formatters and linters + $ hatch run docs:serve # look at the docs if you changed them! + $ hatch build # this may take 1-2 hours! + $ hatch run test:run + $ hatch run test:run-coverage # with coverage! + ``` + +1. Commit your changes and push your branch to GitHub:: + + ```sh + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + ``` + +1. (Optional) Run the GitHub Actions build workflow on your fork to ensure that all + architectures work. + +1. Submit a pull request through the GitHub website. + +## Tests + +If you want to run the tests, you need to build the package first: + +```sh + $ hatch build +``` + +Then run: + +```sh + $ hatch run test +``` + +Or for the full test matrix: + +```sh + $ hatch run test:run + $ hatch run test:run-coverage # with coverage! +``` + +## Pull Request Guidelines + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +1. If the pull request adds functionality, the docs should be updated. Put your new + functionality into a function with a docstring, and add the feature to the list in + README.rst. +1. The pull request should work for the entire test matrix of Python versions + (`hatch run tests:run`). + +## Releasing `PyMiniRacer` + +Releases for `PyMiniRacer` should be done by GitHub Actions on the official project +repository. + +To release: + +1. Push all code to `main` on the offical repository. + +1. Add and push a tag: + + ```sh + $ git fetch --tags + $ git tag -l + # observe the next available tag + NEXT_TAG=the next tag + $ git tag "${NEXT_TAG}" + $ git push origin "${NEXT_TAG}" + ``` + +1. Observe the build process on GitHub Actions. It should build and push docs and upload + wheels to PyPI automatically. + + !!! warning + As of this writing, the `aarch64` Linux builds are slow because they're running on + emulation. They time out on the first try (and second and third and...) after 6 + hours. If you "restart failed jobs", they will quickly catch up to where where they + left off due to [`sccache`](https://github.com/mozilla/sccache). The jobs should + *eventually* complete within the time limit. You can observe their slow progress + using the Ninja build status (e.g., `[1645/2312] CXX obj/v8_foo_bar.o`). diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index b13bd07e..00000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,112 +0,0 @@ -============ -Contributing -============ - -Contributions are welcome, and they are greatly appreciated! Every -little bit helps, and credit will always be given. - -You can contribute in many ways: - -Types of Contributions ----------------------- - -Report Bugs -~~~~~~~~~~~ - -Report bugs at https://github.com/sqreen/PyMiniRacer/issues. - -If you are reporting a bug, please include: - -* Your operating system name and version. -* Any details about your local setup that might be helpful in troubleshooting. -* Detailed steps to reproduce the bug. - -Fix Bugs -~~~~~~~~ - -Look through the GitHub issues for bugs. Anything tagged with "bug" -is open to whoever wants to implement it. - -Implement Features -~~~~~~~~~~~~~~~~~~ - -Look through the GitHub issues for features. Anything tagged with "feature" -is open to whoever wants to implement it. - -Write Documentation -~~~~~~~~~~~~~~~~~~~ - -Python Mini Racer could always use more documentation, whether as part of the -official Python Mini Racer docs, in docstrings, or even on the web in blog posts, -articles, and such. - -Submit Feedback -~~~~~~~~~~~~~~~ - -The best way to send feedback is to file an issue at https://github.com/sqreen/PyMiniRacer/issues. - -If you are proposing a feature: - -* Explain in detail how it would work. -* Keep the scope as narrow as possible, to make it easier to implement. -* Remember that this is a volunteer-driven project, and that contributions - are welcome :) - -Get Started! ------------- - -Ready to contribute? Here's how to set up `PyMiniRacer` for local development. - -1. Fork the `PyMiniRacer` repo on GitHub. -2. Clone your fork locally:: - - $ git clone git@github.com:your_name_here/PyMiniRacer.git - -3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: - - $ mkvirtualenv PyMiniRacer - $ cd PyMiniRacer/ - $ python helpers/v8_build.py - $ python setup.py develop - -**Warning**: building this package from source takes several GB of disk space and takes ~60 minutes. - -4. Create a branch for local development:: - - $ git checkout -b name-of-your-bugfix-or-feature - - Now you can make your changes locally. - -5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: - - $ flake8 py_mini_racer tests - $ python setup.py test - $ tox - - To get flake8 and tox, just pip install them into your virtualenv. - -6. Commit your changes and push your branch to GitHub:: - - $ git add . - $ git commit -m "Your detailed description of your changes." - $ git push origin name-of-your-bugfix-or-feature - -7. Submit a pull request through the GitHub website. - -Pull Request Guidelines ------------------------ - -Before you submit a pull request, check that it meets these guidelines: - -1. The pull request should include tests. -2. If the pull request adds functionality, the docs should be updated. Put - your new functionality into a function with a docstring, and add the - feature to the list in README.rst. -3. The pull request should work for Python 2.7, at least 3.5 and for PyPy. - -Tips ----- - -To run a subset of tests:: - - $ python -m unittest tests.test_eval diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 00000000..7e086127 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,142 @@ +# History + +## 0.7.0 (2024-03-06) + +- Update V8 to 12.2 +- Drop Python 2 support +- Fix small Python 3.12 issue and add testing for Python 3.9-3.12 +- Add aarch64 support for Mac and Linux +- Revamp DLL loading to be compliant with Python 3.9-style resource loading. This may + present a small breaking change for advanced usage; the `EXTENSION_PATH` and + `EXTENSION_NAME` module variables, and `MiniRacer.v8_flags` and `MiniRacer.ext` + class variable have all been removed. +- Add support for the [ECMAScript internalization API](https://v8.dev/docs/i18n) and use + [fast startup snapshots](https://v8.dev/blog/custom-startup-snapshots) +- Switch from setuptools to Hatch +- Switch from tox to Hatch +- Switch from use of flake8 and isort to Hatch's wrapper of Ruff +- Switch from Sphinx to mkdocs (and hatch-mkdocs) +- Switch from unittest to pytest +- Add ARCHITECTURE.md and lots of code comments + +## 0.6.0 (2020-04-20) + +- Update V8 to 8.9 +- Optimize function calls without arguments +- Switch V8 to single threaded mode to avoid crashes after fork +- Switch to strict mode by default +- Revamp documentation + +## 0.5.0 (2020-02-25) + +- Update V8 to 8.8 + +## 0.4.0 (2020-09-22) + +- Universal wheels for Linux, Mac and Windows +- Fallback to source package for Alpine Linux + +## 0.3.0 (2020-06-29) + +- Introduce a strict mode +- Fix array conversion when size changes dynamically (CVE-2020-25489) + +## 0.2.0 (2020-03-11) + +- Support for Alpine Linux +- Avoid pip private modules in setup.py + +## 0.2.0b1 (2020-01-09) + +- Support for Windows 64 bits +- Support for Python 3.8 +- Upgrade V8 to 7.8 +- Support soft memory limits + +## 0.1.18 (2019-01-04) + +- Support memory and time limits + +## 0.1.17 (2018-19-12) + +- Upgrade libv8 +- Fix a memory leak + +## 0.1.16 (2018-07-11) + +- Add wheel for Python without PyMalloc + +## 0.1.15 (2018-06-18) + +- Add wheel for Python 3.7 + +## 0.1.14 (2018-05-25) + +- Add support for pip 10 +- Update package metadata + +## 0.1.13 (2018-03-15) + +- Add heap_stats function +- Fix issue with returned strings containing null bytes + +## 0.1.12 (2018-17-04) + +- Remove dependency to enum + +## 0.1.11 (2017-07-11) + +- Add compatibility for centos6 + +## 0.1.10 (2017-03-31) + +- Add the possibility to pass a custom JSON encoder in call. + +## 0.1.9 (2017-03-24) + +- Fix the compilation for Ubuntu 12.04 and glibc \< 2.17. + +## 0.1.8 (2017-03-02) + +- Update targets build for better compatibility with old Mac OS X and linux platforms. + +## 0.1.7 (2016-10-04) + +- Improve general performances of the JS execution. +- Add the possibility to build a different version of V8 (for example with debug + symbols). +- Fix a conflict that could happens between statically linked libraries and dynamic + ones. + +## 0.1.6 (2016-08-12) + +- Add error message when py_mini_racer sdist fails to build asking to update pip in + order to download the pre-compiled wheel instead of the source distribution. + +## 0.1.5 (2016-08-04) + +- Build py_mini_racer against a static Python. When built against a shared library + python, it doesn't work with a static Python. + +## 0.1.4 (2016-08-04) + +- Ensure JSEvalException message is converted to unicode + +## 0.1.3 (2016-08-04) + +- Fix extension loading for python3 +- Add a make target for building distributions (sdist + wheels) +- Fix eval conversion for python 3 + +## 0.1.2 (2016-08-03) + +- Fix date support +- Fix Dockerfile for generating python3 wheels + +## 0.1.1 (2016-08-02) + +- Fix sdist distribution. + +## 0.1.0 (2016-08-01) + +- First release on PyPI. diff --git a/HISTORY.rst b/HISTORY.rst deleted file mode 100644 index 82c8278f..00000000 --- a/HISTORY.rst +++ /dev/null @@ -1,125 +0,0 @@ -.. :changelog: - -History -------- - -0.6.0 (2020-04-20) -''''''''''''''''''' -* Update V8 to 8.9 -* Optimize function calls without arguments -* Switch V8 to single threaded mode to avoid crashes after fork -* Switch to strict mode by default -* Revamp documentation - -0.5.0 (2020-02-25) -''''''''''''''''''' -* Update V8 to 8.8 - -0.4.0 (2020-09-22) -''''''''''''''''''' -* Universal wheels for Linux, Mac and Windows -* Fallback to source package for Alpine Linux - -0.3.0 (2020-06-29) -''''''''''''''''''' -* Introduce a strict mode -* Fix array conversion when size changes dynamically (CVE-2020-25489) - -0.2.0 (2020-03-11) -''''''''''''''''''' -* Support for Alpine Linux -* Avoid pip private modules in setup.py - -0.2.0b1 (2020-01-09) -''''''''''''''''''''' -* Support for Windows 64 bits -* Support for Python 3.8 -* Upgrade V8 to 7.8 -* Support soft memory limits - -0.1.18 (2019-01-04) -'''''''''''''''''''' -* Support memory and time limits - -0.1.17 (2018-19-12) -'''''''''''''''''''' -* Upgrade libv8 -* Fix a memory leak - -0.1.16 (2018-07-11) -'''''''''''''''''''' -* Add wheel for Python without PyMalloc - -0.1.15 (2018-06-18) -'''''''''''''''''''' -* Add wheel for Python 3.7 - - -0.1.14 (2018-05-25) -'''''''''''''''''''' -* Add support for pip 10 -* Update package metadata - -0.1.13 (2018-03-15) -'''''''''''''''''''' -* Add heap_stats function -* Fix issue with returned strings containing null bytes - -0.1.12 (2018-17-04) -'''''''''''''''''''' -* Remove dependency to enum - -0.1.11 (2017-07-11) -'''''''''''''''''''' -* Add compatibility for centos6 - -0.1.10 (2017-03-31) -'''''''''''''''''''' -* Add the possibility to pass a custom JSON encoder in call. - -0.1.9 (2017-03-24) -''''''''''''''''''' -* Fix the compilation for Ubuntu 12.04 and glibc < 2.17. - -0.1.8 (2017-03-02) -''''''''''''''''''' -* Update targets build for better compatibility with old Mac OS X and linux platforms. - -0.1.7 (2016-10-04) -''''''''''''''''''' -* Improve general performances of the JS execution. -* Add the possibility to build a different version of V8 (for example with debug symbols). -* Fix a conflict that could happens between statically linked libraries and dynamic ones. - -0.1.6 (2016-08-12) -''''''''''''''''''' -* Add error message when py_mini_racer sdist fails to build asking to update pip in order to download the pre-compiled wheel instead of the source distribution. - -0.1.5 (2016-08-04) -''''''''''''''''''' -* Build py_mini_racer against a static Python. When built against a shared library python, it doesn't work with a static Python. - -0.1.4 (2016-08-04) -''''''''''''''''''' -* Ensure JSEvalException message is converted to unicode - -0.1.3 (2016-08-04) -''''''''''''''''''' -* Fix extension loading for python3 -* Add a make target for building distributions (sdist + wheels) -* Fix eval conversion for python 3 - -0.1.2 (2016-08-03) -''''''''''''''''''' -* Fix date support -* Fix Dockerfile for generating python3 wheels - - -0.1.1 (2016-08-02) -''''''''''''''''''' -* Fix sdist distribution. - - -0.1.0 (2016-08-01) -''''''''''''''''''' -* First release on PyPI. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index b99a907a..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,15 +0,0 @@ -include AUTHORS.rst -include CONTRIBUTING.rst -include HISTORY.rst -include LICENSE -include README.rst - -include py_mini_racer/libmini_racer.glibc.so py_mini_racer/libmini_racer.muslc.so -include py_mini_racer/libmini_racer.dylib -include py_mini_racer/mini_racer.dll - -recursive-include tests * -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] - -recursive-include docs *.rst conf.py diff --git a/Makefile b/Makefile deleted file mode 100644 index 351a3910..00000000 --- a/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -.PHONY: clean-pyc clean-build docs clean -define BROWSER_PYSCRIPT -import os, webbrowser, sys -try: - from urllib import pathname2url -except: - from urllib.request import pathname2url - -webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) -endef -export BROWSER_PYSCRIPT -BROWSER := python -c "$$BROWSER_PYSCRIPT" - -help: - @echo "clean - remove all build, test, coverage and Python artifacts" - @echo "clean-build - remove build artifacts" - @echo "clean-pyc - remove Python file artifacts" - @echo "clean-test - remove test and coverage artifacts" - @echo "lint - check style with flake8" - @echo "test - run tests quickly with the default Python" - @echo "test-all - run tests on every Python version with tox" - @echo "coverage - check code coverage quickly with the default Python" - @echo "docs - generate Sphinx HTML documentation, including API docs" - @echo "release - package and upload a release" - @echo "dist - package" - @echo "install - install the package to the active Python's site-packages" - -clean: clean-build clean-pyc clean-test - -clean-build: - rm -fr build/ - rm -fr dist/ - rm -fr .eggs/ - rm -fr venv* - rm -Rf py_mini_racer/*.so - find . -name '*.egg-info' -exec rm -fr {} + - find . -name '*.egg' -exec rm -f {} + - -clean-pyc: - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + - -clean-test: - rm -fr .tox/ - rm -f .coverage - rm -fr htmlcov/ - -lint: - flake8 py_mini_racer tests - -test: - python setup.py test - -test-all: - tox - -coverage: - coverage run --source py_mini_racer setup.py test - coverage report -m - coverage html - $(BROWSER) htmlcov/index.html - -docs: - rm -f docs/py_mini_racer.rst - rm -f docs/modules.rst - sphinx-apidoc -o docs/ py_mini_racer - $(MAKE) -C docs clean - $(MAKE) -C docs html - $(BROWSER) docs/_build/html/index.html - -servedocs: docs - watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . - -release: clean - python setup.py sdist upload - python setup.py bdist_wheel upload - -dist: clean - python setup.py sdist - python setup.py bdist_wheel - ls -l dist - -upload: dist - twine upload dist/* - -install: clean - python setup.py install diff --git a/README.md b/README.md new file mode 100644 index 00000000..12500a22 --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +[![PyPI status indicator](https://img.shields.io/pypi/v/py_mini_racer.svg)](https://pypi.python.org/pypi/py_mini_racer) +[![Github workflow status indicator](https://github.com/sqreen/PyMiniRacer/actions/workflows/build.yml/badge.svg)](https://github.com/sqreen/PyMiniRacer/actions/workflows/build.yml) +[![ISC License](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC) + +Minimal, modern embedded V8 for Python. + +![MiniRacer logo: a V8 with a very snakey 8](py_mini_racer.png) + +## Features + +- Latest ECMAScript support +- Web Assembly support +- Unicode support +- Thread safe +- Re-usable contexts + +MiniRacer can be easily used by Django or Flask projects to minify assets, run babel or +WASM modules. + +## Examples + +MiniRacer is straightforward to use: + +```sh + $ pip install py-mini-racer +``` + +and then: + +```python + $ python3 + >>> from py_mini_racer import MiniRacer + >>> ctx = MiniRacer() + >>> ctx.eval("1+1") + 2 + >>> ctx.eval("var x = {company: 'Sqreen'}; x.company") + 'Sqreen' + >>> print(ctx.eval("'❤'")) + ❤ + >>> ctx.eval("var fun = () => ({ foo: 1 });") +``` + +Variables are kept inside of a context: + +```python + >>> ctx.eval("x.company") + 'Sqreen' +``` + +While `eval` only supports returning primitive data types such as strings, `call` +supports returning composite types such as objects: + +```python + >>> ctx.call("fun") + {'foo': 1} +``` + +Composite values are serialized using JSON. Use a custom JSON encoder when sending +non-JSON encodable parameters: + +```python + import json + + from datetime import datetime + + class CustomEncoder(json.JSONEncoder): + + def default(self, obj): + if isinstance(obj, datetime): + return obj.isoformat() + + return json.JSONEncoder.default(self, obj) +``` + +```python + >>> ctx.eval("var f = function(args) { return args; }") + >>> ctx.call("f", datetime.now(), encoder=CustomEncoder) + '2017-03-31T16:51:02.474118' +``` + +MiniRacer is ES6 capable: + +```python + >>> ctx.execute("[1,2,3].includes(5)") + False +``` + +V8 heap information can be retrieved: + +```python + >>> ctx.heap_stats() + {'total_physical_size': 1613896, + 'used_heap_size': 1512520, + 'total_heap_size': 3997696, + 'total_heap_size_executable': 3145728, + 'heap_size_limit': 1501560832} +``` + +A WASM example is available in the +[`tests`](https://github.com/sqreen/PyMiniRacer/blob/master/tests/test_wasm.py). + +## Compatibility + +PyMiniRacer is compatible with Python 3.8 and is based on `ctypes`. + +PyMiniRacer is distributed using [wheels](https://pythonwheels.com/) on +[PyPI](https://pypi.org/). The wheels are intended to provide compatibility with: + +| OS | x86_64 | aarch64 | +| ------------------------------- | ------ | ------- | +| macOS ≥ 10.9 | ✓ | ✓ | +| Windows ≥ 10 | ✓ | ✖ | +| Ubuntu ≥ 20.04 | ✓ | ✓ | +| Debian ≥ 11 | ✓ | ✓ | +| RHEL ≥ 8 | ✓ | ✓ | +| other Linuxes with glibc ≥ 2.31 | ✓ | ✓ | +| Alpine ≥ 3.16 | ✓ | ✓ | +| other Linux with musl ≥ 1.2 | ✓ | ✓ | + +If you have a up-to-date pip and it doesn't use a wheel, you might have an environment +for which no wheel is built. Please open an issue. + +## Developing and releasing PyMiniRacer + +See [the contribution guide](CONTRIBUTING.md). + +## Credits + +Built with love by [Sqreen](https://www.sqreen.com). + +PyMiniRacer launch was described in +[`this blog post`](https://blog.sqreen.com/embedding-javascript-into-python/). + +PyMiniRacer is inspired by [mini_racer](https://github.com/SamSaffron/mini_racer), built +for the Ruby world by Sam Saffron. + +[`Cookiecutter-pypackage`](https://github.com/audreyr/cookiecutter-pypackage) was used +as this package skeleton. diff --git a/README.rst b/README.rst deleted file mode 100644 index 520191ad..00000000 --- a/README.rst +++ /dev/null @@ -1,212 +0,0 @@ -.. image:: https://img.shields.io/pypi/v/py_mini_racer.svg - :target: https://pypi.python.org/pypi/py_mini_racer - -.. image:: https://github.com/sqreen/PyMiniRacer/actions/workflows/build.yml/badge.svg - :target: https://github.com/sqreen/PyMiniRacer/actions/workflows/build.yml - -.. image:: https://img.shields.io/badge/License-ISC-blue.svg - :target: https://opensource.org/licenses/ISC - -Minimal, modern embedded V8 for Python. - -.. image:: data/py_mini_racer.png - :align: center - -Features --------- - -* Latest ECMAScript support -* Web Assembly support -* Unicode support -* Thread safe -* Re-usable contexts - -MiniRacer can be easily used by Django or Flask projects to minify assets, run -babel or WASM modules. - -Examples --------- - -MiniRacer is straightforward to use: - -.. code-block:: python - - >>> from py_mini_racer import MiniRacer - >>> ctx = MiniRacer() - >>> ctx.eval("1+1") - 2 - >>> ctx.eval("var x = {company: 'Sqreen'}; x.company") - 'Sqreen' - >>> print(ctx.eval("'\N{HEAVY BLACK HEART}'")) - ❤ - >>> ctx.eval("var fun = () => ({ foo: 1 });") - -Variables are kept inside of a context: - -.. code-block:: python - - >>> ctx.eval("x.company") - 'Sqreen' - - -While ``eval`` only supports returning primitive data types such as -strings, ``call`` supports returning composite types such as objects: - -.. code-block:: python - - >>> ctx.call("fun") - {'foo': 1} - - -Composite values are serialized using JSON. -Use a custom JSON encoder when sending non-JSON encodable parameters: - -.. code-block:: python - - import json - - from datetime import datetime - - class CustomEncoder(json.JSONEncoder): - - def default(self, obj): - if isinstance(obj, datetime): - return obj.isoformat() - - return json.JSONEncoder.default(self, obj) - - -.. code-block:: python - - >>> ctx.eval("var f = function(args) { return args; }") - >>> ctx.call("f", datetime.now(), encoder=CustomEncoder) - '2017-03-31T16:51:02.474118' - - -MiniRacer is ES6 capable: - -.. code-block:: python - - >>> ctx.execute("[1,2,3].includes(5)") - False - -V8 heap information can be retrieved: - -.. code-block:: python - - >>> ctx.heap_stats() - {'total_physical_size': 1613896, - 'used_heap_size': 1512520, - 'total_heap_size': 3997696, - 'total_heap_size_executable': 3145728, - 'heap_size_limit': 1501560832} - - -A WASM example is available in the `tests`_. - -.. _`tests`: https://github.com/sqreen/PyMiniRacer/blob/master/tests/test_wasm.py - - -Compatibility -------------- - -PyMiniRacer is compatible with Python 2 & 3 and based on ctypes. - -The binary builds have been tested on x86_64 with: - -* macOS >= 10.13 -* Ubuntu >= 16.04 -* Debian >= 9 -* CentOS >= 7 -* Alpine >= 3.11 -* Windows 10 - -It should work on any Linux with a libc >= 2.12 and a wheel compatible pip (>= 8.1). - -If you're running Alpine Linux, you may need to install required dependencies manually using the following command: - -.. code-block:: bash - - $ apk add libgcc libstdc++ - -If you have a up-to-date pip and it doesn't use a wheel, you might have an environment for which no wheel is built. Please open an issue. - -Installation ------------- - -We built Python wheels (prebuilt binaries) for macOS 64 bits, Linux 64 bits and Windows 64 bits. - -.. code:: bash - - $ pip install py-mini-racer - -Build ------ - -**Warning**: building this package from source takes several GB of disk space and takes ~60 minutes. - -First check that your current Python executable is version 2.7. This is required -by the V8 build system. - -.. code:: bash - - $ python --version - Python 2.7.16 - -You can build the extension with the following command: - -.. code:: bash - - $ python helpers/v8_build.py - -You can generate a wheel for whatever Python version with the command: - -.. code:: bash - - $ python3 helpers/build_package.py wheel dist - -It will then build V8, the extension, and generates a wheel for your current -Python version. The V8 builds are cached in the ``py_mini_racer/extension/v8/`` -directory. - -Notes for building on macOS -''''''''''''''''''''''''''' - -The legacy Python binary builds (OSX 10.6) need to be downloaded from: - https://www.python.org/downloads/ - -They will allow to build a wheel compatible with former OSX versions. - -Tests ------ - -If you want to run the tests, you need to build the extension first, first install pytest: - -.. code-block:: bash - - $ python -m pip install pytest - -Then launch: - -.. code:: bash - - $ python -m pytest tests - -Credits -------- - -Built with love by Sqreen_. - -.. _Sqreen: https://www.sqreen.com - -PyMiniRacer launch was described in `this blog post`_. - -.. _`this blog post`: https://blog.sqreen.com/embedding-javascript-into-python/ - -PyMiniRacer is inspired by mini_racer_, built for the Ruby world by Sam Saffron. - -.. _`mini_racer`: https://github.com/SamSaffron/mini_racer - -`Cookiecutter-pypackage`_ was used as this package skeleton. - -.. _`Cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage diff --git a/data/py_mini_racer.png b/data/py_mini_racer.png deleted file mode 100644 index 7654b21185f0924b803074d74a92aacbb81c00b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78857 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/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/py_mini_racer.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/py_mini_racer.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/py_mini_racer" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/py_mini_racer" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..a5f17e1b --- /dev/null +++ b/docs/api.md @@ -0,0 +1,3 @@ +# API Reference + +::: py_mini_racer.py_mini_racer diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 83492fe7..00000000 --- a/docs/api.rst +++ /dev/null @@ -1,5 +0,0 @@ -API Reference -------------- - -.. autoclass:: py_mini_racer.MiniRacer - :members: eval, execute, call, set_soft_memory_limit, was_soft_memory_limit_reached, low_memory_notification, heap_stats, v8_version diff --git a/docs/architecture.md b/docs/architecture.md new file mode 120000 index 00000000..6763c822 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1 @@ +../ARCHITECTURE.md \ No newline at end of file diff --git a/docs/authors.md b/docs/authors.md new file mode 120000 index 00000000..3234d6e0 --- /dev/null +++ b/docs/authors.md @@ -0,0 +1 @@ +../AUTHORS.md \ No newline at end of file diff --git a/docs/authors.rst b/docs/authors.rst deleted file mode 100644 index e122f914..00000000 --- a/docs/authors.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../AUTHORS.rst diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100755 index bc6b3e21..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,283 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# py_mini_racer documentation build configuration file, created by -# sphinx-quickstart on Tue Jul 9 22:26:36 2013. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os - -# If extensions (or modules to document with autodoc) are in another -# directory, add these directories to sys.path here. If the directory is -# relative to the documentation root, use os.path.abspath to make it -# absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# Get the project root dir, which is the parent dir of this -cwd = os.getcwd() -project_root = os.path.dirname(cwd) - -# Insert the project root dir as the first element in the PYTHONPATH. -# This lets us ensure that the source package is imported, and that its -# version is used. -sys.path.insert(0, project_root) - -from py_mini_racer import __about__ - -# -- General configuration --------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] - -autodoc_member_order = 'bysource' - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'PyMiniRacer' -copyright = u'2019, Sqreen' - -# The version info for the project you're documenting, acts as replacement -# for |version| and |release|, also used in various other places throughout -# the built documents. -# -# The short X.Y version. -version = __about__.__version__ -# The full version, including alpha/beta/rc tags. -release = __about__.__version__ - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to -# some non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built -# documents. -#keep_warnings = False - - -# -- Options for HTML output ------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a -# theme further. For a list of options available for each theme, see the -# documentation. -html_theme_options = { - 'logo_name': False, - 'github_user': 'sqreen', - 'github_repo': 'PyMiniRacer', - 'github_button': True, -} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as -# html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the -# top of the sidebar. -html_logo = 'data/py_mini_racer.png' - -# The name of an image file (within the static path) to use as favicon -# of the docs. This file should be a Windows icon file (.ico) being -# 16x16 or 32x32 pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) -# here, relative to this directory. They are copied after the builtin -# static files, so a file named "default.css" will overwrite the builtin -# "default.css". -html_static_path = ['data'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - - -# Additional templates that should be rendered to pages, maps page names -# to template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. -# Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. -# Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages -# will contain a tag referring to it. The value of this option -# must be the base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'py_mini_racerdoc' - - -# -- Options for LaTeX output ------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - #'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). -latex_documents = [ - ('index', 'py_mini_racer.tex', - u'PyMiniRacer Documentation', - u'Boris FELD', 'manual'), -] - -# The name of an image file (relative to this directory) to place at -# the top of the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings -# are parts, not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output ------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'py_mini_racer', - u'PyMiniRacer Documentation', - [u'Sqreen'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ---------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'py_mini_racer', - u'PyMiniRacer Documentation', - u'Sqreen', - 'py_mini_racer', - 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/contributing.md b/docs/contributing.md new file mode 120000 index 00000000..44fcc634 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1 @@ +../CONTRIBUTING.md \ No newline at end of file diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index e582053e..00000000 --- a/docs/contributing.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CONTRIBUTING.rst diff --git a/docs/history.md b/docs/history.md new file mode 120000 index 00000000..a5333ae4 --- /dev/null +++ b/docs/history.md @@ -0,0 +1 @@ +../HISTORY.md \ No newline at end of file diff --git a/docs/history.rst b/docs/history.rst deleted file mode 100644 index 25064996..00000000 --- a/docs/history.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../HISTORY.rst diff --git a/docs/index.md b/docs/index.md new file mode 120000 index 00000000..32d46ee8 --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index db166057..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. include:: readme.rst - -API Reference -------------- - -.. toctree:: - :maxdepth: 2 - - api - -Additional Information ----------------------- - -.. toctree:: - :maxdepth: 2 - - contributing - authors - history diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 42cd866d..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,242 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\py_mini_racer.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\py_mini_racer.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/readme.rst b/docs/readme.rst deleted file mode 100644 index 8fb07a4e..00000000 --- a/docs/readme.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. include:: ../README.rst - :start-after: center - :end-before: Credits diff --git a/hatch_build.py b/hatch_build.py new file mode 100644 index 00000000..71722fe8 --- /dev/null +++ b/hatch_build.py @@ -0,0 +1,39 @@ +from os.path import dirname +from sys import path as syspath +from typing import Iterable + +from hatchling.builders.config import BuilderConfig +from hatchling.builders.hooks.plugin.interface import BuildHookInterface +from packaging.tags import Tag + +# During env initialization the PYTHONPATH doesn't include our helpers, so +# give it some help: +syspath.append(dirname(__file__)) +from helpers.v8_build import build_v8, clean_v8, get_platform_tag # noqa: E402 + + +class V8BuildHook(BuildHookInterface[BuilderConfig]): + def clean(self, versions: Iterable[str]) -> None: + del versions + + clean_v8("src/py_mini_racer") + + def initialize(self, version: str, build_data): + del version + + artifacts = build_v8(out_path="src/py_mini_racer") + + build_data.setdefault("force_include", {}).update(artifacts) + + # From https://stackoverflow.com/questions/76450587/python-wheel-that-includes-shared-library-is-built-as-pure-python-platform-indep + # We have to tell Hatch that we're building a non-pure-Python wheel, even + # though there are no extension modules (instead, there is native code in a + # dynamic-link library in the package): + build_data["pure_python"] = False + + # Because we aren't building an extension module (just pure Python and a + # Python-independent DLL), any single wheel we create is broadly compatible + # with different Python interpreters. Just mark py3 with any ABI: + tag = Tag("py3", "none", get_platform_tag()) + + build_data["tag"] = str(tag) diff --git a/py_mini_racer/extension/__init__.py b/helpers/__init__.py similarity index 100% rename from py_mini_racer/extension/__init__.py rename to helpers/__init__.py diff --git a/helpers/babel.py b/helpers/babel.py index 7cd76081..8fdf7038 100644 --- a/helpers/babel.py +++ b/helpers/babel.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python """ Transform the input stream using babel.transform """ + import os import sys @@ -7,12 +7,14 @@ def babel_transform(es_string): - """ Transform the provided string using babel.transform """ + """Transform the provided string using babel.transform""" - path_to_babel = os.path.join(os.path.dirname(__file__), '..', 'tests', - 'fixtures', 'babel.js') + path_to_babel = os.path.join( + os.path.dirname(__file__), "..", "tests", "fixtures", "babel.js" + ) - babel_source = open(path_to_babel, "r").read() + with open(path_to_babel) as f: + babel_source = f.read() # Initializes PyMiniRacer ctx = py_mini_racer.MiniRacer() @@ -22,12 +24,10 @@ def babel_transform(es_string): # Transform stuff :) val = "babel.transform(`%s`)['code']" % es_string - res = ctx.eval(val) - return res - + return ctx.eval(val) -if __name__ == '__main__': +if __name__ == "__main__": if len(sys.argv) != 1: name = sys.argv[0] sys.stderr.write("Usage: cat es6file.js | %s\n" % name) @@ -38,4 +38,4 @@ def babel_transform(es_string): res = babel_transform(es6_data) - print(res) + sys.stdout.write(res) diff --git a/helpers/build_package.py b/helpers/build_package.py deleted file mode 100644 index 307d45f9..00000000 --- a/helpers/build_package.py +++ /dev/null @@ -1,56 +0,0 @@ -import glob -import os -import shutil -import sys - -from setuptools.build_meta import ( - build_sdist as setuptools_build_sdist, - build_wheel as setuptools_build_wheel, - get_requires_for_build_sdist, - get_requires_for_build_wheel, - prepare_metadata_for_build_wheel, -) -from v8_build import build_v8 - - -def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): - config_settings = config_settings or {} - # Our wheel is compatible both with python 2 & python 3 - config_settings["--global-option"] = options = ["--python-tag", "py2.py3"] - # Clean previous version of the lib - for pattern in ("py_mini_racer/*.so", "py_mini_racer/*.dylib", "py_mini_racer/*.dll"): - for filename in glob.glob(pattern): - print("removing {}".format(filename)) - os.unlink(filename) - # Build V8 - build_v8("py_mini_racer_shared_lib") - # Build the wheel - if os.name == "posix" and sys.platform == "darwin": - shutil.copyfile("py_mini_racer/extension/out/libmini_racer.dylib", "py_mini_racer/libmini_racer.dylib") - options.extend(["--plat-name", "macosx_10_10_x86_64"]) - elif sys.platform == "win32": - shutil.copyfile("py_mini_racer/extension/out/mini_racer.dll", "py_mini_racer/mini_racer.dll") - options.extend(["--plat-name", "win_amd64"]) - else: - shutil.copyfile("py_mini_racer/extension/out/libmini_racer.so", "py_mini_racer/libmini_racer.glibc.so") - options.extend(["--plat-name", "manylinux1_x86_64"]) - return setuptools_build_wheel(wheel_directory, config_settings=config_settings, metadata_directory=metadata_directory) - - -def build_sdist(sdist_directory, config_settings=None): - return setuptools_build_sdist(sdist_directory) - - -__all__ = [ - "get_requires_for_build_wheel", - "get_requires_for_build_sdist", - "prepare_metadata_for_build_wheel", - "build_wheel", - "build_sdist", -] - -if __name__ == "__main__": - if sys.argv[1] == "wheel": - build_wheel(sys.argv[2]) - else: - build_sdist(sys.argv[2]) diff --git a/helpers/no-aarch64-linux-gnu-target.patch b/helpers/no-aarch64-linux-gnu-target.patch new file mode 100644 index 00000000..22040f24 --- /dev/null +++ b/helpers/no-aarch64-linux-gnu-target.patch @@ -0,0 +1,13 @@ +--- build/config/compiler/BUILD.gn.orig 2024-03-04 23:25:24.939930259 -0500 ++++ build/config/compiler/BUILD.gn 2024-03-04 23:25:54.208173956 -0500 +@@ -1191,8 +1191,8 @@ + } else if (current_cpu == "arm64") { + if (is_clang && !is_android && !is_nacl && !is_fuchsia && + !(is_chromeos_lacros && is_chromeos_device)) { +- cflags += [ "--target=aarch64-linux-gnu" ] +- ldflags += [ "--target=aarch64-linux-gnu" ] ++ # cflags += [ "--target=aarch64-linux-gnu" ] ++ # ldflags += [ "--target=aarch64-linux-gnu" ] + } + } else if (current_cpu == "mipsel" && !is_nacl) { + ldflags += [ "-Wl,--hash-style=sysv" ] diff --git a/helpers/split-threshold-for-reg-with-hint.patch b/helpers/split-threshold-for-reg-with-hint.patch new file mode 100644 index 00000000..98679e69 --- /dev/null +++ b/helpers/split-threshold-for-reg-with-hint.patch @@ -0,0 +1,11 @@ +--- build/config/compiler/BUILD.gn.orig 2024-03-04 19:20:42.461477378 -0500 ++++ build/config/compiler/BUILD.gn 2024-03-04 19:20:49.953492009 -0500 +@@ -621,7 +621,7 @@ + # TODO(crbug.com/1488374): This causes binary size growth and potentially + # other problems. + # TODO(crbug.com/1491036): This isn't supported by Cronet's mainline llvm version. +- if (default_toolchain != "//build/toolchain/cros:target" && ++ if (false && default_toolchain != "//build/toolchain/cros:target" && + !llvm_android_mainline) { + cflags += [ + "-mllvm", diff --git a/helpers/v8_build.py b/helpers/v8_build.py index 17b7f55e..e9629bfd 100644 --- a/helpers/v8_build.py +++ b/helpers/v8_build.py @@ -1,319 +1,443 @@ -# -*- coding: utf-8 -*-" -import argparse -import errno -import glob -import json -import logging -import os -import os.path -import subprocess -import sys -from contextlib import contextmanager - -logging.basicConfig() -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) -ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -V8_VERSION = "branch-heads/8.9" +from argparse import ArgumentParser +from errno import EEXIST +from functools import lru_cache +from logging import DEBUG, basicConfig, getLogger +from os import environ, makedirs, pathsep, remove, symlink, unlink +from os.path import abspath, dirname, exists, isdir, isfile +from os.path import join as pathjoin +from platform import machine +from re import match +from shlex import join as shlexjoin +from shutil import copyfile, rmtree +from subprocess import check_call +from sys import executable, platform + +from packaging.tags import platform_tags + +basicConfig() +LOGGER = getLogger(__name__) +LOGGER.setLevel(DEBUG) +ROOT_DIR = dirname(abspath(__file__)) +V8_VERSION = "branch-heads/12.2" def local_path(path="."): - """ Return path relative to this file - """ - return os.path.abspath(os.path.join(ROOT_DIR, path)) + """Return path relative to this file.""" + return abspath(pathjoin(ROOT_DIR, path)) -PATCHES_PATH = local_path('../patches') +@lru_cache(maxsize=None) +def is_win(): + return platform.startswith("win") -def call(cmd): - LOGGER.debug("Calling: '%s' from working directory %s", cmd, os.getcwd()) - current_env = os.environ - depot_tools_env = os.pathsep.join([local_path("../py_mini_racer/extension/depot_tools"), os.environ['PATH']]) - current_env['PATH'] = depot_tools_env - current_env['DEPOT_TOOLS_WIN_TOOLCHAIN'] = '0' - return subprocess.check_call(cmd, shell=True, env=current_env) +@lru_cache(maxsize=None) +def is_linux(): + return platform == "linux" -@contextmanager -def chdir(new_path, make=False): - old_path = os.getcwd() +@lru_cache(maxsize=None) +def is_mac(): + return platform == "darwin" - if make is True: - try: - os.mkdir(new_path) - except OSError: - pass - try: - yield os.chdir(new_path) - finally: - os.chdir(old_path) +class UnknownArchError(RuntimeError): + def __init__(self, arch): + super().__init__(f"Unknown arch {arch!r}") -def install_depot_tools(): - if not os.path.isdir(local_path("../py_mini_racer/extension/depot_tools")): - LOGGER.debug("Cloning depot tools") - call("git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git {}".format(local_path("../py_mini_racer/extension/depot_tools"))) - else: - LOGGER.debug("Using already cloned depot tools") +@lru_cache(maxsize=None) +def get_v8_target_cpu(): + m = machine().lower() + if m in ("arm64", "aarch64"): + return "arm64" + if m == "arm": + return "arm" + if (not m) or (match("(x|i[3-6]?)86$", m) is not None): + return "ia32" + if m in ("x86_64", "amd64"): + return "x64" + if m == "s390x": + return "s390x" + if m == "ppc64": + return "ppc64" + raise UnknownArchError(m) -def prepare_workdir(): - directories = ["build", "build_overrides", "buildtools", "testing", - "third_party", "tools"] - with chdir(local_path("../py_mini_racer/extension")): - for item in directories: - if not os.path.exists(item): - symlink_force(os.path.join("v8", item), item) +@lru_cache(maxsize=None) +def get_dll_filename(): + if is_mac(): + return "libmini_racer.dylib" -def ensure_v8_src(revision): - """ Ensure that v8 src are presents and up-to-date - """ - path = local_path("../py_mini_racer/extension") + if is_win(): + return "mini_racer.dll" - if not os.path.isfile(local_path("../py_mini_racer/extension/.gclient")): - fetch_v8(path) - else: - update_v8(path) + return "libmini_racer.so" - checkout_v8_version(local_path("../py_mini_racer/extension/v8"), revision) - dependencies_sync(path) +@lru_cache(maxsize=None) +def get_data_files_list(): + """List the files which v8 builds and then needs at runtime.""" -def fetch_v8(path): - """ Fetch v8 - """ - with chdir(os.path.abspath(path), make=True): - call("fetch --nohooks v8") + return ( + # V8 i18n data: + "icudtl.dat", + # V8 fast-startup snapshot; a dump of the heap after loading built-in JS + # modules: + "snapshot_blob.bin", + # And obviously, the V8 build itself: + get_dll_filename(), + ) -def update_v8(path): - """ Update v8 repository - """ - with chdir(path): - call("gclient fetch") +@lru_cache(maxsize=None) +def is_musl(): + # Alpine uses musl for libc, instead of glibc. This breaks many assumptions in the + # V8 build, so we have to reconfigure various things when running on musl libc. + # Determining if we're on musl (or Alpine) is surprisingly complicated; the best + # way seems to be to check the dynamic linker ependencies of the current Python + # executable for musl! packaging.tags.platform_tags (which is used by pip et al) + # does this for us: + return any("musllinux" in t for t in platform_tags()) -def checkout_v8_version(path, revision): - """ Ensure that we have the right version +@lru_cache(maxsize=None) +def get_platform_tag(): + """Return a pip platform tag indicating compatibility of the mini_racer binary. + + See https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/. """ - with chdir(path): - call("git checkout {} -- .".format(revision)) + if is_mac(): + # pip seems finicky about platform tags with larger macos versions, so just + # tell arm64 is 11.0 and everything is is 10.9: + if get_v8_target_cpu() == "arm64": + return "macosx_11_0_arm64" -def dependencies_sync(path): - """ Sync v8 build dependencies - """ - with chdir(path): - call("gclient sync") + return "macosx_10_9_x86_64" + # return the first, meaning the most-specific, platform tag: + return next(platform_tags()) -def run_hooks(path): - """ Run v8 build hooks - """ - with chdir(path): - call("gclient runhooks") - - -def gen_makefiles(build_path, no_sysroot=False): - with chdir(local_path("../py_mini_racer/extension")): - build_path = local_path(build_path) - if not os.path.exists(build_path): - os.makedirs(build_path) - LOGGER.debug("Writing args.gn in %s", build_path) - with open(os.path.join(build_path, "args.gn"), "w") as f: - opts = { - "proprietary_codecs": "false", - "toolkit_views": "false", - "use_aura": "false", - "use_dbus": "false", - "use_gio": "false", - "use_glib": "false", - "use_ozone": "false", - "use_udev": "false", - "is_desktop_linux": "false", - - "is_cfi": "false", - "is_debug": "false", - "is_component_build": "false", - - "symbol_level": "0", - "strip_debug_info": "true", - "treat_warnings_as_errors": "true", - - "v8_monolithic": "false", - "v8_use_external_startup_data": "false", - "v8_enable_i18n_support": "false", - - "v8_untrusted_code_mitigations": "false", - # See https://v8.dev/docs/untrusted-code-mitigations - - # See cc_wrapper - "clang_use_chrome_plugins": "false", - } - if no_sysroot: - opts.update({ - "treat_warnings_as_errors": "false", - "use_sysroot": "false", - "clang_use_chrome_plugins": "false", - "clang_base_path": "\"/usr\"", - "use_custom_libcxx": "false", - "use_gold": "true", - "use_lld": "false", - }) - sccache = os.environ.get('SCCACHE') - if sccache is not None: - opts["cc_wrapper"] = json.dumps(sccache) - f.write("# This file is auto generated by v8_build.py") - f.write("\n".join("{}={}".format(a, b) for (a, b) in opts.items())) - f.write("\n") - extra_args = os.getenv("GN_ARGS") - if extra_args: - f.write("\n".join(extra_args.split())) - f.write("\n") - call("gn gen {}".format(local_path(build_path))) - - -def make(build_path, target, cmd_prefix=""): - """ Create a release of v8 - """ - with chdir(local_path("../py_mini_racer/extension")): - call("{} ninja -vv -C {} {}".format(cmd_prefix, local_path(build_path), target)) +@lru_cache(maxsize=None) +def get_workspace_path(): + return local_path(pathjoin("..", "v8_workspace")) -def patch_v8(): - """ Apply patch on v8 - """ - path = local_path("../py_mini_racer/extension/v8") - patches_paths = PATCHES_PATH - apply_patches(path, patches_paths) +@lru_cache(maxsize=None) +def get_depot_tools_path(): + return pathjoin(get_workspace_path(), "depot_tools") -def symlink_force(target, link_name): - LOGGER.debug("Creating symlink to %s on %s", target, link_name) - if sys.platform == "win32": - call(["mklink", "/d", os.path.abspath(link_name), os.path.abspath(target)]) + +@lru_cache(maxsize=None) +def get_v8_path(): + return pathjoin(get_workspace_path(), "v8") + + +def unlink_if_exists(f): + if exists(f): + unlink(f) + + +def run(*args, cwd, depot_tools_first=True): + LOGGER.debug("Calling: '%s' from working directory %s", shlexjoin(args), cwd) + env = environ.copy() + + if depot_tools_first: + env["PATH"] = pathsep.join([get_depot_tools_path(), environ["PATH"]]) + else: + env["PATH"] = pathsep.join([environ["PATH"], get_depot_tools_path()]) + + env["DEPOT_TOOLS_WIN_TOOLCHAIN"] = "0" + # vpython is V8's Python environment manager; it downloads Python binaries + # dynamically. This doesn't work on Alpine (because it downloads a glibc binary, + # but needs a musl binary), so let's just disable it on all environments: + env["VPYTHON_BYPASS"] = "manually managed python not supported by chrome operations" + # Goma is a remote build system which we aren't using. depot_tools/autoninja.py + # tries to run the goma client, which is checked into depot_tools as a glibc binary. + # This fails on musl (on Alpine), so let's just disable the thing: + env["GOMA_DISABLED"] = "1" + + return check_call(args, env=env, cwd=cwd) + + +def ensure_depot_tools(): + if isdir(get_depot_tools_path()): + LOGGER.debug("Using already cloned depot tools") + return + + LOGGER.debug("Cloning depot tools") + makedirs(f"{get_workspace_path()}", exist_ok=True) + run( + "git", + "clone", + "https://chromium.googlesource.com/chromium/tools/depot_tools.git", + cwd=get_workspace_path(), + ) + + # depot_tools will auto-update when we run various commands. This creates extra + # dependencies, e.g., on goma (which has trouble running on Alpine due to musl). + # We just created a fresh single-use depot_tools checkout. There is no reason to + # update it, so let's just disable that functionality: + open(pathjoin(get_depot_tools_path(), ".disable_auto_update"), "w").close() + + if is_win(): + # Create git.bit and maybe other shortcuts used by the Windows V8 build tools: + run( + pathjoin(get_depot_tools_path(), "bootstrap", "win_tools.bat"), + cwd=get_depot_tools_path(), + ) + + +def ensure_v8_src(revision): + """Ensure that v8 src are present and up-to-date.""" + + # Note that we run fetch.py and gclient.py directly instead of the bash+batch + # wrappers so that we use the correct virtualized Python. + if not isfile(pathjoin(get_workspace_path(), ".gclient")): + makedirs(get_workspace_path(), exist_ok=True) + run( + executable, + pathjoin(get_depot_tools_path(), "fetch.py"), + "--nohooks", + "v8", + cwd=get_workspace_path(), + ) + + run( + executable, + pathjoin(get_depot_tools_path(), "gclient.py"), + "sync", + "--revision", + f"v8@{revision}", + cwd=get_workspace_path(), + ) + + ensure_symlink( + local_path(pathjoin("..", "src", "v8_py_frontend")), + pathjoin(get_v8_path(), "custom_deps", "mini_racer"), + ) + + +def apply_patch(path, patch_filename): + applied_patches_filename = local_path(".applied_patches") + + if not exists(applied_patches_filename): + open(applied_patches_filename, "w").close() + + with open(applied_patches_filename, "r+") as f: + applied_patches = set(f.read().splitlines()) + + if patch_filename in applied_patches: + return + + run( + "patch", + "-p0", + "-i", + patch_filename, + cwd=path, + ) + + f.write(patch_filename + "\n") + + +def run_build(build_dir): + """Run the actual v8 build.""" + + # As explained in the design principles in ARCHITECTURE.md, we want to reduce the + # surface area of the V8 build system which PyMiniRacer depends upon. To accomodate + # that goal, we run with as few non-default build options as possible. + + # The standard developer guide for V8 suggests we use the v8/tools/dev/v8gen.py + # tool to both generate args.gn, and run gn to generate Ninja build files. + + # Unfortunately, v8/tools/dev/v8gen.py is unhappy about being run on non-amd64 + # architecture (it seems to think we're always cross-compiling from amd64). Thus + # we reproduce what it does, which for our simple case devolves to just generating + # args.gn with minimal arguments, and running "gn gen ...". + + opts = { + # These following settings are based those found for the "x64.release" + # configuration. This can be verified by running: + # tools/mb/mb.py lookup -b x64.release -m developer_default + # ... from within the v8 directory. + "dcheck_always_on": "false", + "is_debug": "false", + "target_cpu": f'"{get_v8_target_cpu()}"', + "v8_target_cpu": f'"{get_v8_target_cpu()}"', + # We sneak our C++ frontend into V8 as a symlinked "custom_dep" so + # that we can reuse the V8 build system to make our dynamic link + # library: + "v8_custom_deps": '"//custom_deps/mini_racer"', + } + + if (is_linux() and get_v8_target_cpu() == "arm64") or is_musl(): + # The V8 build process includes its own clang binary, but not for aarch64 on + # Linux glibc, and not for Alpine (musl) at all. + # Per tools/dev/gm.py, use the the system clang instead: + opts["clang_base_path"] = '"/usr"' + + opts["clang_use_chrome_plugins"] = "false" + # Because we use a different clang, more warnings pop up. Ignore them: + opts["treat_warnings_as_errors"] = "false" + + # V8 currently uses a clang flag -split-threshold-for-reg-with-hint=0 which + # doen't exist on Alpine's mainline llvm yet. Disable it: + if is_musl(): + apply_patch( + get_v8_path(), + local_path("split-threshold-for-reg-with-hint.patch"), + ) + + if is_musl() and get_v8_target_cpu() == "arm64": + # The V8 build unhelpfully sets the clang flag --target=aarch64-linux-gnu + # on musl. The --target flag is useful when we're cross-compiling (which we're + # not) and we aren't on aarch64-linux-gnu, we're actually on what clang calls + # aarch64-alpine-linux-musl. + # This patch just disables the spurious cflags and ldflags: + apply_patch( + get_v8_path(), + local_path("no-aarch64-linux-gnu-target.patch"), + ) + + if is_musl(): + # On various OSes, the V8 build process brings in a whole copy of the sysroot + # (/usr/include, /usr/lib, etc). Unfortunately on Alpine it tries to use a + # Debian sysroot, which doesn't work. Disable it: + opts["use_sysroot"] = "false" + + # V8 includes its own libc++ whose headers don't seem to work on Alpine: + opts["use_custom_libcxx"] = "false" + + # We optionally use SCCACHE to speed up builds (or restart them on failure): + sccache_path = environ.get("SCCACHE_PATH") + if sccache_path is not None: + opts["cc_wrapper"] = f'"{sccache_path}"' + + makedirs(build_dir, exist_ok=True) + + with open(pathjoin(build_dir, "args.gn"), "w") as f: + f.write("# This file is auto-generated by v8_build.py") + f.write("\n".join(f"{n}={v}" for n, v in opts.items())) + f.write("\n") + + # Now generate Ninja build files: + if is_musl(): + # depot_tools doesn't include a musl-compatible GN, so use the system one: + gn_bin = ("/usr/bin/gn",) else: - try: - os.symlink(target, link_name) - except OSError as e: - if e.errno == errno.EEXIST: - os.remove(link_name) - os.symlink(target, link_name) - else: - raise e - - -def fixup_libtinfo(dir): - dirs = ['/lib64', '/usr/lib64', '/lib', '/usr/lib'] - - v5_locs = ["{}/libtinfo.so.5".format(d) for d in dirs] - found_v5 = next((f for f in v5_locs if os.path.isfile(f)), None) - if found_v5 and os.stat(found_v5).st_size > 100: - return '' - - v6_locs = ["{}/libtinfo.so.6".format(d) for d in dirs] - found_v6 = next((f for f in v6_locs if os.path.isfile(f)), None) - if not found_v6: - return '' - - symlink_force(found_v6, os.path.join(dir, 'libtinfo.so.5')) - return "LD_LIBRARY_PATH='{}:{}'"\ - .format(dir, os.getenv('LD_LIBRARY_PATH', '')) - - -def apply_patches(path, patches_path): - with chdir(path): - - if not os.path.isfile('.applied_patches'): - open('.applied_patches', 'w').close() - - with open('.applied_patches', 'r+') as applied_patches_file: - applied_patches = set(applied_patches_file.read().splitlines()) - - for patch in glob.glob(os.path.join(patches_path, '*.patch')): - if patch not in applied_patches: - call("patch -p1 -N < {}".format(patch)) - applied_patches_file.write(patch + "\n") - - -def patch_sysroot(): - with chdir(local_path("../py_mini_racer/extension/v8/build/linux/debian_sid_amd64-sysroot")): - with open("usr/include/glob.h", "r") as f: - header = f.read() - s, e = header.split("sysroot-creator.sh.", 1) - LOGGER.debug("Patching sysroot /usr/include/glob.h") - with open("usr/include/glob.h", "w") as f: - f.write(s) - f.write("sysroot-creator.sh.") - f.write(""" -__asm__(".symver glob, glob@GLIBC_2.2.5"); -__asm__(".symver glob64, glob64@GLIBC_2.2.5"); - """) - LOGGER.debug("Patching sysroot /usr/include/string.h") - with open("usr/include/string.h", "a") as f: - f.write(""" -__asm__(".symver _sys_errlist, _sys_errlist@GLIBC_2.4"); -__asm__(".symver _sys_nerr, _sys_nerr@GLIBC_2.4"); -__asm__(".symver fmemopen, fmemopen@GLIBC_2.2.5"); -__asm__(".symver memcpy, memcpy@GLIBC_2.2.5"); -__asm__(".symver posix_spawn, posix_spawn@GLIBC_2.2.5"); -__asm__(".symver posix_spawnp, posix_spawnp@GLIBC_2.2.5"); -__asm__(".symver sys_errlist, sys_errlist@GLIBC_2.4"); -__asm__(".symver sys_nerr, sys_nerr@GLIBC_2.4"); - """) - with open("usr/include/math.h", "r") as f: - header = f.read() - s, e = header.split("sysroot-creator.sh.", 1) - LOGGER.debug("Patching sysroot /usr/include/math.h") - with open("usr/include/math.h", "w") as f: - f.write(s) - f.write("sysroot-creator.sh.") - f.write(""" -__asm__(".symver exp2f, exp2f@GLIBC_2.2.5"); -__asm__(".symver expf, expf@GLIBC_2.2.5"); -__asm__(".symver lgamma, lgamma@GLIBC_2.2.5"); -__asm__(".symver lgammaf, lgammaf@GLIBC_2.2.5"); -__asm__(".symver lgammal, lgammal@GLIBC_2.2.5"); -__asm__(".symver log2f, log2f@GLIBC_2.2.5"); -__asm__(".symver logf, logf@GLIBC_2.2.5"); -__asm__(".symver powf, powf@GLIBC_2.2.5"); - """) - - -def build_v8(target=None, build_path=None, revision=None, no_build=False, - no_sysroot=False, no_update=False): - if target is None: - target = "v8" - if build_path is None: - # Must be relative to local_path() - build_path = "../py_mini_racer/extension/out" - if revision is None: - revision = V8_VERSION - install_depot_tools() - if not no_update: + gn_bin = ( + executable, + pathjoin(get_depot_tools_path(), "gn.py"), + ) + + run( + *gn_bin, + "gen", + build_dir, + "--check", + cwd=get_v8_path(), + ) + + # Finally, actually do the build: + if is_musl(): + # depot_tools doesn't include a musl-compatible ninja, so use the system one: + ninja_bin = ("/usr/bin/ninja",) + else: + ninja_bin = ( + executable, + pathjoin(get_depot_tools_path(), "ninja.py"), + ) + + run( + *ninja_bin, + # "-vv", # this is so spammy GitHub Actions struggles to show all the output + "-C", + build_dir, + pathjoin("custom_deps", "mini_racer"), + cwd=get_v8_path(), + ) + + +def ensure_symlink(target, link_name): + LOGGER.debug("Creating symlink to %s on %s", target, link_name) + try: + symlink(target, link_name) + except OSError as e: + if e.errno == EEXIST: + remove(link_name) + symlink(target, link_name) + else: + raise + + +def build_v8( + out_path, + *, + revision=None, + fetch_only=False, + skip_fetch=False, +): + revision = revision or V8_VERSION + + ensure_depot_tools() + + if not skip_fetch: ensure_v8_src(revision) - patch_v8() - if not no_sysroot and sys.platform.startswith("linux"): - patch_sysroot() - prepare_workdir() - if not no_build: - checkout_path = local_path("../py_mini_racer/extension/v8") - cmd_prefix = fixup_libtinfo(checkout_path) - gen_makefiles(build_path, no_sysroot=no_sysroot) - make(build_path, target, cmd_prefix) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("--target", default="v8", help="Ninja target") - parser.add_argument("--build-path", default="../py_mini_racer/extension/out", help="Build destination directory (relative to the current path)") + + if fetch_only: + return + + build_dir = pathjoin(get_v8_path(), "out.gn", "build") + + run_build(build_dir) + + # Fish out the build artifacts: + makedirs(out_path, exist_ok=True) + + # Create a map of actual files to in-package filenames, for Hatch to use + # when building the wheel: + artifacts = {} + + for f in get_data_files_list(): + src = pathjoin(build_dir, f) + dst = pathjoin(out_path, f) + + LOGGER.debug("Copying build artifact %s to %s", src, dst) + unlink_if_exists(dst) + copyfile(src, dst) + + artifacts[dst] = pathjoin("py_mini_racer", f) + + LOGGER.debug("Build complete!") + + return artifacts + + +def clean_v8(out_path): + for f in get_data_files_list(): + unlink_if_exists(pathjoin(out_path, f)) + + rmtree(get_workspace_path(), ignore_errors=True) + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument( + "--out-path", + default=pathjoin("src", "py_mini_racer"), + help="Build destination directory", + ) parser.add_argument("--v8-revision", default=V8_VERSION) - parser.add_argument("--no-build", action="store_true", help="Only prepare workdir") - parser.add_argument("--no-update", action="store_true", help="Do not update the workdir") - parser.add_argument("--no-sysroot", action="store_true", help="Do not use the V8 build sysroot") + parser.add_argument("--fetch-only", action="store_true", help="Only fetch V8") + parser.add_argument("--skip-fetch", action="store_true", help="Do not fetch V8") args = parser.parse_args() - build_v8(target=args.target, build_path=args.build_path, revision=args.v8_revision, - no_build=args.no_build, no_update=args.no_update, no_sysroot=args.no_sysroot) + build_v8( + out_path=args.out_path, + revision=args.v8_revision, + fetch_only=args.fetch_only, + skip_fetch=args.skip_fetch, + ) diff --git a/helpers/wheel_pymalloc.py b/helpers/wheel_pymalloc.py deleted file mode 100644 index 9d5ab063..00000000 --- a/helpers/wheel_pymalloc.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Script to add without pymalloc version of the wheel""" - -import os -import re -import sys - -from auditwheel.wheeltools import InWheelCtx, _dist_info_dir -from wheel import pkginfo - - -def get_filenames(directory): - """Get all the file to copy""" - for filename in os.listdir(directory): - if re.search(r"cp\d{2}mu?-manylinux1_\S+\.whl", filename): - yield filename - - -def copy_file(filename, destination): - """Copy the file and put the correct tag""" - - print("Updating file %s" % filename) - out_dir = os.path.abspath(destination) - - tags = filename[:-4].split("-") - - tags[-2] = tags[-2].replace("m", "") - - new_name = "-".join(tags) + ".whl" - wheel_flag = "-".join(tags[2:]) - - with InWheelCtx(os.path.join(destination, filename)) as ctx: - info_fname = os.path.join(_dist_info_dir(ctx.path), 'WHEEL') - infos = pkginfo.read_pkg_info(info_fname) - print("Current Tags: ", ", ".join([v for k, v in infos.items() - if k == "Tag"])) - print("Adding Tag", wheel_flag) - del infos['Tag'] - infos.add_header('Tag', wheel_flag) - pkginfo.write_pkg_info(info_fname, infos) - - ctx.out_wheel = os.path.join(out_dir, new_name) - - print("Saving new wheel into %s" % ctx.out_wheel) - - -def main(): - if len(sys.argv) == 2: - directory = sys.argv[1] - else: - directory = "dist" - for filename in get_filenames(directory): - copy_file(filename, directory) - - -if __name__ == "__main__": - main() diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..26f3b95a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,33 @@ +site_name: PyMiniRacer +site_url: https://sqreen.github.io/PyMiniRacer +theme: + name: material + custom_dir: data + logo: py_mini_racer.png +nav: + - Home: index.md + - API Reference: api.md + - Contributing: contributing.md + - Architecture: architecture.md + - Credits: authors.md + - History: history.md + +plugins: + - search + - mkdocstrings: + handlers: + python: + paths: [src] # search packages in the src folder + +markdown_extensions: + - admonition + - sane_lists + - toc: + permalink: true + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences diff --git a/py_mini_racer.png b/py_mini_racer.png new file mode 100644 index 0000000000000000000000000000000000000000..7654b21185f0924b803074d74a92aacbb81c00b4 GIT binary patch literal 78857 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 is_unicode(u'foo') - True - >>> is_unicode(u'✌') - True - >>> is_unicode(b'foo') - False - >>> is_unicode(42) - False - >>> is_unicode(('abc',)) - False - """ - return isinstance(value, UNICODE_TYPE) - - -def _build_ext_handle(): - - if EXTENSION_PATH is None or not os.path.exists(EXTENSION_PATH): - raise RuntimeError("Native library not available at {}".format(EXTENSION_PATH)) - - _ext_handle = ctypes.CDLL(EXTENSION_PATH) - - _ext_handle.mr_init_context.argtypes = [ctypes.c_char_p] - _ext_handle.mr_init_context.restype = ctypes.c_void_p - - _ext_handle.mr_eval_context.argtypes = [ - ctypes.c_void_p, - ctypes.c_char_p, - ctypes.c_int, - ctypes.c_ulong, - ctypes.c_size_t] - _ext_handle.mr_eval_context.restype = ctypes.POINTER(MiniRacerValueStruct) - - _ext_handle.mr_free_value.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - - _ext_handle.mr_free_context.argtypes = [ctypes.c_void_p] - - _ext_handle.mr_heap_stats.argtypes = [ctypes.c_void_p] - _ext_handle.mr_heap_stats.restype = ctypes.POINTER(MiniRacerValueStruct) - - _ext_handle.mr_low_memory_notification.argtypes = [ctypes.c_void_p] - - _ext_handle.mr_heap_snapshot.argtypes = [ctypes.c_void_p] - _ext_handle.mr_heap_snapshot.restype = ctypes.POINTER(MiniRacerValueStruct) - - _ext_handle.mr_set_soft_memory_limit.argtypes = [ctypes.c_void_p, ctypes.c_size_t] - _ext_handle.mr_set_soft_memory_limit.restype = None - - _ext_handle.mr_soft_memory_limit_reached.argtypes = [ctypes.c_void_p] - _ext_handle.mr_soft_memory_limit_reached.restype = ctypes.c_bool - - _ext_handle.mr_v8_version.restype = ctypes.c_char_p - - return _ext_handle - - -class MiniRacer(object): - """ - MiniRacer evaluates JavaScript code using a V8 isolate. - - :cvar json_impl: JSON module used by helper methods default is :py:mod:`json` - :cvar v8_flags: Flags used for V8 initialization - :vartype v8_flags: class attribute list of str - """ - - json_impl = json - v8_flags = ["--single-threaded"] - ext = None - - def __init__(self): - if self.__class__.ext is None: - self.__class__.ext = _build_ext_handle() - - self.ctx = self.ext.mr_init_context(" ".join(self.v8_flags).encode("utf-8")) - self.lock = threading.Lock() - - @property - def v8_version(self): - """Return the V8 version string.""" - return UNICODE_TYPE(self.ext.mr_v8_version()) - - def eval(self, code, timeout=None, max_memory=None): - # type: (str, Optional[int], Optional[int]) -> Any - """Evaluate JavaScript code in the V8 isolate. - - Side effects from the JavaScript evaluation is persisted inside a context - (meaning variables set are kept for the next evaluations). - - The JavaScript value returned by the last expression in `code` is converted - to a Python value and returned by this method. Only primitive types are - supported (numbers, strings, buffers...). Use the :py:meth:`.execute` method to return - more complex types such as arrays or objects. - - The evaluation can be interrupted by an exception for several reasons: a limit - was reached, the code could not be parsed, a returned value could not be - converted to a Python value. - - :param code: JavaScript code - :param timeout: number of milliseconds after which the execution is interrupted - :param max_memory: hard memory limit after which the execution is interrupted - """ - - if is_unicode(code): - code = code.encode("utf8") - - with self.lock: - res = self.ext.mr_eval_context(self.ctx, - code, - len(code), - ctypes.c_ulong(timeout or 0), - ctypes.c_size_t(max_memory or 0)) - if not res: - raise JSConversionException() - - return MiniRacerValue(self, res).to_python() - - def execute(self, expr, timeout=None, max_memory=None): - # type: (str, Optional[int], Optional[int]) -> Any - """Helper to evaluate a JavaScript expression and return composite types. - - Returned value is serialized to JSON inside the V8 isolate and deserialized - using :py:attr:`.json_impl`. - - :param expr: JavaScript expression - :param timeout: number of milliseconds after which the execution is interrupted - :param max_memory: hard memory limit after which the execution is interrupted - """ - wrapped_expr = u"JSON.stringify((function(){return (%s)})())" % expr - ret = self.eval(wrapped_expr, timeout=timeout, max_memory=max_memory) - if not is_unicode(ret): - raise ValueError(u"Unexpected return value type {}".format(type(ret))) - return self.json_impl.loads(ret) - - def call(self, expr, *args, **kwargs): - """Helper to call a JavaScript function and return compositve types. - - The `expr` argument refers to a JavaScript function in the current V8 - isolate context. Further positional arguments are serialized using the JSON - implementation :py:attr:`.json_impl` and passed to the JavaScript function - as arguments. - - Returned value is serialized to JSON inside the V8 isolate and deserialized - using :py:attr:`.json_impl`. - - :param str expr: JavaScript expression referring to a function - :param encoder: Custom JSON encoder - :type encoder: JSONEncoder or None - :param int timeout: number of milliseconds after which the execution is interrupted - :param int max_memory: hard memory limit after which the execution is interrupted - """ - - encoder = kwargs.get('encoder', None) - timeout = kwargs.get('timeout', None) - max_memory = kwargs.get('max_memory', None) - - json_args = self.json_impl.dumps(args, separators=(',', ':'), cls=encoder) - js = u"{expr}.apply(this, {json_args})".format(expr=expr, json_args=json_args) - return self.execute(js, timeout=timeout, max_memory=max_memory) - - def set_soft_memory_limit(self, limit): - # type: (int) -> None - """Set a soft memory limit on this V8 isolate. - - The Garbage Collection will use a more aggressive strategy when - the soft limit is reached but the execution will not be stopped. - - :param int limit: memory limit in bytes or 0 to reset the limit - """ - self.ext.mr_set_soft_memory_limit(self.ctx, limit) - - def was_soft_memory_limit_reached(self): - # type: () -> bool - """Return true if the soft memory limit was reached on the V8 isolate.""" - return self.ext.mr_soft_memory_limit_reached(self.ctx) - - def low_memory_notification(self): - """Ask the V8 isolate to collect memory more aggressively.""" - self.ext.mr_low_memory_notification(self.ctx) - - def heap_stats(self): - """Return the V8 isolate heap statistics.""" - - with self.lock: - res = self.ext.mr_heap_stats(self.ctx) - - if not res: - return { - u"total_physical_size": 0, - u"used_heap_size": 0, - u"total_heap_size": 0, - u"total_heap_size_executable": 0, - u"heap_size_limit": 0 - } - - return self.json_impl.loads(MiniRacerValue(self, res).to_python()) - - def heap_snapshot(self): - """Return a snapshot of the V8 isolate heap.""" - - with self.lock: - res = self.ext.mr_heap_snapshot(self.ctx) - - return MiniRacerValue(self, res).to_python() - - def _free(self, res): - self.ext.mr_free_value(self.ctx, res) - - def __del__(self): - self.ext.mr_free_context(getattr(self, "ctx", None)) - - -# Compatibility with versions 0.4 & 0.5 -StrictMiniRacer = MiniRacer - - -class MiniRacerTypes(object): - """MiniRacer types identifier - - Note: it needs to be coherent with mini_racer_extension.cc. - """ - - invalid = 0 - null = 1 - bool = 2 - integer = 3 - double = 4 - str_utf8 = 5 - array = 6 # deprecated - hash = 7 # deprecated - date = 8 - symbol = 9 - object = 10 - - function = 100 - shared_array_buffer = 101 - array_buffer = 102 - - execute_exception = 200 - parse_exception = 201 - oom_exception = 202 - timeout_exception = 203 - - -class MiniRacerValueStruct(ctypes.Structure): - _fields_ = [("value", ctypes.c_void_p), # value is 8 bytes, works only for 64bit systems - ("type", ctypes.c_int), - ("len", ctypes.c_size_t)] - - -class ArrayBufferByte(ctypes.Structure): - # Cannot use c_ubyte directly because it uses = (3, 9): + # resources.as_file was added in Python 3.9 + resource_path = resources.files("py_mini_racer") / filename + + context_manager = resources.as_file(resource_path) + else: + # now-deprecated API for Pythons older than 3.9 + context_manager = resources.path("py_mini_racer", filename) + + return str(exit_stack.enter_context(context_manager)) + + +def _check_path(path): + if path is None or not exists(path): + raise LibNotFoundError(path) + + +@contextmanager +def _open_dll(): + dll_filename = _get_lib_filename("mini_racer") + + with ExitStack() as exit_stack: + # Find the dll and its external dependency files: + meipass = getattr(sys, "_MEIPASS", None) + if meipass is not None: + # We are running under PyInstaller + dll_path = pathjoin(meipass, dll_filename) + icu_data_path = pathjoin(meipass, _ICU_DATA_FILENAME) + snapshot_path = pathjoin(meipass, _SNAPSHOT_FILENAME) + else: + dll_path = _open_resource_file(dll_filename, exit_stack) + icu_data_path = _open_resource_file(_ICU_DATA_FILENAME, exit_stack) + snapshot_path = _open_resource_file(_SNAPSHOT_FILENAME, exit_stack) + + _check_path(dll_path) + _check_path(icu_data_path) + _check_path(snapshot_path) + + dll = _build_dll_handle(dll_path) + + dll.mr_init_v8( + " ".join(_V8_FLAGS).encode("utf-8"), + icu_data_path.encode("utf-8"), + snapshot_path.encode("utf-8"), + ) + + yield dll + + +_init_lock = Lock() +_dll_handle_context_manager = None +_dll_handle = None + + +def _get_dll_handle(): + global _dll_handle # noqa: PLW0603 + + with _init_lock: + if _dll_handle is None: + _dll_handle_context_manager = _open_dll() + _dll_handle = _dll_handle_context_manager.__enter__() + # Note: we never call _dll_handle_context_manager.__exit__() because it's + # designed as a singleton. But we could if we wanted to! + + return _dll_handle + + +class MiniRacer: + """ + MiniRacer evaluates JavaScript code using a V8 isolate. + + Attributes: + json_impl: JSON module used by helper methods default is + [json](https://docs.python.org/3/library/json.html) + """ + + json_impl: ClassVar[object] = json + + def __init__(self): + self._dll = _get_dll_handle() + self.ctx = self._dll.mr_init_context() + self.lock = Lock() + + @property + def v8_version(self): + """Return the V8 version string.""" + return str(self._dll.mr_v8_version()) + + def eval( # noqa: A003 + self, code: str, timeout: int | None = None, max_memory: int | None = None + ): + """Evaluate JavaScript code in the V8 isolate. + + Side effects from the JavaScript evaluation is persisted inside a context + (meaning variables set are kept for the next evaluations). + + The JavaScript value returned by the last expression in `code` is converted to + a Python value and returned by this method. Only primitive types are supported + (numbers, strings, buffers...). Use the + [py_mini_racer.py_mini_racer.MiniRacer.execute][] method to return more complex + types such as arrays or objects. + + The evaluation can be interrupted by an exception for several reasons: a limit + was reached, the code could not be parsed, a returned value could not be + converted to a Python value. + + Args: + code: JavaScript code + timeout: number of milliseconds after which the execution is interrupted + max_memory: hard memory limit after which the execution is interrupted + """ + + if isinstance(code, str): + code = code.encode("utf8") + + with self.lock: + res = self._dll.mr_eval_context( + self.ctx, + code, + len(code), + ctypes.c_ulong(timeout or 0), + ctypes.c_size_t(max_memory or 0), + ) + if not res: + raise JSConversionException + + return MiniRacerValue(self, res).to_python() + + def execute( + self, expr: str, timeout: int | None = None, max_memory: int | None = None + ): + """Helper to evaluate a JavaScript expression and return composite types. + + Returned value is serialized to JSON inside the V8 isolate and deserialized + using `json_impl`. + + Args: + expr: JavaScript expression + timeout: number of milliseconds after which the execution is interrupted + max_memory: hard memory limit after which the execution is interrupted + """ + + wrapped_expr = "JSON.stringify((function(){return (%s)})())" % expr + ret = self.eval(wrapped_expr, timeout=timeout, max_memory=max_memory) + if not isinstance(ret, str): + raise WrongReturnTypeException(type(ret)) + return self.json_impl.loads(ret) + + def call( + self, + expr: str, + *args, + encoder: JSONEncoder | None = None, + timeout: int | None = None, + max_memory: int | None = None, + ): + """Helper to call a JavaScript function and return compositve types. + + The `expr` argument refers to a JavaScript function in the current V8 + isolate context. Further positional arguments are serialized using the JSON + implementation `json_impl` and passed to the JavaScript function as arguments. + + Returned value is serialized to JSON inside the V8 isolate and deserialized + using `json_impl`. + + Args: + expr: JavaScript expression referring to a function + encoder: Custom JSON encoder + timeout: number of milliseconds after which the execution is + interrupted + max_memory: hard memory limit after which the execution is + interrupted + """ + + json_args = self.json_impl.dumps(args, separators=(",", ":"), cls=encoder) + js = f"{expr}.apply(this, {json_args})" + return self.execute(js, timeout=timeout, max_memory=max_memory) + + def set_soft_memory_limit(self, limit): + # type: (int) -> None + """Set a soft memory limit on this V8 isolate. + + The Garbage Collection will use a more aggressive strategy when + the soft limit is reached but the execution will not be stopped. + + :param int limit: memory limit in bytes or 0 to reset the limit + """ + self._dll.mr_set_soft_memory_limit(self.ctx, limit) + + def was_soft_memory_limit_reached(self): + # type: () -> bool + """Return true if the soft memory limit was reached on the V8 isolate.""" + return self._dll.mr_soft_memory_limit_reached(self.ctx) + + def low_memory_notification(self): + """Ask the V8 isolate to collect memory more aggressively.""" + self._dll.mr_low_memory_notification(self.ctx) + + def heap_stats(self): + """Return the V8 isolate heap statistics.""" + + with self.lock: + res = self._dll.mr_heap_stats(self.ctx) + + if not res: + return { + "total_physical_size": 0, + "used_heap_size": 0, + "total_heap_size": 0, + "total_heap_size_executable": 0, + "heap_size_limit": 0, + } + + return self.json_impl.loads(MiniRacerValue(self, res).to_python()) + + def heap_snapshot(self): + """Return a snapshot of the V8 isolate heap.""" + + with self.lock: + res = self._dll.mr_heap_snapshot(self.ctx) + + return MiniRacerValue(self, res).to_python() + + def _free(self, res): + self._dll.mr_free_value(self.ctx, res) + + def __del__(self): + if self._dll: + self._dll.mr_free_context(getattr(self, "ctx", None)) + + +# Compatibility with versions 0.4 & 0.5 +StrictMiniRacer = MiniRacer + + +class MiniRacerTypes: + """MiniRacer types identifier + + Note: it needs to be coherent with mini_racer.cc. + """ + + invalid = 0 + null = 1 + bool = 2 # noqa: A003 + integer = 3 + double = 4 + str_utf8 = 5 + # deprecated: + array = 6 + # deprecated: + hash = 7 # noqa: A003 + date = 8 + symbol = 9 + object = 10 # noqa: A003 + + function = 100 + shared_array_buffer = 101 + array_buffer = 102 + + execute_exception = 200 + parse_exception = 201 + oom_exception = 202 + timeout_exception = 203 + + +class MiniRacerValueStruct(ctypes.Structure): + _fields_: ClassVar[tuple[str, object]] = [ + ("value", ctypes.c_void_p), # value is 8 bytes, works only for 64bit systems + ("type", ctypes.c_int), + ("len", ctypes.c_size_t), + ] + + +class ArrayBufferByte(ctypes.Structure): + # Cannot use c_ubyte directly because it uses * context; - ArrayBufferAllocator* allocator; + v8::ArrayBuffer::Allocator* allocator; std::map> backing_stores; bool interrupted; size_t soft_memory_limit; @@ -156,7 +146,6 @@ enum IsolateData { }; static std::unique_ptr current_platform = NULL; -static std::mutex platform_lock; static void gc_callback(Isolate* isolate, GCType type, GCCallbackFlags flags) { ContextInfo* context_info = (ContextInfo*)isolate->GetData(CONTEXT_INFO); @@ -180,28 +169,22 @@ static void gc_callback(Isolate* isolate, GCType type, GCCallbackFlags flags) { } } -static void init_v8(char const* flags) { - // no need to wait for the lock if already initialized - if (current_platform != NULL) - return; - - platform_lock.lock(); +static void init_v8(char const* flags, + char const* icu_path, + char const* snapshot_path) { + V8::InitializeICU(icu_path); + V8::InitializeExternalStartupDataFromFile(snapshot_path); - if (current_platform == NULL) { - V8::InitializeICU(); - if (flags != NULL) { - V8::SetFlagsFromString(flags); - } - if (flags != NULL && strstr(flags, "--single-threaded") != NULL) { - current_platform = platform::NewSingleThreadedDefaultPlatform(); - } else { - current_platform = platform::NewDefaultPlatform(); - } - V8::InitializePlatform(current_platform.get()); - V8::Initialize(); + if (flags != NULL) { + V8::SetFlagsFromString(flags); } - - platform_lock.unlock(); + if (flags != NULL && strstr(flags, "--single-threaded") != NULL) { + current_platform = platform::NewSingleThreadedDefaultPlatform(); + } else { + current_platform = platform::NewDefaultPlatform(); + } + V8::InitializePlatform(current_platform.get()); + V8::Initialize(); } static void breaker(std::timed_mutex& breaker_mutex, void* d) { @@ -518,11 +501,15 @@ static void deallocate(void* data) { delete context_info; } -ContextInfo* MiniRacer_init_context(char const* v8_flags) { - init_v8(v8_flags); +void MiniRacer_init_v8(char const* v8_flags, + char const* icu_path, + char const* snapshot_path) { + init_v8(v8_flags, icu_path, snapshot_path); +} +ContextInfo* MiniRacer_init_context() { ContextInfo* context_info = new (xalloc(context_info)) ContextInfo(); - context_info->allocator = new ArrayBufferAllocator(); + context_info->allocator = ArrayBuffer::Allocator::NewDefaultAllocator(); Isolate::CreateParams create_params; create_params.array_buffer_allocator = context_info->allocator; @@ -759,8 +746,14 @@ LIB_EXPORT BinaryValue* mr_eval_context(ContextInfo* context_info, return res; } -LIB_EXPORT ContextInfo* mr_init_context(const char* v8_flags) { - return MiniRacer_init_context(v8_flags); +LIB_EXPORT void mr_init_v8(const char* v8_flags, + const char* icu_path, + const char* snapshot_path) { + MiniRacer_init_v8(v8_flags, icu_path, snapshot_path); +} + +LIB_EXPORT ContextInfo* mr_init_context() { + return MiniRacer_init_context(); } LIB_EXPORT void mr_free_value(ContextInfo* context_info, BinaryValue* val) { diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_babel.py b/tests/test_babel.py index e4ae2686..2dc8b946 100644 --- a/tests/test_babel.py +++ b/tests/test_babel.py @@ -1,35 +1,26 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - """ Basic JS types tests """ -import os -import unittest -from io import open - -from py_mini_racer import py_mini_racer - - -class Test(unittest.TestCase): - """ Test basic types """ - - def test_babel(self): - - context = py_mini_racer.MiniRacer() - - path = os.path.join(os.path.dirname(__file__), 'fixtures/babel.js') - babel_source = open(path, "r", encoding='utf-8').read() - source = """ - var self = this; - %s - babel.eval = function(code) { - return eval(babel.transform(code)["code"]); - } - """ % babel_source - context.eval(source) - self.assertEqual(64, context.eval("babel.eval(((x) => x * x)(8))")) - - -if __name__ == '__main__': - import sys - sys.exit(unittest.main()) +from os.path import dirname +from os.path import join as pathjoin + +from py_mini_racer import MiniRacer + + +def test_babel(): + context = MiniRacer() + + path = pathjoin(dirname(__file__), "fixtures/babel.js") + with open(path, encoding="utf-8") as f: + babel_source = f.read() + source = ( + """ + var self = this; + %s + babel.eval = function(code) { + return eval(babel.transform(code)["code"]); + } + """ + % babel_source + ) + context.eval(source) + assert context.eval("babel.eval(((x) => x * x)(8))") == 64 diff --git a/tests/test_call.py b/tests/test_call.py index da39a6c9..82c64b71 100644 --- a/tests/test_call.py +++ b/tests/test_call.py @@ -1,51 +1,39 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - """ Basic JS call functions """ -import json -import unittest -from datetime import datetime - -from py_mini_racer import py_mini_racer - - -class TestEval(unittest.TestCase): - """ Test calling a function """ - - def setUp(self): - - self.mr = py_mini_racer.MiniRacer() +from datetime import datetime, timezone +from json import JSONEncoder - def test_call_js(self): +from py_mini_racer import MiniRacer - js_func = """var f = function() { - return arguments.length; - }""" - self.mr.eval(js_func) +def test_call_js(): + js_func = """var f = function() { + return arguments.length; + }""" - self.assertEqual(self.mr.call('f'), 0) - self.assertEqual(self.mr.call('f', *list(range(5))), 5) - self.assertEqual(self.mr.call('f', *list(range(10))), 10) - self.assertEqual(self.mr.call('f', *list(range(20))), 20) + mr = MiniRacer() + mr.eval(js_func) - def test_call_custom_encoder(self): + assert mr.call("f") == 0 + assert mr.call("f", *list(range(5))) == 5 + assert mr.call("f", *list(range(10))) == 10 + assert mr.call("f", *list(range(20))) == 20 - # Custom encoder for dates - class CustomEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, datetime): - return obj.isoformat() +def test_call_custom_encoder(): + # Custom encoder for dates + class CustomEncoder(JSONEncoder): + def default(self, obj): + if isinstance(obj, datetime): + return obj.isoformat() - return json.JSONEncoder.default(self, obj) + return JSONEncoder.default(self, obj) - now = datetime.now() - js_func = """var f = function(args) { - return args; - }""" - self.mr.eval(js_func) + now = datetime.now(tz=timezone.utc) + js_func = """var f = function(args) { + return args; + }""" + mr = MiniRacer() + mr.eval(js_func) - self.assertEqual(self.mr.call('f', now, encoder=CustomEncoder), - now.isoformat()) + assert mr.call("f", now, encoder=CustomEncoder) == now.isoformat() diff --git a/tests/test_eval.py b/tests/test_eval.py index 03f46ca1..0f65ed7d 100644 --- a/tests/test_eval.py +++ b/tests/test_eval.py @@ -1,140 +1,164 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - """ Basic JS types tests """ -import sys -import time -import unittest -from py_mini_racer import py_mini_racer +from time import sleep, time + +import pytest + +from py_mini_racer import ( + JSEvalException, + JSOOMException, + JSParseException, + JSSymbol, + JSTimeoutException, + MiniRacer, +) + + +def test_invalid(): + mr = MiniRacer() + + with pytest.raises(JSEvalException): + mr.eval("invalid") + +def test_global(): + mr = MiniRacer() + mr.eval("var xabc = 22;") + assert mr.eval("xabc") == 22 -class Test(unittest.TestCase): - """ Test basic types """ - def setUp(self): +def test_fun(): + mr = MiniRacer() + mr.eval("var x = function(y) {return y+1;}") - self.mr = py_mini_racer.MiniRacer() + assert mr.eval("x(1)") == 2 + assert mr.eval("x(10)") == 11 + assert mr.eval("x(100)") == 101 - def test_invalid(self): - with self.assertRaises(py_mini_racer.JSEvalException): - self.mr.eval("invalid") +def test_multiple_ctx(): + c1 = MiniRacer() + c2 = MiniRacer() + c3 = MiniRacer() - def test_global(self): - self.mr.eval('var xabc = 22;') - self.assertEqual(22, self.mr.eval('xabc')) + c1.eval("var x = 1") + c2.eval("var x = 2") + c3.eval("var x = 3") + assert c1.eval("(x)") == 1 + assert c2.eval("(x)") == 2 + assert c3.eval("(x)") == 3 - def test_fun(self): - self.mr.eval('var x = function(y) {return y+1;}') - self.assertEqual(2, self.mr.eval('x(1)')) - self.assertEqual(11, self.mr.eval('x(10)')) - self.assertEqual(101, self.mr.eval('x(100)')) +def test_exception_thrown(): + context = MiniRacer() - def test_multiple_ctx(self): + js_source = "var f = function() {throw 'error'};" - c1 = py_mini_racer.MiniRacer() - c2 = py_mini_racer.MiniRacer() - c3 = py_mini_racer.MiniRacer() + context.eval(js_source) - c1.eval('var x = 1') - c2.eval('var x = 2') - c3.eval('var x = 3') - self.assertEqual(c1.eval('(x)'), 1) - self.assertEqual(c2.eval('(x)'), 2) - self.assertEqual(c3.eval('(x)'), 3) + with pytest.raises(JSEvalException): + context.eval("f()") - def test_exception_thrown(self): - context = py_mini_racer.MiniRacer() - js_source = "var f = function() {throw 'error'};" +def test_cannot_parse(): + context = MiniRacer() + js_source = "var f = function(" + with pytest.raises(JSParseException) as exc_info: context.eval(js_source) - with self.assertRaises(py_mini_racer.JSEvalException): - context.eval("f()") - - @unittest.skipIf(sys.version_info[0] < 3, "no assertRaisesRegex on Python 2") - def test_cannot_parse(self): - context = py_mini_racer.MiniRacer() - js_source = "var f = function(" - - with self.assertRaisesRegex(py_mini_racer.JSParseException, '.*Unknown JavaScript error during parse.*'): - context.eval(js_source) - - def test_null_byte(self): - - context = py_mini_racer.MiniRacer() - - s = "\x00 my string!" - - # Try return a string including a null byte - in_val = "var str = \"" + s + "\"; str;" - result = context.eval(in_val) - self.assertEqual(result, s) - - def test_timeout(self): - timeout_ms = 100 - with self.assertRaises(py_mini_racer.JSTimeoutException): - start_time = time.time() - self.mr.eval('while(1) { }', timeout=timeout_ms) - duration = time.time() - start_time - assert timeout_ms <= duration * 1000 <= timeout_ms + 10 - - def test_max_memory_soft(self): - self.mr.set_soft_memory_limit(100000000) - with self.assertRaises(py_mini_racer.JSOOMException): - self.mr.eval('''let s = 1000; - var a = new Array(s); - a.fill(0); - while(true) { - s *= 1.1; - let n = new Array(Math.floor(s)); - n.fill(0); - a = a.concat(n); - }''', max_memory=200000000) - self.assertEqual(self.mr.was_soft_memory_limit_reached(), True) - - def test_max_memory_hard(self): - with self.assertRaises(py_mini_racer.JSOOMException): - self.mr.eval('''let s = 1000; - var a = new Array(s); - a.fill(0); - while(true) { - s *= 1.1; - let n = new Array(Math.floor(s)); - n.fill(0); - a = a.concat(n); - }''', max_memory=200000000) - - def test_symbol(self): - res = self.mr.eval('Symbol.toPrimitive') - self.assertEqual(type(res), py_mini_racer.JSSymbol) - - def test_async(self): - self.assertFalse(self.mr.eval(""" - var done = false; - const shared = new SharedArrayBuffer(8); - const view = new Int32Array(shared); - - const p = Atomics.waitAsync(view, 0, 0, 1); // 1 ms timeout - p.value.then(() => { done = true; }); - done - """)) - time.sleep(0.01) - self.assertFalse(self.mr.eval("done")) - self.assertTrue(self.mr.eval("done")) - - def test_fast_call(self): - self.mr.eval("const test = function () { return 42; }") - # this syntax is optimized and takes another execution path in the extension - self.assertEqual(self.mr.eval("test()"), 42) - # It looks like a fast call but it is not (it ends with '()' but no identifier) - # should not fail and do a classical eval. - self.assertEqual(self.mr.eval("1+test()"), 43) - - -if __name__ == '__main__': - import sys - sys.exit(unittest.main()) + assert b"Unknown JavaScript error during parse" in exc_info.value.args[0] + + +def test_null_byte(): + context = MiniRacer() + + s = "\x00 my string!" + + # Try return a string including a null byte + in_val = 'var str = "' + s + '"; str;' + result = context.eval(in_val) + assert result == s + + +def test_timeout(): + timeout_ms = 100 + start_time = time() + + mr = MiniRacer() + with pytest.raises(JSTimeoutException): + mr.eval("while(1) { }", timeout=timeout_ms) + + duration = time() - start_time + assert timeout_ms <= duration * 1000 <= timeout_ms + 200 + + +def test_max_memory_soft(): + mr = MiniRacer() + mr.set_soft_memory_limit(100000000) + with pytest.raises(JSOOMException): + mr.eval( + """let s = 1000; + var a = new Array(s); + a.fill(0); + while(true) { + s *= 1.1; + let n = new Array(Math.floor(s)); + n.fill(0); + a = a.concat(n); + }""", + max_memory=200000000, + ) + + assert mr.was_soft_memory_limit_reached() + + +def test_max_memory_hard(): + mr = MiniRacer() + with pytest.raises(JSOOMException): + mr.eval( + """let s = 1000; + var a = new Array(s); + a.fill(0); + while(true) { + s *= 1.1; + let n = new Array(Math.floor(s)); + n.fill(0); + a = a.concat(n); + }""", + max_memory=200000000, + ) + + +def test_symbol(): + mr = MiniRacer() + res = mr.eval("Symbol.toPrimitive") + assert isinstance(res, JSSymbol) + + +def test_async(): + mr = MiniRacer() + assert not mr.eval( + """ + var done = false; + const shared = new SharedArrayBuffer(8); + const view = new Int32Array(shared); + + const p = Atomics.waitAsync(view, 0, 0, 1); // 1 ms timeout + p.value.then(() => { done = true; }); + done + """ + ) + sleep(0.1) + assert not mr.eval("done") + assert mr.eval("done") + + +def test_fast_call(): + mr = MiniRacer() + mr.eval("const test = function () { return 42; }") + # this syntax is optimized and takes another execution path in the extension + assert mr.eval("test()") == 42 + # It looks like a fast call but it is not (it ends with '()' but no identifier) + # should not fail and do a classical eval. + assert mr.eval("1+test()") == 43 diff --git a/tests/test_heap.py b/tests/test_heap.py index 850c98ca..5790e597 100644 --- a/tests/test_heap.py +++ b/tests/test_heap.py @@ -1,18 +1,8 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +from py_mini_racer import MiniRacer -import unittest -from py_mini_racer import py_mini_racer +def test_heap_stats(): + mr = MiniRacer() - -class TestHeap(unittest.TestCase): - """ Test heap stats """ - - def setUp(self): - - self.mr = py_mini_racer.MiniRacer() - - def test_heap_stats(self): - self.assertGreater(self.mr.heap_stats()["used_heap_size"], 0) - self.assertGreater(self.mr.heap_stats()["total_heap_size"], 0) + assert mr.heap_stats()["used_heap_size"] > 0 + assert mr.heap_stats()["total_heap_size"] > 0 diff --git a/tests/test_strict.py b/tests/test_strict.py index 18a99202..86da7fdc 100644 --- a/tests/test_strict.py +++ b/tests/test_strict.py @@ -1,34 +1,40 @@ -import unittest +import pytest -from py_mini_racer import py_mini_racer +from py_mini_racer import JSEvalException, StrictMiniRacer -class StrictTestCase(unittest.TestCase): - """Test StrictMiniRacer""" +def test_basic_int(): + mr = StrictMiniRacer() + assert mr.execute("42") == 42 - def setUp(self): - self.mr = py_mini_racer.StrictMiniRacer() - def test_basic_int(self): - self.assertEqual(42, self.mr.execute("42")) +def test_basic_string(): + mr = StrictMiniRacer() + assert mr.execute('"42"') == "42" - def test_basic_string(self): - self.assertEqual("42", self.mr.execute('"42"')) - def test_basic_hash(self): - self.assertEqual({}, self.mr.execute('{}')) +def test_basic_hash(): + mr = StrictMiniRacer() + assert mr.execute("{}") == {} - def test_basic_array(self): - self.assertEqual([1, 2, 3], self.mr.execute('[1, 2, 3]')) - def test_call(self): - js_func = """var f = function(args) { - return args.length; - }""" +def test_basic_array(): + mr = StrictMiniRacer() + assert mr.execute("[1, 2, 3]") == [1, 2, 3] - self.assertIsNone(self.mr.eval(js_func)) - self.assertEqual(self.mr.call('f', list(range(5))), 5) - def test_message(self): - with self.assertRaises(py_mini_racer.JSEvalException): - self.mr.eval("throw new EvalError('Hello', 'someFile.js', 10);") +def test_call(): + js_func = """var f = function(args) { + return args.length; + }""" + + mr = StrictMiniRacer() + + assert mr.eval(js_func) is None + assert mr.call("f", list(range(5))) == 5 + + +def test_message(): + mr = StrictMiniRacer() + with pytest.raises(JSEvalException): + mr.eval("throw new EvalError('Hello', 'someFile.js', 10);") diff --git a/tests/test_types.py b/tests/test_types.py index ef05f943..515a9b1f 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,148 +1,163 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - """ Basic JS types tests """ -import json -import time -import unittest -from datetime import datetime +from datetime import datetime, timezone +from json import dumps +from time import time + +import pytest -from py_mini_racer import py_mini_racer +from py_mini_racer import JSEvalException, JSFunction, JSObject, MiniRacer -class Test(unittest.TestCase): - """ Test basic types """ +class Validator: + def __init__(self): + self.mr = MiniRacer() + + def validate(self, py_val, **kwargs): + testee = kwargs.get("testee", py_val) + js_str = dumps(py_val) - def valid(self, py_val, **kwargs): - if 'testee' in kwargs: - testee = kwargs['testee'] - else: - testee = py_val - js_str = json.dumps(py_val) parsed = self.mr.execute(js_str) - self.assertEqual(testee, parsed) - - def setUp(self): - - self.mr = py_mini_racer.MiniRacer() - - def test_str(self): - self.valid("'a string'") - self.valid("'a ' + 'string'") - - def test_unicode(self): - ustr = u"\N{GREEK CAPITAL LETTER DELTA}" - res = self.mr.eval("'" + ustr + "'") - self.assertEqual(ustr, res) - - def test_numbers(self): - self.valid(1) - self.valid(1.0) - self.valid(2**16) - self.valid(2**31 - 1) - self.valid(2**31) - self.valid(2**33) - - def test_arrays(self): - self.valid([1]) - self.valid([]) - self.valid([1, 2, 3]) - # Nested - self.valid([1, 2, ['a', 1]]) - - def test_none(self): - self.valid(None) - - def test_hash(self): - self.valid({}) - self.valid('{}') - self.valid({'a': 1}) - self.valid({" ": {'z': 'www'}}) - - def test_complex(self): - - self.valid({ - '1': [ - 1, 2, 'qwe', { - 'z': [ - 4, 5, 6, { - 'eqewr': 1, - 'zxczxc': 'qweqwe', - 'z': {'1': 2} - } - ] - } - ], 'qwe': 1 - }) - - def test_function(self): - res = self.mr.eval('var a = function(){}; a') - self.assertTrue(isinstance(res, py_mini_racer.JSFunction)) - - def test_object(self): - res = self.mr.eval('var a = {}; a') - self.assertTrue(isinstance(res, py_mini_racer.JSObject)) - - def test_timestamp(self): - val = int(time.time()) - res = self.mr.eval("var a = new Date(%d); a" % (val * 1000)) - self.assertEqual(res, datetime.utcfromtimestamp(val)) - - def test_date(self): - res = self.mr.eval("var a = new Date(Date.UTC(2014, 0, 2, 3, 4, 5)); a") - self.assertEqual(res, datetime(2014, 1, 2, 3, 4, 5)) - - def test_exception(self): - js_source = """ - var f = function(arg) { - throw 'error: '+arg - return nil - }""" - - self.mr.eval(js_source) - - with self.assertRaises(py_mini_racer.JSEvalException) as cm: - self.mr.eval("f(42)") - - self.assertIn('error: 42', cm.exception.args[0]) - - def test_array_buffer(self): - js_source = """ - var b = new ArrayBuffer(1024); - var v = new Uint8Array(b); - v[0] = 0x42; - b - """ - ret = self.mr.eval(js_source) - self.assertEqual(len(ret), 1024) - self.assertEqual(ret[0:1].tobytes(), b"\x42") - - def test_array_buffer_view(self): - js_source = """ - var b = new ArrayBuffer(1024); - var v = new Uint8Array(b, 1, 1); - v[0] = 0x42; - v - """ - ret = self.mr.eval(js_source) - self.assertEqual(len(ret), 1) - self.assertEqual(ret.tobytes(), b"\x42") - - def test_shared_array_buffer(self): - js_source = """ - var b = new SharedArrayBuffer(1024); - var v = new Uint8Array(b); - v[0] = 0x42; - b - """ - ret = self.mr.eval(js_source) - self.assertEqual(len(ret), 1024) - self.assertEqual(ret[0:1].tobytes(), b"\x42") - ret[1:2] = b"\xFF" - self.assertEqual(self.mr.eval("v[1]"), 0xFF) - - -if __name__ == '__main__': - import sys - sys.exit(unittest.main()) + assert testee == parsed + + +def test_str(): + v = Validator() + v.validate("'a string'") + v.validate("'a ' + 'string'") + + +def test_unicode(): + ustr = "\N{GREEK CAPITAL LETTER DELTA}" + mr = MiniRacer() + res = mr.eval("'" + ustr + "'") + assert ustr == res + + +def test_numbers(): + v = Validator() + v.validate(1) + v.validate(1.0) + v.validate(2**16) + v.validate(2**31 - 1) + v.validate(2**31) + v.validate(2**33) + + +def test_arrays(): + v = Validator() + v.validate([1]) + v.validate([]) + v.validate([1, 2, 3]) + # Nested + v.validate([1, 2, ["a", 1]]) + + +def test_none(): + v = Validator() + v.validate(None) + + +def test_hash(): + v = Validator() + v.validate({}) + v.validate("{}") + v.validate({"a": 1}) + v.validate({" ": {"z": "www"}}) + + +def test_complex(): + v = Validator() + v.validate( + { + "1": [ + 1, + 2, + "qwe", + {"z": [4, 5, 6, {"eqewr": 1, "zxczxc": "qweqwe", "z": {"1": 2}}]}, + ], + "qwe": 1, + } + ) + + +def test_function(): + mr = MiniRacer() + res = mr.eval("var a = function(){}; a") + assert isinstance(res, JSFunction) + + +def test_object(): + mr = MiniRacer() + res = mr.eval("var a = {}; a") + assert isinstance(res, JSObject) + + +def test_timestamp(): + val = int(time()) + mr = MiniRacer() + res = mr.eval("var a = new Date(%d); a" % (val * 1000)) + assert res == datetime.fromtimestamp(val, timezone.utc) + + +def test_date(): + mr = MiniRacer() + res = mr.eval("var a = new Date(Date.UTC(2014, 0, 2, 3, 4, 5)); a") + assert res == datetime(2014, 1, 2, 3, 4, 5, tzinfo=timezone.utc) + + +def test_exception(): + js_source = """ + var f = function(arg) { + throw 'error: '+arg + return nil + }""" + + mr = MiniRacer() + mr.eval(js_source) + + with pytest.raises(JSEvalException) as exc_info: + mr.eval("f(42)") + + assert "error: 42" in exc_info.value.args[0] + + +def test_array_buffer(): + js_source = """ + var b = new ArrayBuffer(1024); + var v = new Uint8Array(b); + v[0] = 0x42; + b + """ + mr = MiniRacer() + ret = mr.eval(js_source) + assert len(ret) == 1024 + assert ret[0:1].tobytes() == b"\x42" + + +def test_array_buffer_view(): + js_source = """ + var b = new ArrayBuffer(1024); + var v = new Uint8Array(b, 1, 1); + v[0] = 0x42; + v + """ + mr = MiniRacer() + ret = mr.eval(js_source) + assert len(ret) == 1 + assert ret.tobytes() == b"\x42" + + +def test_shared_array_buffer(): + js_source = """ + var b = new SharedArrayBuffer(1024); + var v = new Uint8Array(b); + v[0] = 0x42; + b + """ + mr = MiniRacer() + ret = mr.eval(js_source) + assert len(ret) == 1024 + assert ret[0:1].tobytes() == b"\x42" + ret[1:2] = b"\xFF" + assert mr.eval("v[1]") == 0xFF diff --git a/tests/test_wasm.py b/tests/test_wasm.py index e53206ef..8bdd731e 100644 --- a/tests/test_wasm.py +++ b/tests/test_wasm.py @@ -1,47 +1,45 @@ -# -*- coding: utf-8 -*- -import os -import unittest +"""Test executing a WASM module.""" + +from os.path import abspath, dirname, getsize +from os.path import join as pathjoin from py_mini_racer import MiniRacer -test_dir = os.path.dirname(os.path.abspath(__file__)) +test_dir = dirname(abspath(__file__)) + +def test_add(): + fn = pathjoin(test_dir, "add.wasm") + mr = MiniRacer() -class WASMTest(unittest.TestCase): + # 1. Allocate a buffer to hold the WASM module code + size = getsize(fn) + module_raw = mr.eval( + f""" + const moduleRaw = new SharedArrayBuffer({size}); + moduleRaw """ - Test executing a WASM module. + ) + + # 2. Read the WASM module code + with open(fn, "rb") as f: + assert f.readinto(module_raw) == size + + # 3. Instantiate the WASM module + mr.eval( + """ + var res = null; + WebAssembly.instantiate(new Uint8Array(moduleRaw)).then(result => { + res = result.instance; + }).catch(result => { res = result.message; }); """ + ) - def setUp(self): - self.mr = MiniRacer() - - def test_add(self): - fn = os.path.join(test_dir, "add.wasm") - - # 1. Allocate a buffer to hold the WASM module code - size = os.path.getsize(fn) - moduleRaw = self.mr.eval(""" - const moduleRaw = new SharedArrayBuffer({}); - moduleRaw - """.format(size)) - - # 2. Read the WASM module code - with open(fn, "rb") as f: - self.assertEqual(f.readinto(moduleRaw), size) - - # 3. Instantiate the WASM module - self.mr.eval(""" - var res = null; - WebAssembly.instantiate(new Uint8Array(moduleRaw)).then(result => { - res = result.instance; - }).catch(result => { res = result.message; }); - """) - - # 4. Wait for WASM module instantiation - while not self.mr.eval("res"): - pass + # 4. Wait for WASM module instantiation + while not mr.eval("res"): + pass - self.assertTrue(self.mr.eval("typeof res !== 'string'")) + assert mr.eval("typeof res !== 'string'") - # 5. Execute a WASM function - self.assertEqual(self.mr.eval("res.exports.addTwo(1, 2)"), 3) + # 5. Execute a WASM function + assert mr.eval("res.exports.addTwo(1, 2)") == 3 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 93cac702..00000000 --- a/tox.ini +++ /dev/null @@ -1,7 +0,0 @@ -[tox] -envlist = py27, py35, py36, py37, py38 - -[testenv] -setenv = - PYTHONPATH = {toxinidir}:{toxinidir}/py_mini_racer -commands = python setup.py test --addopts tests From c898f1209a2fc3ce476145337b1511f7374a4f35 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Wed, 6 Mar 2024 17:39:48 -0500 Subject: [PATCH 002/109] Fix up theme and logo --- data/favicon.ico | Bin 0 -> 1150 bytes mkdocs.yml | 3 +++ py_mini_racer.png | Bin 78857 -> 61789 bytes 3 files changed, 3 insertions(+) create mode 100644 data/favicon.ico diff --git a/data/favicon.ico b/data/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..40eec18588425190fde2a17633898d97444e6d05 GIT binary patch literal 1150 zcmcJOU1(Ba7{}jH5lGN(>UP}(?XsIP7{aV=wCSQWYWAU{F~^|AV%sDE0RX6yT!W|U=7!puDy?tjCzDAu`{>%deXP&( zDekDa$7VgOq4gu?AZs)FxhT=IVCJngoR)6JmV4H4=Ah1>Pe>?$q8cgSM}u6*8fFid z^X2(SfSV+B^*#ZPd${;fZ$3tHIR!aOC^KHpm*;&S`8C2fe#nB_%kGjcbE0FE@RNAv zVAkz*SDrWNrJ#j4VVk@;PF*^s9)ycrFmeXl*Rk@It{I-@Ju?3avJ0{ z7Z`Z1!aX_|$Y~?Mp9=|~T+G>=2HBn>N21H5JX1P{;=P&y5$<8U$iQb!1lBaG!u|6U oFDkB=#(({(@d%M{t01-pp&z^dR&*yqbK4NoRbiEEMp#n&8*veg4FCWD literal 0 HcmV?d00001 diff --git a/mkdocs.yml b/mkdocs.yml index 26f3b95a..9c4a3fc8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,6 +4,9 @@ theme: name: material custom_dir: data logo: py_mini_racer.png + favicon: favicon.ico + palette: + primary: green nav: - Home: index.md - API Reference: api.md diff --git a/py_mini_racer.png b/py_mini_racer.png index 7654b21185f0924b803074d74a92aacbb81c00b4..174e19041f874c3f37aa91787141af292bbf57f8 100644 GIT binary patch literal 61789 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 Y^&nb#rK-#(cmD0QC}-H2?qr literal 78857 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 Date: Wed, 6 Mar 2024 18:34:54 -0500 Subject: [PATCH 003/109] Let's use sccache on Mac and Windows too --- .github/workflows/pypi-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pypi-build.yml b/.github/workflows/pypi-build.yml index a68adc0e..b614bf38 100644 --- a/.github/workflows/pypi-build.yml +++ b/.github/workflows/pypi-build.yml @@ -308,6 +308,9 @@ jobs: # Fetch all tags fetch-depth: 0 + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + - uses: actions/setup-python@v5 with: # depot_tools still uses distutils which is gone in 3.12: From 3423b5b1268a9420e8b6df26c979b01112429c38 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Fri, 8 Mar 2024 16:31:07 -0500 Subject: [PATCH 004/109] Fix __del__ bug if can't load dll --- src/py_mini_racer/py_mini_racer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/py_mini_racer/py_mini_racer.py b/src/py_mini_racer/py_mini_racer.py index 7cb190aa..366c7065 100644 --- a/src/py_mini_racer/py_mini_racer.py +++ b/src/py_mini_racer/py_mini_racer.py @@ -360,7 +360,8 @@ def _free(self, res): self._dll.mr_free_value(self.ctx, res) def __del__(self): - if self._dll: + dll = getattr(self, "_dll", None) + if dll: self._dll.mr_free_context(getattr(self, "ctx", None)) From 468c5e0be5b4ac44067bd2507126a54de5fe360a Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Fri, 8 Mar 2024 16:43:08 -0500 Subject: [PATCH 005/109] Deflake test and fix Alpine compat note --- .github/workflows/pypi-build.yml | 9 +++++---- README.md | 2 +- tests/test_eval.py | 16 +++++++++++----- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pypi-build.yml b/.github/workflows/pypi-build.yml index b614bf38..fbb9e5d9 100644 --- a/.github/workflows/pypi-build.yml +++ b/.github/workflows/pypi-build.yml @@ -54,10 +54,11 @@ jobs: # build distro): - image: debian:bullseye - image: arm64v8/debian:bullseye - # And for Alpine we mostly measure compatibility in musl minor versions, and - # the oldest supported Alpine as of this writing (3.16) has the same minor - # version as 3.19, so use 3.19. (This helps bring a newer system clang, which - # works better with the V8 build.) + # Alpine 3.19 includes a clang new enough for V8 to build (with only minor + # patches!). Builds on 3.19 seem incompatible with <= 3.18 due to libstdc++ + # symbols. (And we can't just run on an old Alpine and update clang from the + # llvm site, because unlike Debian, the llvm project doesn't maintain + # updated packages for old Alpine distros.) - image: alpine:3.19 - image: arm64v8/alpine:3.19 diff --git a/README.md b/README.md index 12500a22..c10b73fe 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ PyMiniRacer is distributed using [wheels](https://pythonwheels.com/) on | Debian ≥ 11 | ✓ | ✓ | | RHEL ≥ 8 | ✓ | ✓ | | other Linuxes with glibc ≥ 2.31 | ✓ | ✓ | -| Alpine ≥ 3.16 | ✓ | ✓ | +| Alpine ≥ 3.19 | ✓ | ✓ | | other Linux with musl ≥ 1.2 | ✓ | ✓ | If you have a up-to-date pip and it doesn't use a wheel, you might have an environment diff --git a/tests/test_eval.py b/tests/test_eval.py index 0f65ed7d..c0f94ec3 100644 --- a/tests/test_eval.py +++ b/tests/test_eval.py @@ -82,15 +82,17 @@ def test_null_byte(): def test_timeout(): - timeout_ms = 100 + timeout = 0.1 start_time = time() mr = MiniRacer() with pytest.raises(JSTimeoutException): - mr.eval("while(1) { }", timeout=timeout_ms) + mr.eval("while(1) { }", timeout=int(timeout * 1000)) duration = time() - start_time - assert timeout_ms <= duration * 1000 <= timeout_ms + 200 + # Make sure it timed out on time, and allow a giant leeway (because aarch64 + # emulation tests are surprisingly slow!) + assert timeout <= duration <= timeout + 5 def test_max_memory_soft(): @@ -144,13 +146,17 @@ def test_async(): const shared = new SharedArrayBuffer(8); const view = new Int32Array(shared); - const p = Atomics.waitAsync(view, 0, 0, 1); // 1 ms timeout + const p = Atomics.waitAsync(view, 0, 0, 1000); // 1 s timeout p.value.then(() => { done = true; }); done """ ) - sleep(0.1) assert not mr.eval("done") + start = time() + # Give the 1-second wait 10 seconds to finish. (Emulated aarch64 tests are + # surprisingly slow!) + while time() - start < 10 and not mr.eval("done"): + sleep(0.1) assert mr.eval("done") From 7134a0078c1b8a8d94fce69f1eb675be21c69d45 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Fri, 8 Mar 2024 17:40:00 -0500 Subject: [PATCH 006/109] Add a GitHub Release job --- .github/workflows/pypi-build.yml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi-build.yml b/.github/workflows/pypi-build.yml index fbb9e5d9..82bb43d8 100644 --- a/.github/workflows/pypi-build.yml +++ b/.github/workflows/pypi-build.yml @@ -336,17 +336,34 @@ jobs: hatch run testinstalled:install dist/*.whl hatch run testinstalled:run - publish: - name: Upload release to PyPI + release: + name: Create GitHub release if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') needs: - linux-wheels - non-linux-wheels - sdist runs-on: ubuntu-latest + + steps: + - uses: actions/download-artifact@v3 + with: + path: dist + + - name: Make release + uses: ncipollo/release-action@v1 + with: + artifacts: dist/* + + publish: + name: Upload release to PyPI + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: + - release + runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/ + url: https://pypi.org/p/py-mini-racer permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: From 95f87cb9a3fcff25a974320630b7b6b8034eb6ea Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Fri, 8 Mar 2024 17:58:02 -0500 Subject: [PATCH 007/109] Fix up PyPI readme (Moving build yml to match what the readme has.) --- .github/workflows/{pypi-build.yml => build.yml} | 0 pyproject.toml | 5 +++++ 2 files changed, 5 insertions(+) rename .github/workflows/{pypi-build.yml => build.yml} (100%) diff --git a/.github/workflows/pypi-build.yml b/.github/workflows/build.yml similarity index 100% rename from .github/workflows/pypi-build.yml rename to .github/workflows/build.yml diff --git a/pyproject.toml b/pyproject.toml index a3bb10df..87f39ec3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,11 @@ content-type = "text/markdown" [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "README.md" +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +text = """ +## Release history +""" + [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "HISTORY.md" start-after = "# History" From 70a2b2230e3d8390e0c428a02143c69bc901c13e Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Fri, 8 Mar 2024 19:15:51 -0500 Subject: [PATCH 008/109] Re-allow changing flags in case someone needs that --- src/py_mini_racer/__init__.py | 4 +++ src/py_mini_racer/py_mini_racer.py | 43 ++++++++++++++++++++++-------- tests/test_init.py | 24 +++++++++++++++++ 3 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 tests/test_init.py diff --git a/src/py_mini_racer/__init__.py b/src/py_mini_racer/__init__.py index 9f49f622..6a577c06 100644 --- a/src/py_mini_racer/__init__.py +++ b/src/py_mini_racer/__init__.py @@ -1,5 +1,6 @@ from py_mini_racer import py_mini_racer +DEFAULT_V8_FLAGS = py_mini_racer.DEFAULT_V8_FLAGS JSEvalException = py_mini_racer.JSEvalException JSFunction = py_mini_racer.JSFunction JSOOMException = py_mini_racer.JSOOMException @@ -7,8 +8,11 @@ JSParseException = py_mini_racer.JSParseException JSSymbol = py_mini_racer.JSSymbol JSTimeoutException = py_mini_racer.JSTimeoutException +LibAlreadyInitializedError = py_mini_racer.LibAlreadyInitializedError +LibNotFoundError = py_mini_racer.LibNotFoundError MiniRacer = py_mini_racer.MiniRacer StrictMiniRacer = py_mini_racer.StrictMiniRacer +init_mini_racer = py_mini_racer.init_mini_racer __all__ = ["py_mini_racer", "MiniRacer"] diff --git a/src/py_mini_racer/py_mini_racer.py b/src/py_mini_racer/py_mini_racer.py index 366c7065..c0f46a38 100644 --- a/src/py_mini_racer/py_mini_racer.py +++ b/src/py_mini_racer/py_mini_racer.py @@ -11,7 +11,7 @@ from os.path import join as pathjoin from sys import platform, version_info from threading import Lock -from typing import ClassVar +from typing import ClassVar, Iterable, Iterator class MiniRacerBaseException(Exception): # noqa: N818 @@ -25,6 +25,15 @@ def __init__(self, path): super().__init__(f"Native library or dependency not available at {path}") +class LibAlreadyInitializedError(MiniRacerBaseException): + """MiniRacer-wrapped V8 build not found.""" + + def __init__(self): + super().__init__( + "MiniRacer was already initialized before the call to init_mini_racer" + ) + + class JSParseException(MiniRacerBaseException): """JavaScript could not be parsed.""" @@ -82,7 +91,7 @@ def _get_lib_filename(name): return prefix + name + ext -def _build_dll_handle(dll_path): +def _build_dll_handle(dll_path) -> ctypes.CDLL: handle = ctypes.CDLL(dll_path) handle.mr_init_v8.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p] @@ -129,7 +138,7 @@ def _build_dll_handle(dll_path): # modules: _SNAPSHOT_FILENAME = "snapshot_blob.bin" -_V8_FLAGS: list[str] = ["--single-threaded"] +DEFAULT_V8_FLAGS = ("--single-threaded",) def _open_resource_file(filename, exit_stack): @@ -151,7 +160,7 @@ def _check_path(path): @contextmanager -def _open_dll(): +def _open_dll(flags: Iterable[str]) -> Iterator[ctypes.CDLL]: dll_filename = _get_lib_filename("mini_racer") with ExitStack() as exit_stack: @@ -171,15 +180,15 @@ def _open_dll(): _check_path(icu_data_path) _check_path(snapshot_path) - dll = _build_dll_handle(dll_path) + handle = _build_dll_handle(dll_path) - dll.mr_init_v8( - " ".join(_V8_FLAGS).encode("utf-8"), + handle.mr_init_v8( + " ".join(flags).encode("utf-8"), icu_data_path.encode("utf-8"), snapshot_path.encode("utf-8"), ) - yield dll + yield handle _init_lock = Lock() @@ -187,15 +196,27 @@ def _open_dll(): _dll_handle = None -def _get_dll_handle(): +def init_mini_racer( + *, flags: Iterable[str] = DEFAULT_V8_FLAGS, ignore_duplicate_init=False +) -> ctypes.CDLL: + """Initialize py_mini_racer (and V8). + + This function can optionally be used to set V8 flags. This function can be called + at most once, before any instances of MiniRacer are initialized. Instances of + MiniRacer will automatically call this function to initialize MiniRacer and V8. + """ + + global _dll_handle_context_manager # noqa: PLW0603 global _dll_handle # noqa: PLW0603 with _init_lock: if _dll_handle is None: - _dll_handle_context_manager = _open_dll() + _dll_handle_context_manager = _open_dll(flags) _dll_handle = _dll_handle_context_manager.__enter__() # Note: we never call _dll_handle_context_manager.__exit__() because it's # designed as a singleton. But we could if we wanted to! + elif not ignore_duplicate_init: + raise LibAlreadyInitializedError return _dll_handle @@ -212,7 +233,7 @@ class MiniRacer: json_impl: ClassVar[object] = json def __init__(self): - self._dll = _get_dll_handle() + self._dll: ctypes.CDLL = init_mini_racer(ignore_duplicate_init=True) self.ctx = self._dll.mr_init_context() self.lock = Lock() diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 00000000..b2de7278 --- /dev/null +++ b/tests/test_init.py @@ -0,0 +1,24 @@ +import pytest + +from py_mini_racer import LibAlreadyInitializedError, init_mini_racer + + +def test_init(): + init_mini_racer(ignore_duplicate_init=True) + + with pytest.raises(LibAlreadyInitializedError): + init_mini_racer() + + init_mini_racer(ignore_duplicate_init=True) + + +# Unfortunately while init_mini_racer allows changing V8 flags, it's hard to test +# automatically because only the first use of V8 can set flags. We'd need to +# restart Python between tests. +# Here's a manual test: +# def test_init_flags(): +# from py_mini_racer import DEFAULT_V8_FLAGS, MiniRacer, init_mini_racer +# init_mini_racer(flags=(*DEFAULT_V8_FLAGS, '--no-use-strict')) +# mr = MiniRacer() +# # this would normally fail in strict JS mode because foo is not declared: +# mr.eval('foo = 4') From 348dcce6ff6622f5b1ff6abeddeeec4def04e138 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sat, 16 Mar 2024 13:58:48 -0400 Subject: [PATCH 009/109] Fork into github.com/bpcreech and just mini-racer --- .github/workflows/build.yml | 2 +- ARCHITECTURE.md | 2 +- CONTRIBUTING.md | 4 ++-- README.md | 14 +++++++++----- mkdocs.yml | 2 +- pyproject.toml | 7 ++++--- src/py_mini_racer/__about__.py | 4 ++-- tests/test_eval.py | 1 - tests/test_init.py | 1 - tests/test_strict.py | 1 - tests/test_types.py | 1 - 11 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82bb43d8..87a24320 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -363,7 +363,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/py-mini-racer + url: https://pypi.org/p/mini-racer permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 55206bb0..33e68ec5 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -126,7 +126,7 @@ plus our Python `ctypes`-friendly frontend. Because V8 takes so long to build (about 2-3 hours at present on the free GitHub Actions runners, and >12 hours when emulating `aarch64` on them), we want to build wheels for -PyPI. We don't want folks to have to build V8 when they `pip install py-mini-racer`!. +PyPI. We don't want folks to have to build V8 when they `pip install mini-racer`!. We build wheels for many operating systems and architectures based on popular demand via GitHib issues. Currently the list is diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e70f175c..8756ffe1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ You can contribute in many ways: ### Report Bugs -Report bugs at . +Report bugs at . If you are reporting a bug, please include: @@ -36,7 +36,7 @@ such. ## Submit Feedback The best way to send feedback is to file an issue at -. +. If you are proposing a feature: diff --git a/README.md b/README.md index c10b73fe..01dd6289 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![PyPI status indicator](https://img.shields.io/pypi/v/py_mini_racer.svg)](https://pypi.python.org/pypi/py_mini_racer) -[![Github workflow status indicator](https://github.com/sqreen/PyMiniRacer/actions/workflows/build.yml/badge.svg)](https://github.com/sqreen/PyMiniRacer/actions/workflows/build.yml) +[![PyPI status indicator](https://img.shields.io/pypi/v/mini_racer.svg)](https://pypi.python.org/pypi/mini_racer) +[![Github workflow status indicator](https://github.com/bpcreech/PyMiniRacer/actions/workflows/build.yml/badge.svg)](https://github.com/bpcreech/PyMiniRacer/actions/workflows/build.yml) [![ISC License](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC) Minimal, modern embedded V8 for Python. @@ -22,7 +22,7 @@ WASM modules. MiniRacer is straightforward to use: ```sh - $ pip install py-mini-racer + $ pip install mini-racer ``` and then: @@ -97,7 +97,7 @@ V8 heap information can be retrieved: ``` A WASM example is available in the -[`tests`](https://github.com/sqreen/PyMiniRacer/blob/master/tests/test_wasm.py). +[`tests`](https://github.com/bpcreech/PyMiniRacer/blob/master/tests/test_wasm.py). ## Compatibility @@ -129,10 +129,14 @@ See [the contribution guide](CONTRIBUTING.md). Built with love by [Sqreen](https://www.sqreen.com). PyMiniRacer launch was described in -[`this blog post`](https://blog.sqreen.com/embedding-javascript-into-python/). +[`this blog post`](https://web.archive.org/web/20230526172627/https://blog.sqreen.com/embedding-javascript-into-python/). PyMiniRacer is inspired by [mini_racer](https://github.com/SamSaffron/mini_racer), built for the Ruby world by Sam Saffron. +In 2024, PyMiniRacer was revived, and adopted by [Ben Creech](https://bpcreech.com). +Upon discussion with the original Sqreen authors, we decided to re-launch PyMiniRacer as +a fork under . + [`Cookiecutter-pypackage`](https://github.com/audreyr/cookiecutter-pypackage) was used as this package skeleton. diff --git a/mkdocs.yml b/mkdocs.yml index 9c4a3fc8..ce03b2b9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: PyMiniRacer -site_url: https://sqreen.github.io/PyMiniRacer +site_url: https://bpcreech.com/PyMiniRacer theme: name: material custom_dir: data diff --git a/pyproject.toml b/pyproject.toml index 87f39ec3..b6c78e12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,12 +7,13 @@ requires = [ ] [project] -name = "py-mini-racer" +name = "mini-racer" dynamic = ["version", "readme"] description = "Minimal, modern embedded V8 for Python." license = "ISC" requires-python = ">= 3.8" authors = [ + { name = "bpcreech", email = "mini-racer@bpcreech.com" }, { name = "Sqreen", email = "support@sqreen.com" }, ] keywords = [ @@ -27,7 +28,7 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/sqreen/PyMiniRacer" +Homepage = "https://github.com/bpcreech/PyMiniRacer" [tool.hatch.version] path = "src/py_mini_racer/__about__.py" @@ -63,7 +64,7 @@ start-after = "# History" [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] pattern = "\\(py_mini_racer.png\\)" -replacement = '(https://github.com/sqreen/PyMiniRacer/raw/main/py_mini_racer.png)' +replacement = '(https://github.com/bpcreech/PyMiniRacer/raw/main/py_mini_racer.png)' [tool.hatch.envs.default] dependencies = [ diff --git a/src/py_mini_racer/__about__.py b/src/py_mini_racer/__about__.py index 4404f90c..6d18e4b1 100644 --- a/src/py_mini_racer/__about__.py +++ b/src/py_mini_racer/__about__.py @@ -1,3 +1,3 @@ -__author__ = "Sqreen" -__email__ = "support@sqreen.com" +__author__ = "bpcreech" +__email__ = "mini-racer@bpcreech.com" __version__ = "0.7.0" diff --git a/tests/test_eval.py b/tests/test_eval.py index c0f94ec3..f370c715 100644 --- a/tests/test_eval.py +++ b/tests/test_eval.py @@ -3,7 +3,6 @@ from time import sleep, time import pytest - from py_mini_racer import ( JSEvalException, JSOOMException, diff --git a/tests/test_init.py b/tests/test_init.py index b2de7278..043389cc 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,5 +1,4 @@ import pytest - from py_mini_racer import LibAlreadyInitializedError, init_mini_racer diff --git a/tests/test_strict.py b/tests/test_strict.py index 86da7fdc..06845875 100644 --- a/tests/test_strict.py +++ b/tests/test_strict.py @@ -1,5 +1,4 @@ import pytest - from py_mini_racer import JSEvalException, StrictMiniRacer diff --git a/tests/test_types.py b/tests/test_types.py index 515a9b1f..6a563c68 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -5,7 +5,6 @@ from time import time import pytest - from py_mini_racer import JSEvalException, JSFunction, JSObject, MiniRacer From f6dfd05933e954f80098b5ef61dd0cb0b8cfbdb8 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sat, 16 Mar 2024 14:03:40 -0400 Subject: [PATCH 010/109] Fix link --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 01dd6289..4042ba34 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,8 @@ for the Ruby world by Sam Saffron. In 2024, PyMiniRacer was revived, and adopted by [Ben Creech](https://bpcreech.com). Upon discussion with the original Sqreen authors, we decided to re-launch PyMiniRacer as -a fork under . +a fork under and +. [`Cookiecutter-pypackage`](https://github.com/audreyr/cookiecutter-pypackage) was used as this package skeleton. From 479dffeb5a2959a89d37d6eee830a82ddbfdd39c Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sat, 16 Mar 2024 14:05:25 -0400 Subject: [PATCH 011/109] Run the build on tags too --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87a24320..2b06f4eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,6 @@ name: pypi-build -on: - push: - branches: - - main +on: push concurrency: group: build-${{ github.head_ref }} From 8f1fb27d11d6123adc153a40cef0bba0d6022b33 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sat, 16 Mar 2024 15:00:19 -0400 Subject: [PATCH 012/109] Stop canceling builds automatically --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b06f4eb..6c53398d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,6 @@ on: push concurrency: group: build-${{ github.head_ref }} - cancel-in-progress: true defaults: run: From d1b6f4d83306bf7b94a7827d45476017b6ec5382 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sat, 16 Mar 2024 15:47:55 -0400 Subject: [PATCH 013/109] Tell Hatch where the package lives post-fork --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b6c78e12..4916d7cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,11 @@ Homepage = "https://github.com/bpcreech/PyMiniRacer" [tool.hatch.version] path = "src/py_mini_racer/__about__.py" +[tool.hatch.build.targets.wheel] +# The PyPI package is mini_racer (forked from py_mini_racer) but we keep the original +# Python module name since this is intended to be a permanent, drop-in replacement: +packages = ["src/py_mini_racer"] + [tool.hatch.build.targets.wheel.hooks.custom] path = "./hatch_build.py" From 69b7cc31e36ccf49143f7c5bca23963e12e9a906 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sat, 16 Mar 2024 17:17:58 -0400 Subject: [PATCH 014/109] Add doc notes for Intl API --- ARCHITECTURE.md | 4 ++++ HISTORY.md | 5 +++-- README.md | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 33e68ec5..7377349d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -190,6 +190,10 @@ is, we could have the following separate components: 3.9 and 3.10 would be identical), but it wouldn't matter because it would be cheap and automatic. +This is similar to how the Ruby [`mini_racer`](https://github.com/rubyjs/mini_racer) and +[`libv8-node`](https://github.com/rubyjs/libv8-node) projects, which inspired +PyMiniRacer, work together today. + To sum up, to use `cibuildwheel`, we would still need our own *separate* multi-architecture build workflow for V8, *ahead of* the `cibuildwheel` step. So `cibuildwheel` could potentially simplify the actual wheel distribution for us, but it diff --git a/HISTORY.md b/HISTORY.md index 7e086127..71c17eed 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,8 +10,9 @@ present a small breaking change for advanced usage; the `EXTENSION_PATH` and `EXTENSION_NAME` module variables, and `MiniRacer.v8_flags` and `MiniRacer.ext` class variable have all been removed. -- Add support for the [ECMAScript internalization API](https://v8.dev/docs/i18n) and use - [fast startup snapshots](https://v8.dev/blog/custom-startup-snapshots) +- Add support for the [ECMAScript internalization API](https://v8.dev/docs/i18n) and + thus [the ECMA `Intl` API](https://tc39.es/ecma402/) +- Use [fast startup snapshots](https://v8.dev/blog/custom-startup-snapshots) - Switch from setuptools to Hatch - Switch from tox to Hatch - Switch from use of flake8 and isort to Hatch's wrapper of Ruff diff --git a/README.md b/README.md index 4042ba34..fb3249e5 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,14 @@ MiniRacer is ES6 capable: False ``` +MiniRacer supports [the ECMA `Intl` API](https://tc39.es/ecma402/): + +```python + # Indonesian dates! + >>> ctx.eval('Intl.DateTimeFormat(["ban", "id"]).format(new Date())') + '16/3/2024' +``` + V8 heap information can be retrieved: ```python From c4358a88025156143bbf4390159685468149341c Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sun, 17 Mar 2024 11:26:42 -0400 Subject: [PATCH 015/109] Note latest Python version, drop old cookiecutter note --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index fb3249e5..e5ea56a1 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ A WASM example is available in the ## Compatibility -PyMiniRacer is compatible with Python 3.8 and is based on `ctypes`. +PyMiniRacer is compatible with Python 3.8-3.12 and is based on `ctypes`. PyMiniRacer is distributed using [wheels](https://pythonwheels.com/) on [PyPI](https://pypi.org/). The wheels are intended to provide compatibility with: @@ -146,6 +146,3 @@ In 2024, PyMiniRacer was revived, and adopted by [Ben Creech](https://bpcreech.c Upon discussion with the original Sqreen authors, we decided to re-launch PyMiniRacer as a fork under and . - -[`Cookiecutter-pypackage`](https://github.com/audreyr/cookiecutter-pypackage) was used -as this package skeleton. From 85780b66b84b0e4cfdaaf49e6b2b9a86cb4dfdd2 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sun, 17 Mar 2024 16:06:43 -0400 Subject: [PATCH 016/109] tweak history --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 71c17eed..9ba368c7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -15,7 +15,7 @@ - Use [fast startup snapshots](https://v8.dev/blog/custom-startup-snapshots) - Switch from setuptools to Hatch - Switch from tox to Hatch -- Switch from use of flake8 and isort to Hatch's wrapper of Ruff +- Switch from flake8 and isort to Hatch's wrapper of Ruff - Switch from Sphinx to mkdocs (and hatch-mkdocs) - Switch from unittest to pytest - Add ARCHITECTURE.md and lots of code comments From 3ad0337527fc7f8279741cb19a440cc7e274a3ae Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Mon, 18 Mar 2024 11:11:15 -0400 Subject: [PATCH 017/109] Fix release artifact names --- .github/workflows/build.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c53398d..2ee1e3eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -215,7 +215,7 @@ jobs: - uses: actions/upload-artifact@v3 with: - name: wheel + name: wheels path: ./wheels/* if-no-files-found: error @@ -321,7 +321,7 @@ jobs: - uses: actions/upload-artifact@v3 with: - name: wheel + name: wheels path: dist/* if-no-files-found: error @@ -343,13 +343,11 @@ jobs: steps: - uses: actions/download-artifact@v3 - with: - path: dist - name: Make release uses: ncipollo/release-action@v1 with: - artifacts: dist/* + artifacts: "wheels/*,sdist/*" publish: name: Upload release to PyPI @@ -364,8 +362,9 @@ jobs: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/download-artifact@v3 - with: - path: dist + + - name: Make dist directory + run: mkdir dist && cp wheels/* sdist/* dist/ - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 From 7cb0a2c46672eb62f9c68cd586c8aa5ab8ba8aa9 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sat, 9 Mar 2024 10:58:45 -0500 Subject: [PATCH 018/109] Refactor and simplify C++ code This also improves errors on parse and fixes error message type to be str (not bytes). --- .pre-commit-config.yaml | 2 +- src/py_mini_racer/py_mini_racer.py | 57 +- src/v8_py_frontend/mini_racer.cc | 863 ++++++++++++----------------- tests/test_eval.py | 49 +- 4 files changed, 446 insertions(+), 525 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da6b0135..dff47fd1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,5 +28,5 @@ repos: name: Check C++ format entry: clang-format language: system - files: py_mini_racer/extension/mini_racer_extension.cc + files: src/v8_py_frontend/mini_racer.cc args: ['-style=Chromium', '-i'] diff --git a/src/py_mini_racer/py_mini_racer.py b/src/py_mini_racer/py_mini_racer.py index c0f46a38..9265db17 100644 --- a/src/py_mini_racer/py_mini_racer.py +++ b/src/py_mini_racer/py_mini_racer.py @@ -46,6 +46,10 @@ class JSOOMException(JSEvalException): """JavaScript execution ran out of memory.""" +class JSTerminatedException(JSEvalException): + """JavaScript execution terminated.""" + + class JSTimeoutException(JSEvalException): """JavaScript execution timed out.""" @@ -104,7 +108,6 @@ def _build_dll_handle(dll_path) -> ctypes.CDLL: ctypes.c_char_p, ctypes.c_int, ctypes.c_ulong, - ctypes.c_size_t, ] handle.mr_eval_context.restype = ctypes.POINTER(MiniRacerValueStruct) @@ -120,6 +123,8 @@ def _build_dll_handle(dll_path) -> ctypes.CDLL: handle.mr_heap_snapshot.argtypes = [ctypes.c_void_p] handle.mr_heap_snapshot.restype = ctypes.POINTER(MiniRacerValueStruct) + handle.mr_set_hard_memory_limit.argtypes = [ctypes.c_void_p, ctypes.c_size_t] + handle.mr_set_soft_memory_limit.argtypes = [ctypes.c_void_p, ctypes.c_size_t] handle.mr_set_soft_memory_limit.restype = None @@ -270,12 +275,15 @@ def eval( # noqa: A003 code = code.encode("utf8") with self.lock: + self._dll.mr_set_hard_memory_limit( + self.ctx, ctypes.c_size_t(max_memory or 0) + ) + res = self._dll.mr_eval_context( self.ctx, code, len(code), ctypes.c_ulong(timeout or 0), - ctypes.c_size_t(max_memory or 0), ) if not res: raise JSConversionException @@ -418,6 +426,7 @@ class MiniRacerTypes: parse_exception = 201 oom_exception = 202 timeout_exception = 203 + terminated_exception = 204 class MiniRacerValueStruct(ctypes.Structure): @@ -437,6 +446,27 @@ class ArrayBufferByte(ctypes.Structure): _pack_ = 1 +_ERRORS = { + MiniRacerTypes.parse_exception: ( + JSParseException, + "Unknown JavaScript error during parse", + ), + MiniRacerTypes.execute_exception: ( + JSEvalException, + "Uknown JavaScript error during execution", + ), + MiniRacerTypes.oom_exception: (JSOOMException, "JavaScript memory limit reached"), + MiniRacerTypes.timeout_exception: ( + JSTimeoutException, + "JavaScript was terminated by timeout", + ), + MiniRacerTypes.terminated_exception: ( + JSTerminatedException, + "JavaScript was terminated", + ), +} + + class MiniRacerValue: def __init__(self, ctx, ptr): self.ctx = ctx @@ -461,19 +491,18 @@ def _double_value(self): ptr = ctypes.c_char_p.from_buffer(self.ptr.contents) return ctypes.c_double.from_buffer(ptr).value + def _get_exception_msg(self): + msg = ctypes.c_char_p(self.value).value + return msg.decode("utf-8", errors="replace") + def _raise_from_error(self): - if self.type == MiniRacerTypes.parse_exception: - msg = ctypes.c_char_p(self.value).value - raise JSParseException(msg) - if self.type == MiniRacerTypes.execute_exception: - msg = ctypes.c_char_p(self.value).value - raise JSEvalException(msg.decode("utf-8", errors="replace")) - if self.type == MiniRacerTypes.oom_exception: - msg = ctypes.c_char_p(self.value).value - raise JSOOMException(msg) - if self.type == MiniRacerTypes.timeout_exception: - msg = ctypes.c_char_p(self.value).value - raise JSTimeoutException(msg) + error_info = _ERRORS.get(self.type) + if not error_info: + return + + klass, generic_msg = error_info + + raise klass(self._get_exception_msg() or generic_msg) def to_python(self): self._raise_from_error() diff --git a/src/v8_py_frontend/mini_racer.cc b/src/v8_py_frontend/mini_racer.cc index 2c37548e..688c33c0 100644 --- a/src/v8_py_frontend/mini_racer.cc +++ b/src/v8_py_frontend/mini_racer.cc @@ -1,7 +1,4 @@ #include -#include -#include -#include #include #include #include @@ -17,65 +14,8 @@ #define LIB_EXPORT __attribute__((visibility("default"))) #endif -template -static inline T* xalloc(T*& ptr, size_t x = sizeof(T)) { - void* tmp = malloc(x); - if (tmp == NULL) { - fprintf(stderr, "malloc failed. Aborting"); - abort(); - } - ptr = static_cast(tmp); - return static_cast(ptr); -} - using namespace v8; -struct ContextInfo { - Isolate* isolate; - Persistent* context; - v8::ArrayBuffer::Allocator* allocator; - std::map> backing_stores; - bool interrupted; - size_t soft_memory_limit; - bool soft_memory_limit_reached; - size_t hard_memory_limit; - bool hard_memory_limit_reached; -}; - -struct EvalResult { - bool parsed; - bool executed; - bool terminated; - bool timed_out; - Persistent* value; - Persistent* message; - Persistent* backtrace; - - ~EvalResult() { - kill_value(value); - kill_value(message); - kill_value(backtrace); - } - - private: - static void kill_value(Persistent* val) { - if (!val) { - return; - } - val->Reset(); - delete val; - } -}; - -typedef struct { - ContextInfo* context_info; - const char* eval; - int eval_len; - unsigned long timeout; - EvalResult* result; - size_t max_memory; -} EvalParams; - enum BinaryTypes { type_invalid = 0, type_null = 1, @@ -97,20 +37,88 @@ enum BinaryTypes { type_parse_exception = 201, type_oom_exception = 202, type_timeout_exception = 203, + type_terminated_exception = 204, }; struct BinaryValue { union { void* ptr_val; - char* str_val; - uint32_t int_val; + char* bytes; + uint64_t int_val; double double_val; }; - enum BinaryTypes type = type_invalid; + BinaryTypes type = type_invalid; size_t len; + + BinaryValue() {} + + BinaryValue(const std::string& str, BinaryTypes type) + : type(type), len(str.size()) { + bytes = new char[len + 1]; + std::copy(str.begin(), str.end(), bytes); + bytes[len] = '\0'; + } }; -void BinaryValueFree(ContextInfo* context_info, BinaryValue* v) { +class MiniRacerContext; + +class BinaryValueDeleter { + public: + BinaryValueDeleter() : context(0) {} + BinaryValueDeleter(MiniRacerContext* context) : context(context) {} + void operator()(BinaryValue* bv) const; + + private: + MiniRacerContext* context; +}; + +typedef std::unique_ptr BinaryValuePtr; + +class MiniRacerContext { + public: + MiniRacerContext(); + ~MiniRacerContext(); + + Isolate* isolate; + Persistent* persistentContext; + v8::ArrayBuffer::Allocator* allocator; + std::map> backing_stores; + size_t soft_memory_limit; + bool soft_memory_limit_reached; + size_t hard_memory_limit; + bool hard_memory_limit_reached; + + template + BinaryValuePtr makeBinaryValue(Args&&... args); + + void BinaryValueFree(BinaryValue* v); + + std::optional valueToUtf8String(Local value); + + void gc_callback(Isolate* isolate); + void set_hard_memory_limit(size_t limit); + void set_soft_memory_limit(size_t limit); + BinaryValuePtr convert_v8_to_binary(Local context, + Local value); + BinaryValuePtr heap_snapshot(); + BinaryValuePtr heap_stats(); + BinaryValuePtr eval(const std::string& code, unsigned long timeout); + BinaryValuePtr summarizeTryCatch(Local& context, + TryCatch& trycatch, + BinaryTypes resultType); +}; + +inline void BinaryValueDeleter::operator()(BinaryValue* bv) const { + context->BinaryValueFree(bv); +} + +template +inline BinaryValuePtr MiniRacerContext::makeBinaryValue(Args&&... args) { + return BinaryValuePtr(new BinaryValue(std::forward(args)...), + BinaryValueDeleter(this)); +} + +void MiniRacerContext::BinaryValueFree(BinaryValue* v) { if (!v) { return; } @@ -119,8 +127,9 @@ void BinaryValueFree(ContextInfo* context_info, BinaryValue* v) { case type_parse_exception: case type_oom_exception: case type_timeout_exception: + case type_terminated_exception: case type_str_utf8: - free(v->str_val); + delete[] v->bytes; break; case type_bool: case type_double: @@ -135,262 +144,265 @@ void BinaryValueFree(ContextInfo* context_info, BinaryValue* v) { break; case type_shared_array_buffer: case type_array_buffer: - context_info->backing_stores.erase(v); + backing_stores.erase(v); break; } - free(v); + delete v; } -enum IsolateData { - CONTEXT_INFO, -}; - -static std::unique_ptr current_platform = NULL; - -static void gc_callback(Isolate* isolate, GCType type, GCCallbackFlags flags) { - ContextInfo* context_info = (ContextInfo*)isolate->GetData(CONTEXT_INFO); +static std::unique_ptr current_platform = nullptr; - if (context_info == nullptr) { - return; - } +static void static_gc_callback(Isolate* isolate, + GCType type, + GCCallbackFlags flags, + void* data) { + ((MiniRacerContext*)data)->gc_callback(isolate); +} +void MiniRacerContext::gc_callback(Isolate* isolate) { HeapStatistics stats; isolate->GetHeapStatistics(&stats); size_t used = stats.used_heap_size(); - context_info->soft_memory_limit_reached = - (used > context_info->soft_memory_limit); - isolate->MemoryPressureNotification((context_info->soft_memory_limit_reached) + soft_memory_limit_reached = (used > soft_memory_limit); + isolate->MemoryPressureNotification((soft_memory_limit_reached) ? v8::MemoryPressureLevel::kModerate : v8::MemoryPressureLevel::kNone); - if (used > context_info->hard_memory_limit) { - context_info->hard_memory_limit_reached = true; + if (used > hard_memory_limit) { + hard_memory_limit_reached = true; isolate->TerminateExecution(); } } -static void init_v8(char const* flags, - char const* icu_path, - char const* snapshot_path) { - V8::InitializeICU(icu_path); - V8::InitializeExternalStartupDataFromFile(snapshot_path); +void MiniRacerContext::set_hard_memory_limit(size_t limit) { + hard_memory_limit = limit; + hard_memory_limit_reached = false; - if (flags != NULL) { - V8::SetFlagsFromString(flags); - } - if (flags != NULL && strstr(flags, "--single-threaded") != NULL) { - current_platform = platform::NewSingleThreadedDefaultPlatform(); - } else { - current_platform = platform::NewDefaultPlatform(); - } - V8::InitializePlatform(current_platform.get()); - V8::Initialize(); -} - -static void breaker(std::timed_mutex& breaker_mutex, void* d) { - EvalParams* data = (EvalParams*)d; - - if (!breaker_mutex.try_lock_for(std::chrono::milliseconds(data->timeout))) { - data->result->timed_out = true; - data->context_info->isolate->TerminateExecution(); + if (limit > 0) { + isolate->AddGCEpilogueCallback(static_gc_callback, this); } } -static void set_hard_memory_limit(ContextInfo* context_info, size_t limit) { - context_info->hard_memory_limit = limit; - context_info->hard_memory_limit_reached = false; +void MiniRacerContext::set_soft_memory_limit(size_t limit) { + soft_memory_limit = limit; + soft_memory_limit_reached = false; } -static bool maybe_fast_call(const char* eval, int eval_len) { - // Does the eval string ends with '()'? +static bool maybe_fast_call(const std::string& code) { + // Does the code string end with '()'? // TODO check if the string is an identifier - return (eval_len > 2 && eval[eval_len - 2] == '(' && - eval[eval_len - 1] == ')'); + return (code.size() > 2 && code[code.size() - 2] == '(' && + code[code.size() - 1] == ')'); } -static void* nogvl_context_eval(void* arg) { - EvalParams* eval_params = (EvalParams*)arg; - EvalResult* result = eval_params->result; - Isolate* isolate = eval_params->context_info->isolate; - Isolate::Scope isolate_scope(isolate); - HandleScope handle_scope(isolate); - - TryCatch trycatch(isolate); +BinaryValuePtr MiniRacerContext::summarizeTryCatch(Local& context, + TryCatch& trycatch, + BinaryTypes resultType) { + if (!trycatch.StackTrace(context).IsEmpty()) { + Local stacktrace; + + if (trycatch.StackTrace(context).ToLocal(&stacktrace)) { + std::optional backtrace = valueToUtf8String(stacktrace); + if (backtrace.has_value()) { + // Generally the backtrace from V8 starts with the exception message, so + // we can skip the exception message (below) when we have the backtrace. + return makeBinaryValue(backtrace.value(), resultType); + } + } + } - Local context = eval_params->context_info->context->Get(isolate); + // Fall back to the backtrace-less exception message: + if (!trycatch.Exception()->IsNull()) { + std::optional message = + valueToUtf8String(trycatch.Exception()); + if (message.has_value()) { + return makeBinaryValue(message.value(), resultType); + } + } - Context::Scope context_scope(context); + // Send no message at all; the recipient can fill in generic messages based on + // the type code. + return makeBinaryValue("", resultType); +} - set_hard_memory_limit(eval_params->context_info, eval_params->max_memory); +/** Spawns a separate thread which calls isolate->TerminateExecution() after a + * timeout, if not first disengaged. */ +class BreakerThread { + public: + BreakerThread(Isolate* isolate, unsigned long timeout) + : isolate(isolate), timeout(timeout), timed_out_(false) { + if (timeout > 0) { + engaged = true; + mutex.lock(); + thread_ = std::thread(&BreakerThread::threadMain, this); + } else { + engaged = false; + } + } - result->parsed = false; - result->executed = false; - result->terminated = false; - result->timed_out = false; - result->value = NULL; + ~BreakerThread() { disengage(); } - std::timed_mutex breaker_mutex; - std::thread breaker_thread; + bool timed_out() { return timed_out_; } - // timeout limit - auto timeout = eval_params->timeout; - if (timeout > 0) { - breaker_mutex.lock(); - breaker_thread = - std::thread(&breaker, std::ref(breaker_mutex), (void*)eval_params); + void disengage() { + if (engaged) { + mutex.unlock(); + thread_.join(); + engaged = false; + } } - // memory limit - if (eval_params->max_memory > 0) { - isolate->AddGCEpilogueCallback(gc_callback); + + private: + void threadMain() { + if (!mutex.try_lock_for(std::chrono::milliseconds(timeout))) { + timed_out_ = true; + isolate->TerminateExecution(); + } } - MaybeLocal maybe_value; + Isolate* isolate; + unsigned long timeout; + bool engaged; + bool timed_out_; + std::thread thread_; + std::timed_mutex mutex; +}; + +BinaryValuePtr MiniRacerContext::eval(const std::string& code, + unsigned long timeout) { + Locker lock(isolate); + Isolate::Scope isolate_scope(isolate); + + TryCatch trycatch(isolate); + + // Later in this function, we pump the V8 message loop. + // Per comment in v8/samples/shell.cc, it is important not to pump the message + // loop when there are v8::Local handles on the stack, as this may trigger a + // stackless GC when the new conservative stack scanning flag is enabled. So + // we don't use any Local handles here; only in sub-scopes of this method. + + // Spawn a thread to inforce the timeout limit: + BreakerThread breaker_thread(isolate, timeout); + + bool parsed = false; + bool executed = false; + BinaryValuePtr ret; // Is it a single function call? - if (maybe_fast_call(eval_params->eval, eval_params->eval_len)) { + if (maybe_fast_call(code)) { + HandleScope handle_scope(isolate); + Local context = persistentContext->Get(isolate); + Context::Scope context_scope(context); Local identifier; Local func; // Let's check if the value is a callable identifier - result->parsed = - String::NewFromUtf8(isolate, eval_params->eval, NewStringType::kNormal, - eval_params->eval_len - 2) - .ToLocal(&identifier) && - context->Global()->Get(context, identifier).ToLocal(&func) && - func->IsFunction(); - - if (result->parsed) { + parsed = String::NewFromUtf8(isolate, code.data(), NewStringType::kNormal, + static_cast(code.size() - 2)) + .ToLocal(&identifier) && + context->Global()->Get(context, identifier).ToLocal(&func) && + func->IsFunction(); + + if (parsed) { // Call the identifier - maybe_value = Local::Cast(func)->Call( + MaybeLocal maybe_value = Local::Cast(func)->Call( context, v8::Undefined(isolate), 0, {}); - result->executed = !maybe_value.IsEmpty(); + if (!maybe_value.IsEmpty()) { + executed = true; + ret = convert_v8_to_binary(context, maybe_value.ToLocalChecked()); + } } } // Fallback on a slower full eval - if (!result->executed) { - Local eval; + if (!executed) { + HandleScope handle_scope(isolate); + Local context = persistentContext->Get(isolate); + Context::Scope context_scope(context); + Local asString; Local